Skip to content

Redact registered secrets from hook and tool failure logs and tool-call events - #103

Merged
udaychandra merged 11 commits into
mainfrom
agent/audit-hook-event-redaction
Oct 22, 2026
Merged

udaychandra merged 11 commits into
mainfrom
agent/audit-hook-event-redaction

Conversation

@udaychandra

@udaychandra udaychandra commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Spec audit-hook-event-redaction (P1, found by the PR #98 security and correctness reviewers).

Logs. SerializedError.redactedForLog(throwable, redactor) builds a copy of an exception for a log record: each level of its cause chain (bounded at 16) becomes <class>: <message> redacted against the session's SecretRegistry, with that level's original frames; suppressed exceptions are not copied. Six session log lines attach it instead of the raw exception: HookRegistry's observer line, its stream-event line and the PreToolUse guard (which now carries the trace instead of the class alone), DispatchedCall's SEVERE for a tool that threw after its call was answered, SessionTeardown's onSessionEnd WARNING, and ContextCompaction's compactor WARNING. Log message texts are unchanged.

HookRegistry(hooks, secrets). SessionWiring passes the session's registry; empty() uses an empty one.

Events. ToolUse, ToolMutated (call, inputBefore, inputAfter), ToolBlocked and ToolResult carry the arguments with every registered secret redacted, in keys and in string values at any depth (loop.RedactedArguments). A key or string with no secret is kept as it is; a key or string that redaction lengthens past its JsonSnapshot limit is cut to it, ending in …, so a valid call always has a valid event copy. The call that is authorized, dispatched, passed to PreToolUse/PostToolUse hooks and appended to the history keeps its arguments.

Docs. CHANGELOG Security and Breaking rows, README secret-redaction section (its "Not covered" list no longer names ToolMutated arguments and ToolBlocked reasons), CLAUDE.md Hooks row.

Main code: +124 net lines (spec budget ~100). The rest is the JsonSnapshot cut that Sail's round-1 MEDIUM asked for and the sixth log line found in pre-merge review.

Pre-merge review (correctness, adversarial, coverage, design): attack sessions recovered no secret or 4+ char fragment through the logs or events; 45 hand mutants, all observable ones killed; ~105k repetitions of the new tests, 0 failures.

mvn -B clean verify javadoc:jar: 7875 tests run, 0 failures, 62 skipped (tests that need live provider keys); PMD, CPD, ArchUnit and coverage gates pass.

🤖 Generated with Claude Code

udaychandra and others added 11 commits October 21, 2026 23:13
…paired surrogates

Redaction can lengthen a key or string past its JsonSnapshot limit; the event
copy is now cut to the limit with a trailing ellipsis instead of failing the
call. Redacting through UTF-8 turned unpaired surrogates into '?', which could
collide distinct keys; the runs between them are redacted and the surrogates
kept. Registered secrets are ASCII, so none spans one.
…und surrogates

The event copy now reuses ToolRound's rule for tool results: text with
nothing redacted is returned unchanged, so an unpaired surrogate in it
never becomes ? and distinct keys stay distinct. The run-splitting
helper goes; v3-simplify-redactor removes the UTF-8 round trip for
every redaction site.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Redacting the call ToolRound passes to the PostToolUse hooks survived every
test: the event test checked the tool and the history but no hook. The event
test now records what a PostToolUse hook receives.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
ContextCompaction attached a ContextCompactor's exception, message and
all, to its WARNING. A compactor works on the whole history, which holds
user messages and hook-injected arguments unredacted, so a compactor that
names what it failed on carried a registered secret into the record, as a
hook did. The record now attaches SerializedError.redactedForLog.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The README's secret-redaction section still listed ToolMutated arguments
and ToolBlocked reasons as not covered, and the CHANGELOG and CLAUDE.md
said "the hooks" see the arguments unredacted, though an OnStreamEvent
hook sees the redacted event.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
StopClassifier and HookRegistry hold the session's secret registry, so the
javadoc no longer calls them reusable across sessions.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@udaychandra
udaychandra merged commit 9364fa2 into main Oct 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant