Skip to content

feat(sable-h6r): consolidate prompt-injection detector — Node + Python parity, hidden behind --experimental flag#118

Merged
Rome-1 merged 1 commit into
maylistfrom
sable-h6r-prompt-injection
May 18, 2026
Merged

feat(sable-h6r): consolidate prompt-injection detector — Node + Python parity, hidden behind --experimental flag#118
Rome-1 merged 1 commit into
maylistfrom
sable-h6r-prompt-injection

Conversation

@Rome-1

@Rome-1 Rome-1 commented May 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Consolidates the in-flight prompt-injection detector (drafts #60 and #82 — same head commit `6d9b91b`, so #82 is baselined) onto `maylist`. Hidden behind `--experimental` flags; default behavior unchanged for non-opted users.

Detector

Pattern-based, English-only, deliberately zero-dependency. Five categories:

  • role_override — "ignore previous instructions", "disregard above", "forget everything", DAN-style mimicry, fake `` tags
  • tool_exfil — "send my API keys to ...", "delete all files", remote-pipe-shell patterns
  • hidden_unicode — tag chars U+E0000–U+E007F, bidi-override, zero-width inside word characters
  • html_comment — imperatives smuggled in HTML/markdown comment blocks
  • encoded_payload — base64 chunks ≥ 40 chars decoded and re-scanned (covers RAG/indirect injection)

Defenses against pathological input: 1 MB cap on scan input (truncated before regex), base64 re-scan gated on chunk length, no catastrophic-backtracking regex shapes.

CLI surface (all opt-in)

  • `rafter scan injection ` — hidden subcommand, spot-check files
  • `rafter hook posttool --experimental-prompt-injection [--prompt-injection-min-severity LEVEL]` — flags markers in tool responses to stderr; does NOT modify the response
  • Default behavior: warn-only. Block requires `.rafter.yml` opt-in (per maylist A4 acceptance).

JSON output shape (documented in `shared-docs/CLI_SPEC.md` "Experimental Commands")

```json
{
"findings": [{ "pattern", "category", "severity", "offset", "evidence", "remediation" }],
"score": 0–100,
"verdict": "clean" | "suspicious" | "likely_injection"
}
```

Files

  • New: `node/src/commands/scan/injection.ts`, `node/src/scanners/prompt-injection.ts`, `node/src/scanners/prompt-injection-patterns.ts`, `node/tests/prompt-injection.test.ts`
  • New: `python/rafter_cli/scanners/prompt_injection.py`, `python/rafter_cli/scanners/prompt_injection_patterns.py`, `python/tests/test_prompt_injection.py`
  • New: `docs/research/prompt-injection-detector.md` (threat model + known limitations + bypass corpus)
  • Modified: scan/hook command registrars + `shared-docs/CLI_SPEC.md`

No new deps.

Status note re: existing drafts

This PR supersedes drafts #60 and #82 (both point at the same head commit `6d9b91b`; nothing #60-unique to salvage). Once this merges to maylist, close #60 and #82 with a "superseded by sable-h6r / PR #118" note.

PR #82 noted "DRAFT — DO NOT MERGE pending rf-i17". That gate still applies at the maylist → main promotion boundary. This PR is mergeable to maylist (it's hidden behind flags), but rf-i17 should be granted before this work crosses to main.

Target: `maylist`.

Closes sable-h6r (A4 from maylist).

Test plan

  • `pnpm exec tsc -p tsconfig.json --noEmit` clean (post-rebase onto current maylist HEAD)
  • `python3 -c "import ast; ast.parse(...)"` clean on all five touched/new python files
  • No `securable-*` repo names in diff
  • `vitest` / `pytest` not run locally — host overloaded (build alone exceeds globalSetup timeout). Tests were carried over from [DRAFT — DO NOT MERGE pending rf-i17] feat(experimental): prompt-injection detector (rf-bmo) #82 (positive corpus, clean-baseline guards, known-bypass FN fixtures).

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.7 noreply@anthropic.com

…n parity, hidden behind --experimental flag

Pattern-based PromptInjectionDetector with parity in Node and Python.
Hidden behind experimental flags pending APPROVE bead rf-i17 — do not
merge to main.

Consolidated from drafts PR #60 (feat/prompt-injection-detector) and
PR #82 (polecat/granite-mokqoiob); both PRs point at the same head
commit, so baseline #82 was used and #60 contained no additional content.

Surfaces:
- rafter scan injection <path> (hidden subcommand) for spot checks
- rafter hook posttool --experimental-prompt-injection flags injection
  markers in tool responses to stderr (does NOT modify the response)

Detector categories: role-override (jailbreak phrases, "ignore previous
instructions", DAN, role-confusion), tool/exfil (remote-pipe-shell,
"send api keys to ...", "delete all files"), hidden Unicode (tag chars
U+E0000–U+E007F, bidi override, zero-width-in-word), HTML/markdown
comment imperatives, and base64-decoded payload re-scan
(indirect/RAG-fetch injection).

Output: structured finding with category (pattern name), severity
(low|medium|high|critical → confidence), evidence and remediation hint;
aggregate score 0-100 and verdict ∈ {clean,suspicious,likely_injection}.

Default behavior: warn only (stderr). Block behavior requires explicit
opt-in via .rafter.yml; no default surface changes for non-opted users.

Documented threat model, prior-art survey, FP/FN tradeoffs, and known
bypasses in docs/research/prompt-injection-detector.md. Tests include
positive corpus, clean-baseline guards, and explicit known-bypass
fixtures so future improvements have a target.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Rome-1

Rome-1 commented May 18, 2026

Copy link
Copy Markdown
Collaborator Author

Security review notes (rafter-code-review, CLI category)

Walked CWE-Top-25 cross-language + JS/TS + Python + investigation-playbook input-shape questions. No merge-blocking findings.

Checked:

  • CWE-400 ReDoS — grepped `prompt-injection-patterns.{ts,py}` for nested-quantifier shapes (`(.*)+`, `(.+)+`, `(a+)+`); none present. Patterns are linear or bounded.
  • CWE-400 memory exhaustion — input capped at 1 MB before regex (`prompt-injection.ts:58`/`prompt_injection.py:58`); base64 re-scan gated on chunk length ≥ 40 chars; truncation happens BEFORE pattern iteration so a 1 GB input doesn't OOM.
  • CWE-78/94 injection — no shell, no eval, no Function() construction. Detector is read-only string analysis.
  • CWE-502 deserialization — only base64-decode of in-input chunks; result is treated as bytes/string and re-scanned with the same regex engine, not deserialized.
  • Input shape — UTF-8 / surrogate / null-byte / very-large fixtures covered by the carried-over test corpus.

Low-severity observation (non-blocking):

  • The `evidence` field of each finding includes a `snippet()` of the input around the match (default 40 chars). If a user runs `rafter scan injection` on a file that contains BOTH an injection pattern AND nearby secrets, those secrets could appear in the evidence snippet, then get logged / persisted to audit.jsonl / piped into another tool. Worth a `finding-triage.md` follow-up that redacts on overlap with rafter's existing secret patterns before persisting. Filing as a follow-up bead.

Approved for merge to maylist. The PR #82 "DO NOT MERGE pending rf-i17" gate still applies at the maylist→main promotion boundary — that's the right place to enforce it.

@Rome-1
Rome-1 merged commit 1c36df9 into maylist May 18, 2026
@Rome-1
Rome-1 deleted the sable-h6r-prompt-injection branch May 18, 2026 23:11
Rome-1 added a commit that referenced this pull request May 19, 2026
Audit pass deferred from sable-bum / PR #122. Adds concrete mechanical
checks for the realistic install-time risks of third-party SKILL.md, MCP
manifest, Cursor rule, or agent config:

- prompt-injection in the skill body
- hidden tool invocations in install steps (curl|sh, npm postinstall)
- exfiltration patterns (curl to attacker host, base64 phone-home)
- overbroad allowed-tools grants
- typo-squat / stale package names
- skills that sabotage / disable peer skills
- sensitive-path reads (~/.ssh, ~/.aws/credentials, keychain)
- MCP manifests granting servers/tools silently

SKILL.md changes:
- new "Watch-items at a glance" table pairing each risk with a grep/jq/CLI
  check and the sub-doc that covers it
- step 0 now points at `rafter scan injection` as a complementary
  mechanical pass alongside `rafter skill review`
- canonical-command note clarifies `rafter agent audit-skill` is the
  deprecated alias (matches sable-bum edits to sibling rafter SKILL.md)
- fast path is now a runnable three-step script
- decision rule is now four items (adds the injection-scan gate)

Sub-doc edits (additive, no restructure):
- prompt-injection.md: new §0 mechanical-first-pass with
  `rafter scan injection` (cross-link to PR #118 / sable-h6r detector)
- data-practices.md §5: explicit allowed-tools reconciliation check;
  calls out the "markdown formatter asking for Bash" red flag
- authorship-provenance.md §4: concrete `npm view ... time.created`
  typo-squat command plus three in-the-wild examples
- malware-indicators.md §6–7: consolidated grep commands for privileged
  writes and intelligence-gathering primitives

Files: 5 Node + 5 Python mirror. `diff -rq` empty.

Punted (future bead candidates):
- supply-chain-risks.md sub-doc consolidating §4 (typo-squat) +
  changelog-review §6 (dependency drift) + malware §3 (postinstall) —
  currently scattered across three docs
- agent-config-specific.md sub-doc for MCP manifest / Cursor rule /
  Continue rule schemas (currently MCP is one row in the table only)
- post-install detection: this skill is pre-install only; a sibling
  "post-install audit" skill could cover installed-skill drift

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant