T09 · Insecure Skill Coding Practices
- Location
scripts/guard.py:222- Finding
Blocked Content Is Emitted with a Successful Exit Status in Wrap Mode
- Content
View full analysis
Vulnerability Details
File Location:
scripts/guard.py, lines 222–229
Vulnerability Type: Fail-open handling of detected prompt-injection content
Risk Level: Mediumpython # --wrap: emit sandwich-wrapped content regardless of verdict, then exit if args.wrap: print(SANDWICH_TEMPLATE.format(content=content)) if result != "CLEAN": # Write scan result to stderr so callers can still check label = hits[0]["label"] if hits else "" print(f"# GUARD NOTE: {result} — {label}", file=sys.stderr) sys.exit(0)Technical Analysis
The
--wrapexecution path prints the original untrusted content inside boundary markers regardless of whether the scan result isCLEAN,SUSPICIOUS, orBLOCKED. It then exits with status code0.This behavior contradicts the documented policy in
SKILL.md, which states that blocked content must not be passed to an LLM or knowledge base. The only indication of a blocked verdict is written to standard error. Integrations that consume standard output, ignore standard error, or rely on the process exit code will interpret the operation as successful and may forward the detected payload.Wrapping content in natural-language boundary markers is not a security boundary. An LLM may still follow adversarial instructions embedded in the wrapped content. The scanner therefore detects a prohibited payload but fails open at the enforcement point.
Attack Path
- An attacker supplies external content containing a pattern classified as high risk.
- An ingestion pipeline invokes
guard.py --text "..." --wrapor uses--file ... --wrap. - The scanner correctly produces an internal
BLOCKEDverdict. - The wrap branch nevertheless emits the attacker-controlled content to standard output.
- The script reports success with exit code
0; the blocked status appears only on standard error. - A caller that forwards standard output or checks only the exit status passes the detected injec ...[truncated 895 chars]
- Remediation
View remediation
Remediation Suggestions
- Do not emit untrusted content when the verdict is
BLOCKED. - Return exit code
2for blocked content in wrap mode, consistent with normal scan mode. - Return a nonzero status for
SUSPICIOUScontent unless the caller explicitly acknowledges and overrides the warning. - Keep verdict metadata separate from payload output. Prefer structured JSON containing the verdict without embedding blocked content.
- If manual override is required, add an explicit option such as
--allow-blocked-after-confirmation; do not enable it by default. - Ensure callers validate the verdict and exit status before consuming standard output.
- Add regression tests confirming that:
CLEAN --wrapemits wrapped content and exits0.SUSPICIOUS --wrapfollows the documented confirmation policy and exits nonzero by default.BLOCKED --wrapemits no attacker-controlled payload and exits2.
- Treat sandwich wrapping only as defense in depth, not as a substitute for enforcement.
A secure blocked branch could follow this pattern:
python if args.wrap: if result == "BLOCKED": label = hits[0]["label"] if hits else "high-risk pattern" print(f"BLOCKED: {label}", file=sys.stderr) sys.exit(2) if result == "SUSPICIOUS": label = hits[0]["label"] if hits else "suspicious pattern" print(f"SUSPICIOUS: {label}", file=sys.stderr) sys.exit(1) print(SANDWICH_TEMPLATE.format(content=content)) sys.exit(0)- Do not emit untrusted content when the verdict is
