fix(sandbox): detect C1 control characters in hasUnsafeControlChars#103381
fix(sandbox): detect C1 control characters in hasUnsafeControlChars#103381lsr911 wants to merge 2 commits into
Conversation
hasUnsafeControlChars() checked for C0 control characters but not C1 controls (0x80-0x9f). Add the C1 range. Same pattern as openclaw#103226, openclaw#103274, openclaw#103379, openclaw#103380. Co-Authored-By: Claude <[email protected]>
|
Codex review: needs real behavior proof before merge. Reviewed July 11, 2026, 4:15 AM ET / 08:15 UTC. Summary PR surface: Source +3, Tests +35. Total +38 across 3 files. Reproducibility: yes. at source level. Current main’s C0/DEL-only predicates allow U+009B through the retained Session-label segment and select the raw-session explain command, though no live current-main invocation was run during this read-only review. Review metrics: none identified. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Next step before merge
Security Review detailsBest possible solution: Keep the canonical shared C0/DEL/C1 escaping contract and unsafe-key fallback, then merge after redacted exact-head runtime output demonstrates both corrected sandbox denial outputs. Do we have a high-confidence way to reproduce the issue? Yes, at source level. Current main’s C0/DEL-only predicates allow U+009B through the retained Session-label segment and select the raw-session explain command, though no live current-main invocation was run during this read-only review. Is this the best way to solve the issue? Yes. Extending the existing shared visible-escaping helper and established unsafe-session fallback is narrower and more maintainable than introducing a parallel sanitizer. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 7bf80dc2c64e. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +3, Tests +35. Total +38 across 3 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (1 earlier review cycle)
|
…h paths The prior change extended hasUnsafeControlChars (which gates the explain command to an agent-only fallback) through the C1 range. But the parallel visible Session-label path went through escapeControlCharsVisible, which only escaped C0/DEL, so a C1 byte (e.g. U+009B CSI) in a redacted session key still reached the single-line Session output raw. Extend the shared escapeControlCharsVisible contract through C1 (0x80-0x9f) so the visible label escapes those bytes too, and add an end-to-end regression in sandbox-explain.test.ts asserting a C1-bearing session key both escapes in the Session line and triggers the agent-only explain fallback. Signed-off-by: lsr911 <[email protected]>
|
Superseded by #104362 — a single sink-wide C1 (0x80–0x9f) pass that folds this sink's fix in with a valid U+009B regression, alongside an audit of every other control-character sink (already-covered / deferred / out-of-scope). This consolidates the isolated C1 PRs per the direction on #103226. Closing in favor of the consolidated review. |
…g sinks Terminal-control sanitizers guard untrusted text before it reaches a terminal or log (CWE-117). The canonical range is C0 (0x00-0x1f), DEL (0x7f), and C1 (0x80-0x9f) - the range already used by the shared sanitizeForLog and by renderTerminalBufferText (openclaw#103274). C1 includes the 8-bit CSI introducer U+009B. Deliberate sink-wide pass: closes the C1 gap in twelve terminal/log output sinks, each keeping its own strip/escape/replace/truncate/reject action and gaining a focused valid-U+009B regression test. The PR body audits every other control-char sink (already-covered, deferred, or out-of-scope). Consolidates the isolated PRs openclaw#103379 openclaw#103380 openclaw#103381 openclaw#103382 openclaw#103383 openclaw#103402 Signed-off-by: lsr911 <[email protected]> openclaw#103405 and re-lands the sanitizeForConsole fix from openclaw#103226 cleanly.
What Problem This Solves
When sandbox tool policy blocks a tool,
formatSandboxToolPolicyBlockedMessage()insrc/agents/sandbox/runtime-status.tsrenders a single-line denial message that includes a redactedSession:label and aopenclaw sandbox explain ...command. Two code paths handle an untrusted session key, and both must be hardened against the C1 control range (0x80-0x9f), which includes the CSI introducer U+009B (an alternative ANSI escape prefix equivalent toESC [):hasUnsafeControlChars()decides whether the command uses the raw key (--session '<key>') or the agent-only fallback (--agent <id>). It only covered C0/DEL, so a C1-bearing key was still passed to--session.redactSessionKey()→sanitizeForSingleLineDisplay()→ the sharedescapeControlCharsVisible()(insrc/agents/tool-policy-audit.ts). It only escaped C0/DEL, so a C1 byte in the redacted first/last-6 of the key reached the visibleSession:line raw.The first review pass only fixed path (1). This revision fixes both, matching ClawSweeper's recommended solution ("extend the shared visible-control escaping contract through C1, retain the agent-only fallback for unsafe keys, and test both outputs").
Change
hasUnsafeControlCharsnow treats 0x80-0x9f as unsafe → agent-only explain fallback for C1-bearing keys.escapeControlCharsVisible(shared) now escapes 0x80-0x9f as\xNN→ the visible Session label no longer emits raw C1 bytes.Testing / Proof
Added an end-to-end regression in
src/agents/sandbox-explain.test.tsthat calls the realformatSandboxToolPolicyBlockedMessagewith a session key whose trailing (visible) 6 chars contain U+009B, asserting both outputs:Session:line contains the escaped\x9band not the raw C1 byte;openclaw sandbox explain --agent ...and never emits--sessionwith the raw key.The test drives the real formatting path, so the passing run is the before/after proof: on current
mainthe same input leaves the raw U+009B in the Session line and routes to--session; with this change it is escaped and routed to--agent.