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
39 lines
2.0 KiB
Python
39 lines
2.0 KiB
Python
#!/usr/bin/env python3
|
|
"""SPIKE probe — PreToolUse(Bash) refuse. stdin: hook JSON. Denies the known leak shapes with a reason
|
|
that names the safe form. Logs one line per decision to evidence/q2/pre-hook.log (rule name only).
|
|
Unknown input shape -> exit 1 (a crash), so `onFailure: block` decides, never a silent allow."""
|
|
import json, os, re, sys, time
|
|
|
|
RULES = [
|
|
("source-secret-file",
|
|
re.compile(r"(^|[;&|(]\s*|\s)(source|\.)\s+\S*(credentials|secret|canary|\.env)\b", re.I),
|
|
"Sourcing a secrets file prints values (bash echoes un-assignable lines). Use: sec run --env NAME -- <cmd>"),
|
|
("dump-environment",
|
|
re.compile(r"(^|[;&|(]\s*)\s*(printenv|env|export\s+-p|declare\s+-[px]+|set)\s*($|[;&|>)])"),
|
|
"Dumping the environment prints every secret in it. Print one NAME only: printenv NAME | wc -c"),
|
|
("curl-url-format-var",
|
|
re.compile(r"\bcurl\b.*%\{(redirect_url|url_effective|referer|url)\}"),
|
|
"curl -w '%{redirect_url}' re-prints -u/--netrc credentials inside the URL. Use -w '%{http_code}' and read Location from -D-"),
|
|
("kubectl-read-secret",
|
|
re.compile(r"\bkubectl\b.*\bget\s+(secrets?|sec)\b(?!.*--show-managed-fields-only)"),
|
|
"kubectl get secret prints base64 values. Use: kubectl get secret NAME -o jsonpath='{.data}' | jq 'keys' (names only)"),
|
|
]
|
|
|
|
def log(rule, decision):
|
|
d = os.path.join(os.path.dirname(os.path.abspath(__file__)), "evidence", "q2")
|
|
os.makedirs(d, exist_ok=True)
|
|
with open(os.path.join(d, "pre-hook.log"), "a") as fh:
|
|
fh.write("%s %s %s\n" % (time.strftime("%H:%M:%S"), decision, rule))
|
|
|
|
ev = json.load(sys.stdin)
|
|
cmd = ev["tool_input"]["command"] # KeyError = crash = onFailure decides
|
|
for name, rx, reason in RULES:
|
|
if rx.search(cmd):
|
|
log(name, "deny")
|
|
print(json.dumps({"hookSpecificOutput": {"hookEventName": "PreToolUse",
|
|
"permissionDecision": "deny",
|
|
"permissionDecisionReason": "SPIKE-GUARD[%s]: %s" % (name, reason)}}))
|
|
sys.exit(0)
|
|
log("-", "pass")
|
|
sys.exit(0)
|