| name | injection-guard |
| version | 0.13.0 |
| implements_spec | 0.6.0 |
| description | Generate or audit a prompt-injection defense that satisfies AgenticApps core spec §14 for the project's tech stack. Three subcommands: `init` (greenfield scaffold — detect stacks, materialise the trust-classification enforcement (TS ESLint rule / Go analyzer), the `fenceUntrusted` + canary helpers, the untrusted-input registry, and the attack-matrix regression test behind consent gates, then write the `injection_guard:` metadata block), `scan` (brownfield audit → .injection-scan-report.md; finds tenant-untrusted values reaching prompts without a trust marker, ranked by confidence), and `scan-apply` (apply high-confidence findings — add markers, scaffold missing enforcement assets — with per-file consent). Use when adding or auditing an LLM prompt-building path, tenant-untrusted tagging, prompt fencing, output validation / canary checks, tool-dispatch allowlists, or a §14 prompt-injection regression test in a new or existing service. The shared attack-family corpus and per-stack templates live in ./corpus, ./templates, and the per-subcommand procedure in ./init, ./scan, ./scan-apply. Host-neutral — /injection-guard on Claude, $injection-guard on Codex. |
injection-guard — AgenticApps prompt-injection scaffolder + auditor
This skill implements the generator obligation in core spec §14 (the
static-enforcement, registry, fencing, output-validation, and dynamic-test
machinery of §14.2–§14.7) for AgenticApps projects that build LLM prompts from
non-self-authored values (callbot, cparx, fx-signal-agent, and future
LLM-bearing projects). It is a host-neutral skill: when invoked, you (the
agent) follow the procedure prompts below using the Read, Grep, Write, and
Edit tools.
It is the prompt-injection sibling of the observability skill in this repo:
same install surface (install.sh / install-codex.sh), same migration chain,
same consent-gated init / scan / scan-apply shape. §14 was generalized from
fx-signal-agent's REQ-SEC03 / D-05 / D-06 the way §10 was generalized from the
observability wrapper (see ADR-0016 in agenticapps-workflow-core).
Architecture-first (read before scaffolding)
Per spec §14, detection is the weakest layer. This skill does NOT scaffold
a heuristic "does this input look malicious" classifier as a primary control.
The load-bearing assets it generates are, in priority order:
- Trust separation (§14.1, §14.4) — call-site classification +
fenceUntrusted.
- Output validation + canary (§14.5).
- Least privilege (§14.6) — allowlist + confirmation, where tools are dispatched.
Static enforcement (§14.2) and the dynamic attack-matrix test (§14.7) exist to
keep those guarantees from eroding; they are not themselves the guarantees.
Dispatch
The skill takes one positional argument (the subcommand) and delegates to a
sub-skill prompt:
| Subcommand | Sub-skill prompt | Purpose |
|---|
init | ./init/INIT.md | Greenfield scaffold of all §14 enforcement assets per stack, behind consent gates. |
scan | ./scan/SCAN.md | Walk prompt-building globs, find untrusted values reaching prompts without a marker → .injection-scan-report.md. |
scan-apply | ./scan-apply/APPLY.md | Apply high-confidence findings (add markers, scaffold missing assets) with per-file consent. |
If the subcommand is omitted, default to scan (the safest no-op operation;
produces a report without modifying anything).
Routing-table structural invariant: every Sub-skill prompt path in the
table above MUST exist on disk inside this skill directory. Dispatch resolves
routed paths relative to the skill root; a missing routed path means the
subcommand is unrunnable. Mechanical check:
grep -oiE '\./[a-zA-Z/_-]+\.md' injection-guard/SKILL.md | sort -u | while read rel; do
abs="injection-guard/${rel#./}"
[ -f "$abs" ] && echo " OK $rel" || echo " MISSING $rel"
done
Resolution rules
Per spec §10.7.1 (reused here), all target paths in this skill are resolved
against the language module root (the directory containing the stack's
manifest — package.json, go.mod, …), not the project root. The init and
scan procedures detect module roots from each stack's
templates/<stack-id>/meta.yaml path_root. Multi-root repos (e.g.
fx-signal-agent's 7 roots) are handled per root; --allow-partial lets a run
scaffold the roots it can and report the rest.
The shared attack-family corpus
The attack-family corpus is defined once at ./corpus/injection-attacks.json
and emitted into each host so every stack tests the same families (§14.7).
Minimum families: instruction-override, role-reassign,
system-prompt-probe, data-exfiltration, delimiter-structure. Hosts extend
their emitted copy with real attempts seen in logs; they do not edit the
canonical corpus here.
Stack templates
Each stack at ./templates/<stack-id>/ carries a meta.yaml (detection signals
- prompt-building globs + target paths + parameters). The §14 TypeScript
assets are runtime-agnostic and shared once in
./templates/_ts-common/
(fence-untrusted.ts, eslint-rules/, the vitest tests); each TS stack's
meta.yaml references it via shared_assets: _ts-common. Go assets are not
shared (single Go stack) and live in ./templates/go-fly-http/.
| Stack ID | Manifest | §14.2 binding | Assets source |
|---|
ts-cloudflare-worker | package.json | ESLint rule (no-untagged-tenant-strings-in-prompts) | _ts-common (vitest) |
ts-cloudflare-pages | package.json | ESLint rule | _ts-common (vitest) |
ts-supabase-edge | supabase/config.toml | ESLint rule | _ts-common (+ Deno test wrapper on emit) |
ts-react-vite | package.json | ESLint rule | _ts-common — but see warning below |
go-fly-http | go.mod | go/analysis analyzer (untrustedprompt) | go-fly-http/ (promptguard + analyzer + cmd runner) |
The TypeScript ESLint rule ships the v2 variant that also flags indirect
const msgs = [...] / identifier construction (closing the documented §14.2 v1
gap), behind the checkIndirect option so v1-only repos can opt down.
ts-react-vite is special-cased. Prompt building in a browser bundle is
itself a §14 finding (HIGH): a client-side prompt exposes the system prompt and
canary to the user and usually ships the model API key to the browser. scan
and init MUST surface the stack's applicability_warning and recommend moving
prompt construction server-side; they MUST NOT report a clean §14 posture for a
client-side prompt path. The primitives still scaffold (defense-in-depth), but
detection ≠ approval.
Conformance with spec §14
This skill satisfies §14's generator-facing obligations:
- Trust classification + static enforcement (§14.1–§14.2) —
init scaffolds the
call-site marker convention and the per-language CI enforcer; scan finds
unmarked untrusted→prompt flows.
- Untrusted-input registry (§14.3) —
init emits the registry doc + its
refresh command; scan reconciles it against discovered call sites.
- Runtime trust separation (§14.4) —
init emits the fenceUntrusted helper.
- Output validation + canary (§14.5) —
init emits the canary helper and the
output-validation hook.
- Least privilege (§14.6) —
init emits the tool-dispatch allowlist + confirmation
scaffold where the host dispatches tools; records N/A for single-shot surfaces.
- Dynamic regression test (§14.7) —
init emits the attack-matrix test seeded
from ./corpus/injection-attacks.json, with model-drift pinning.
- Consent —
scan-apply shows per-file diffs and requires explicit confirmation;
never auto-applies (fail-closed: a dirty/hand-modified asset is skipped and
reported, never overwritten).
When to invoke
| User intent | Subcommand |
|---|
| New LLM-bearing project; wire §14 defense from scratch | init |
| Existing project; audit injection surface, no changes | scan |
| Existing project; apply scan-found high-confidence gaps | scan-apply |
| Project has no LLM prompt-building path | none — declare §14 N/A per §09 |
The migration that propagates this skill into host repos is
migrations/0023-prompt-injection-defense.md in this repo, consumed by the
migrations framework (per ADR-0013).