fix(security): safety layer bypass via output truncation [HIGH] - #1851
ilblackdragon merged 3 commits into
Conversation
Previously, `sanitize_tool_output()` returned immediately after truncating oversized output, skipping leak detection, policy enforcement, and injection scanning entirely. This allowed an attacker to embed malicious payloads in the first N bytes of oversized tool output and have them delivered unsanitized to the LLM. Restructure the truncation path so it feeds into the same safety pipeline as non-truncated content: leak detection, policy checks, and Aho-Corasick injection scanning all run on the (possibly truncated) content before it is returned. Adds regression tests to verify truncated output is still scanned. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request refactors the sanitize_tool_output function to ensure that truncated output is still processed by subsequent safety layers, such as leak detection and injection scanning, instead of returning early. It also adds regression tests to confirm that security checks are applied to truncated content. Feedback was provided to correct an invalid range in the truncation warning that could cause out-of-bounds errors and to append low-severity warnings to the end of the list to maintain proper sorting.
| vec![InjectionWarning { | ||
| pattern: "output_too_large".to_string(), | ||
| severity: Severity::Low, | ||
| location: 0..output.len(), |
There was a problem hiding this comment.
The location range 0..output.len() is incorrect because it refers to the original, untruncated string. Since sanitize_tool_output returns a modified string (truncated prefix plus notice), any consumer using this range will encounter an out-of-bounds panic. Additionally, when truncating UTF-8 strings, ensure the split occurs at a valid character boundary using is_char_boundary to prevent panics on multi-byte characters.
| location: 0..output.len(), | |
| location: cut..(cut + notice.len()), |
References
- When truncating a UTF-8 string at a byte boundary, walk backwards from the desired length until a valid character boundary is found using is_char_boundary to prevent panics.
- Always truncate tool output for previews or status updates to a reasonable maximum length to prevent excessive memory/bandwidth usage.
| extra_warnings.append(&mut sanitized.warnings); | ||
| sanitized.warnings = extra_warnings; |
There was a problem hiding this comment.
Merging extra_warnings at the beginning of the list breaks the severity-based sort order (Critical/High first) established by the Sanitizer. Since the truncation warning is Severity::Low, it should be appended to the end of the sanitized.warnings list. This ensures that more urgent security warnings remain at the top of the list for the caller. Additionally, using extend is more idiomatic than the current append and reassignment pattern.
| extra_warnings.append(&mut sanitized.warnings); | |
| sanitized.warnings = extra_warnings; | |
| sanitized.warnings.extend(extra_warnings); |
zmanian
left a comment
There was a problem hiding this comment.
Review -- APPROVE
Legitimate HIGH severity security fix. The vulnerability is real: sanitize_tool_output() has an early return after truncation that skips leak detection, policy enforcement, and injection scanning. An attacker only needs to pad output past max_output_length while placing malicious content in the first N bytes.
The fix is correct -- truncation now produces a tuple that falls through to the full safety pipeline. Truncated content is scanned (which is right, since it's what the LLM sees). Warning merging preserves both truncation and downstream warnings.
No bypass vectors in the fix. Low regression risk -- non-truncated output path is unchanged.
Suggestions (non-blocking)
- The policy
Blockpath discards all warnings including truncation warning -- consider preserving for logging (pre-existing issue) - Consider adding a test where truncated output contains a leaked secret pattern (currently only injection scanning is tested)
zmanian
left a comment
There was a problem hiding this comment.
Re-review -- APPROVE (post-approval commit check)
The new commit (4e2ff0b) is a merge of staging into the feature branch. It brings in unrelated staging changes (channels, config, e2e tests, github tool, etc.) but does not touch crates/ironclaw_safety/ at all -- zero files in the safety crate were modified by the merge.
The security fix from the original commit (a3fa6df) is intact and unchanged:
- Truncation no longer early-returns; content flows through the full safety pipeline
- Warning merging preserves truncation warnings alongside downstream findings
- Regression tests cover injection scanning on truncated output
Gemini's comments (not addressed, both non-blocking)
-
location: 0..output.len()range -- refers to original string length, not truncated content. Pre-existing pattern, not introduced by this PR. Low risk sincelocationis metadata on the warning struct, not used for content slicing. -
Warning ordering -- truncation warnings (Low severity) end up before injection warnings (higher severity) due to the
append+ reassign pattern. Cosmetic; callers should sort/filter by severity if ordering matters.
Neither weakens the security fix. Safe to merge.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code reviewFound 2 issues:
The code appends
When Security & Safety: ✅ No issues — fix correctly prevents truncation from bypassing all downstream checks (leak detection, policy, injection scanning). Bug Scan: ✅ No obvious logic errors — refactoring preserves safety check execution order. Performance: Append order at lines 127-128 could be optimized (see issue #1 above). |
* fix(security): run safety checks on truncated tool output Previously, `sanitize_tool_output()` returned immediately after truncating oversized output, skipping leak detection, policy enforcement, and injection scanning entirely. This allowed an attacker to embed malicious payloads in the first N bytes of oversized tool output and have them delivered unsanitized to the LLM. Restructure the truncation path so it feeds into the same safety pipeline as non-truncated content: leak detection, policy checks, and Aho-Corasick injection scanning all run on the (possibly truncated) content before it is returned. Adds regression tests to verify truncated output is still scanned. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: apply rustfmt to fix CI formatting check Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Wui <wui@Wui-Work-2.local> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Illia Polosukhin <ilblackdragon@gmail.com>
…ai#1851) * fix(security): run safety checks on truncated tool output Previously, `sanitize_tool_output()` returned immediately after truncating oversized output, skipping leak detection, policy enforcement, and injection scanning entirely. This allowed an attacker to embed malicious payloads in the first N bytes of oversized tool output and have them delivered unsanitized to the LLM. Restructure the truncation path so it feeds into the same safety pipeline as non-truncated content: leak detection, policy checks, and Aho-Corasick injection scanning all run on the (possibly truncated) content before it is returned. Adds regression tests to verify truncated output is still scanned. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: apply rustfmt to fix CI formatting check Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Wui <wui@Wui-Work-2.local> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Illia Polosukhin <ilblackdragon@gmail.com>
…ai#1851) * fix(security): run safety checks on truncated tool output Previously, `sanitize_tool_output()` returned immediately after truncating oversized output, skipping leak detection, policy enforcement, and injection scanning entirely. This allowed an attacker to embed malicious payloads in the first N bytes of oversized tool output and have them delivered unsanitized to the LLM. Restructure the truncation path so it feeds into the same safety pipeline as non-truncated content: leak detection, policy checks, and Aho-Corasick injection scanning all run on the (possibly truncated) content before it is returned. Adds regression tests to verify truncated output is still scanned. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: apply rustfmt to fix CI formatting check Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Wui <wui@Wui-Work-2.local> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Illia Polosukhin <ilblackdragon@gmail.com> (cherry picked from commit f303638)
…ai#1851) * fix(security): run safety checks on truncated tool output Previously, `sanitize_tool_output()` returned immediately after truncating oversized output, skipping leak detection, policy enforcement, and injection scanning entirely. This allowed an attacker to embed malicious payloads in the first N bytes of oversized tool output and have them delivered unsanitized to the LLM. Restructure the truncation path so it feeds into the same safety pipeline as non-truncated content: leak detection, policy checks, and Aho-Corasick injection scanning all run on the (possibly truncated) content before it is returned. Adds regression tests to verify truncated output is still scanned. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: apply rustfmt to fix CI formatting check Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Wui <wui@Wui-Work-2.local> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Illia Polosukhin <ilblackdragon@gmail.com>
Summary
sanitize_tool_output()returned immediately after truncating oversized tool output, skipping all three safety checks (leak detection, policy enforcement, injection scanning)Security Finding: Safety Layer Bypass via Output Truncation
Severity: HIGH
Reported by: FailSafe Security Researcher
Component:
crates/ironclaw_safety/src/lib.rs—sanitize_tool_output()Description
The
sanitize_tool_output()method truncated oversized tool output and returned it immediately — before leak detection, policy enforcement, or injection scanning ever ran. This meant any tool output exceedingmax_output_lengthwas delivered to the LLM with zero safety checks applied.Order-of-operations problem (before fix):
output_too_largewarningAttack Vector
A malicious tool (compromised MCP server, poisoned web page via HTTP tool, sandbox job processing attacker-controlled input) could craft output where:
max_output_length, triggering the early-return pathThe attacker does not need to know the exact truncation threshold — they only need to ensure the total output is large enough while placing the payload near the beginning.
Impact
max_output_lengthbypassed all three safety layersFix
Removed the early
returnafter truncation. Truncation now produces(content, was_modified, extra_warnings)and falls through to the same leak detection → policy enforcement → injection scanning pipeline as non-truncated content. Truncation warnings are preserved and merged into the final output.Test plan
cargo test -p ironclaw_safety— existing adversarial truncation tests still passtruncated_output_still_scanned_for_injection— verifies injection patterns are detected in oversized outputtruncated_output_preserves_truncation_warning— verifies truncation warning survives the full pipelinecargo clippy -p ironclaw_safety -- -D warnings— zero warnings🤖 Generated with Claude Code