Skip to content

[Feature] Content-based prompt injection scanning on tool output #79168

Description

@smonett

Problem

Tool output (exec results, web fetches, file reads) is not scanned for prompt injection patterns before being fed to the model. The current mitigation is XML wrapping of tool output, which provides structural separation but no content-level defense against indirect injection.

Originally filed as part of #65824 (item 4) and #65816. Re-audited against 2026.5.7 (eeef486).

What 2026.5.7 gets right

Two meaningful changes since the original filing:

  1. before_tool_call / after_tool_call hook surface — makes it possible to build content scanning as a user-space plugin. The before_tool_call hook (hook-runner-global-CCAcWVdN.js:569-574) is a modifying hook with failurePolicyByHook: "fail-closed" (line 842), meaning a plugin can block tool execution. The after_tool_call hook (line 593-597) is a void hook (fire-and-forget) — useful for logging but cannot modify or block tool results.
  2. Shell variable injection detectionbash-tools-CtaEWCRn.js adds targeted detection of shell variable injection in exec preflight. Narrow but useful.

Neither constitutes built-in content scanning of tool output for prompt injection patterns, but the hook surface significantly lowers the bar for building it externally.

Source evidence (5.7 dist/ file names)

  • before_tool_call hook: hook-runner-global-CCAcWVdN.js:569-574runModifyingHook("before_tool_call", event, ctx, { ... }) — modifying hook, can block tool execution. Fail-closed policy at line 842.
  • after_tool_call hook: hook-runner-global-CCAcWVdN.js:593-597runVoidHook("after_tool_call", event, ctx) — void hook, fire-and-forget. Cannot modify or block tool results from reaching the model.
  • after_tool_call dispatch: selection-BeP8qtCb.js:1402-1424 — tool execution metadata (name, duration, result size) is passed to the hook with error handling (ctx.log.warn(...) on hook failure). The hook receives the tool result but cannot intercept it.
  • Tool execution tracking: selection-BeP8qtCb.js:654/** Track tool execution start data for after_tool_call hook. */ — structured metadata available to scanners.
  • Hook guard: native-hook-relay-Dd9iHtsW.js:19if (!hookRunner?.hasHooks("after_tool_call")) return — early exit when no plugins registered (zero overhead when unused).
  • Hook type registry: types-B6qmUh0I.js:410"after_tool_call" is registered in the typed hook enum.

The architectural gap

after_tool_call is void-only (line 597: runVoidHook). A content scanner on this hook can log and alert but cannot prevent the scanned content from reaching the model. For true blocking, content scanning would need either:

  • A new tool_result_filter modifying hook that intercepts between tool completion and model submission, or
  • Making after_tool_call a modifying hook that can rewrite or redact tool output

The before_tool_call hook can block tool execution preemptively but cannot scan tool results since results don't exist yet at that point.

Acknowledged trade-off

We recognize robust content scanning is genuinely hard to do without false positives — Unicode normalization, encoding tricks, indirect instruction patterns, and the moving target of injection techniques all make this a research-grade problem. This may be a deliberate deferral rather than an oversight.

If built-in scanning is out of scope, an official reference plugin using the new hook surface would be valuable to the community.

Workaround

cc-taint-check.py — regex scanner with Unicode normalization that blocks high-severity injection patterns. Conservative and narrow precisely because of the false-positive difficulty. Uses the after_tool_call hook for alerting (cannot block, only log).

Proposed solution

Built-in or officially-supported content scanning on tool output, applied at the tool-result layer:

  1. Pattern matching — detect common injection patterns (role-switch instructions, system-prompt overrides, encoded payloads)
  2. Unicode normalization — catch homoglyph and encoding-based bypasses
  3. Configurable severity — block high-confidence patterns, warn on medium, ignore low
  4. Opt-intools.contentScanning.enabled: true with tunable sensitivity

If built-in is out of scope, a reference plugin demonstrating scanning via after_tool_call (for alerting) and before_tool_call (for preemptive path-based blocking) that the community can fork and customize would still close much of the gap.

Impact

High, mitigated. THREAT-MODEL-ATLAS.md rates indirect prompt injection (T-EXEC-002) as High residual risk. The new hook surface reduces the barrier to user-space mitigation, but most users won't build their own scanner. The void-only nature of after_tool_call means even users who do build a scanner cannot block suspicious results — only alert on them.

Environment

  • OpenClaw 2026.5.7 (eeef486) · macOS arm64

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:needs-security-reviewClawSweeper marked this issue as needing security-sensitive review.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:securitySecurity boundary, credential, authz, sandbox, or sensitive-data risk.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions