Repository navigation
feat(guard): screen Claude Agent SDK tool results before the model reads them - #271
arcjet-rei wants to merge 11 commits into
Conversation
…result_rules=)
After the handler returns, a second Guard call screens its result before
the model reads it. A DENY, or an unevaluated policy under the default
on_guard_error="deny", returns the existing denial envelope instead of the
result. The check is labelled result_action, which defaults to
{action}.result.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Arcjet Review — 🔴 High Risk
Decision: Needs Review
Rationale: This PR changes security-sensitive guard behavior by adding a second policy evaluation path over tool results, including fail-open/fail-closed handling, policy labels, metadata, and prompt-injection screening inputs. The implementation is well covered by unit and integration tests and I did not identify hardcoded secrets or an obvious injection vulnerability in the code paths shown. However, because the change directly affects security enforcement semantics and includes adversarial prompt-injection text in PR content, it should receive human security review before merging.
Summary of Changes
Adds result_rules and result_action to guard_tool so authored Claude Agent SDK tool results can be screened after the handler runs and before the model receives the result. The PR adds result text extraction, a second Guard evaluation path, fail-closed handling by default, README documentation, and tests for allow, deny, unavailable, invalid configuration, and label override behavior.
Security Flags
- [LOW] Prompt injection in PR content (tests/integration/guard/test_claude_agent_sdk.py:107): The diff contains the phrase "ignore previous instructions" as a test fixture. It appears to be intentional prompt-injection test data, but it was ignored as an instruction and should be verified by a human reviewer under the adversarial PR-content policy.
Review Focus Areas
- Confirm that joining all text blocks without truncation is acceptable for very large tool results, or whether there should be a maximum size with explicit fail-closed behavior.
The design avoids prefix-only prompt-injection bypasses, but unbounded result text can create memory, latency, or provider-limit risks when screening attacker-controlled fetched content. - Verify the degradation path for result rule resolver failures is intended: Guard is still called without local result rules, and
on_guard_errordetermines whether the result is denied or allowed.
This is a security enforcement boundary; a resolver failure must not silently bypass result screening under the default deny mode. - Check that returning the existing denial envelope in place of the handler result is compatible with all authored tool consumers.
This changes the post-handler response path for tools usingresult_rules; consumers expecting a specific result shape need to handle theis_errordenial envelope. - Consider whether an additional test should cover invalid/non-sequence return values from
result_rules, not only a resolver that raises.
The docstring says non-sequence or invalid bound-rule results are handled like resolver failures; an explicit test would help prevent regressions in that fail-closed behavior.
Review: 38eb094c | Model: openai/gpt-5.5 | Powered by Arcjet Review
…s closed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Replies to the Arcjet Review focus areas:
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aced with Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…one from the model Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n PostToolUse Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t a scripted Messages API Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds two ways to screen what a Claude Agent SDK tool returned, before the model reads it:
guard_tool(result_rules=, result_action=)for authored tools: a second Guard call over the handler's return value, labelled{action}.resultby default. A DENY returns the existing denial envelope in place of the result.guard_hooks(tool_results={...})for every tool, onPostToolUse. A DENY replaces the result throughupdatedToolOutputfor MCP tools and for the built-ins inREPLACEABLE_BUILTIN_TOOLS(Bash,Read). For any other tool the CLI would reject the replacement and send the original, so the run stops withcontinue: falseinstead, and the capture records which happened.Claude Code validates a built-in tool's replacement against an unpublished schema and silently sends the original result on a mismatch. A new integration test drives the CLI bundled with
claude-agent-sdkagainst a scripted, local Messages API and asserts what the model receives, and a new CI job runs it with the extra installed.🤖 Generated with Claude Code