Egress capture: PostToolUse hook for what actually left the machine - #4
Merged
Conversation
Real step toward "what actually left the machine" without overclaiming wire-level capture. A PostToolUse hook fires after the host has observed both tool_input and tool_response for a completed call. Attested at time of execution by the host, not reconstructed later from a transcript, which is a genuine step up over the JSONL-parsing baseline. Explicitly not a network proxy: no TLS interception, no independent check that a tool's own response was honest. - bin/hook-post-tool-use.js: reuses src/policy's isPushForce / isPushProtected / isPublish / isEgressOther unchanged (no duplicated matcher logic). On a hit, appends an "egress-event" receipt carrying only digests: paramsDigest, responseDigest, and a best-effort responseOk signal. Raw tool_input and raw tool_response are never written to the chain, matching the digest-only convention every other receipt in this repo follows. No exit-2 path: PostToolUse cannot block a call that already completed, so the hook always exits 0. - views: EGRESS EVENTS block in the morning-after summary, same style as the existing POLICY WARNINGS block. - KNOWN-LIMITS 2 rewritten to describe the new reality precisely: what is captured now, and what still requires a real network-boundary proxy that is not in v1. Tests: 177 passing (172 baseline + 5). Independently re-verified beyond the work order's own demo: a fabricated exfil URL and a distinctive secret string in tool_input/tool_response both produced zero hits when grepped against the persisted chain entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follows #3 (gated runs). Branched from
feat/gated-runssince it reuses that branch's policy matchers; this diff will include #3's changes until #3 merges, which is expected for a stacked branch.What this closes, and what it doesn't
KNOWN-LIMITSitem 2 says outbound capture "is not fully derivable from Claude Code JSONL session transcripts alone" and names MCP-boundary instrumentation as the fix. This is a real step in that direction, not the whole thing, and the docs say so precisely rather than overselling it.A
PostToolUsehook fires after a tool call completes, with both thetool_inputand thetool_responsethe host actually observed. That's captured live, at the moment of execution, by the host — not reconstructed later by parsing a transcript file. Genuine improvement in attestation strength. But it is not wire-level capture: no network proxy, no TLS interception, no independent check that a tool's own response was honest. A tool that lies about its own outcome, or an egress path the matchers don't recognize, isn't caught. True wire-level capture is a larger architectural change, not this PR.What it does
bin/hook-post-tool-use.jsreusesisPushForce,isPushProtected,isPublish,isEgressOtherfromsrc/policy/unchanged — no duplicated matcher logic. On a match, appends anegress-eventreceipt carrying only digests:paramsDigest,responseDigest, and a best-effortresponseOksignal extracted from common success/error shapes. Rawtool_inputand rawtool_responseare never written to the chain, matching the digest-only convention every other receipt in this repo follows.No exit-2 path.
PostToolUsefires after the call already completed, so there is nothing left to block; the hook always exits 0.Verification
git push --forcepayload.tool_input/tool_response, then grepped the persisted chain entry directly. Zero hits on both. That's the check that actually matters for a receipt layer's core promise.Views and docs
renderMorningAftergains anEGRESS EVENTSblock (total + per-rule breakdown), same style as the existingPOLICY WARNINGSblock from gated runs.KNOWN-LIMITSitem 2 rewritten to describe the new reality precisely: what's captured now, and what still needs a real network-boundary proxy that isn't in v1.