gates / gates (push) Failing after 6m36s
Mask PASS, PreToolUse refuse PASS, PostToolUse replace PARTIAL (crash and large-output disk leaks, both with a measured fix), SOPS+age store PARTIAL (private repo could not be created), transcript sweep PASS. Fake secrets only; ~/.config/credentials not read; no CC settings changed. Files use "creds": .gitignore *secret* ignores the brief's names. STATUS D10: the store choice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
29 lines
1.4 KiB
Python
29 lines
1.4 KiB
Python
#!/usr/bin/env python3
|
|
"""SPIKE probe — PostToolUse(Bash) replace. Masks every form of each secret (mask.forms) in the Bash
|
|
result and returns it through hookSpecificOutput.updatedToolOutput, keeping the tool's output shape.
|
|
Logs to evidence/q3/post-hook.log: the tool_response KEY NAMES and whether anything was replaced."""
|
|
import json, os, re, sys, time
|
|
HERE = os.path.dirname(os.path.abspath(__file__)); sys.path.insert(0, HERE)
|
|
import mask
|
|
SECRETS = [("CANARY", open(os.path.join(HERE, "canary-Q3.txt")).read().rstrip("\n"))]
|
|
table = mask.build_table(SECRETS)
|
|
ev = json.load(sys.stdin)
|
|
resp = ev["tool_response"]
|
|
def scrub(s):
|
|
b = s.encode()
|
|
for rx, tok, _ in table:
|
|
b = rx.sub(tok, b)
|
|
return b.decode()
|
|
new = dict(resp) if isinstance(resp, dict) else resp
|
|
changed = False
|
|
if isinstance(resp, dict):
|
|
for k in ("stdout", "stderr"):
|
|
if isinstance(resp.get(k), str):
|
|
v = scrub(resp[k]); changed |= v != resp[k]; new[k] = v
|
|
os.makedirs(os.path.join(HERE, "evidence", "q3"), exist_ok=True)
|
|
with open(os.path.join(HERE, "evidence", "q3", "post-hook.log"), "a") as fh:
|
|
fh.write("%s keys=%s type=%s changed=%s\n" % (time.strftime("%H:%M:%S"),
|
|
sorted(resp) if isinstance(resp, dict) else "-", type(resp).__name__, changed))
|
|
if changed:
|
|
print(json.dumps({"hookSpecificOutput": {"hookEventName": "PostToolUse", "updatedToolOutput": new}}))
|