feat(grok): prompt-injection guard for Grok (parity with the Claude hook)

The injection guard is RoboCo's own hook (user-prompt-hook.sh), not a runtime
built-in, so it can be recreated at our input boundary regardless of runtime —
opencode's lack of a blocking pre-prompt hook is irrelevant.

- prompt_guard.detect_injection: the deny patterns ported to reusable Python.
- IntakeDriver._run_turn scans every interactive turn before sending it to the
  model and denies a match as an error chunk. Covers BOTH Grok (opencode) and
  the Claude SDK intake (which runs with setting_sources=[] and so never loaded
  the bash hook — it was unguarded too).
- The one-shot grok entrypoint scans ROBOCO_INITIAL_PROMPT and refuses a
  poisoned task prompt (parity with the Claude UserPromptSubmit deny).
- Broadened the pattern (Python + the bash hook, kept in sync) to catch the
  multi-qualifier canonical phrasing "ignore all previous instructions", which
  the single-qualifier original missed — without false-positiving on
  "ignore the linting rules" (an intermediate non-qualifier word breaks it).

So Grok now has the command/secret-exfil guard (secret-scrub), the cost cap,
AND the injection guard. Verified: 94 agent_sdk tests pass; bash + Python agree
on detect/miss cases.
This commit is contained in:
Renn F
2026-06-18 18:43:14 +02:00
parent 433a85784e
commit e545023e46
6 changed files with 196 additions and 1 deletions
+8
View File
@@ -29,6 +29,14 @@ if [ -n "${ROBOCO_GROK_VARIANT:-}" ]; then
variant_arg=(--variant "$ROBOCO_GROK_VARIANT")
fi
# Prompt-injection guard (parity with the Claude UserPromptSubmit hook): the
# task prompt is DATA, not instructions — refuse a poisoned one before it
# reaches the model. Same patterns as docker/scripts/user-prompt-hook.sh.
if ! python -m roboco.agent_sdk.prompt_guard "${ROBOCO_INITIAL_PROMPT:-}"; then
echo "Refusing to run: task prompt matched a prompt-injection pattern." >&2
exit 1
fi
exec opencode run \
--model "xai/${ROBOCO_AGENT_MODEL:-grok-build-0.1}" \
"${variant_arg[@]}" \