Hook chain repairs (Group A):
- alignment-check.sh: read .prompt (was .user_prompt) — hook was dead
- block-dangerous.sh: jq instead of inline interpreter (RULE 0.2 + fail-open fix)
- destructive-guard.sh: explicit INPUT=cat + jq guard + exit 0 — was silent no-op
- numeric-claims-guard.sh: exit 1 -> exit 2 (Claude Code spec — was non-blocking)
comments updated 0.17 -> 0.18 (env var name kept)
- no-downgrade.sh: removed (?i) PCRE syntax — POSIX ERE matched literal text
- task-timer.sh: jq -nc instead of bare printf — JSON injection on quotes/backslashes
in description was corrupting RULE 0.18 evidence journal
- check-error-patterns.sh: replaced with no-op stub — had hardcoded /Users/denis/...
PATH LEAK in public kit, plus inline interpreter use
- post-commit-audit.sh: added trailing exit 0 — grep return code was hook exit code
- citation-verify.sh: ALLOW_REGEX accepts HOOK-BYPASS marker — bypass was documented
but never matched
- settings-snippet.json: agent-stub-scan moved PreToolUse:Agent -> PostToolUse:Agent
(RULE 0.16 enforcement was firing before transcript existed)
- check-error-patterns hook removed from settings-snippet.json
New defensive hooks (Group H):
- no-github-push.sh: PreToolUse:Bash hard deny on github.com push/create/sync/remote-add
(RULE 0.1 — patent IP protection; was missing from public kit)
- secrets-pre-guard.sh: PreToolUse:Edit|Write — token-pattern scan with allowlist (RULE 0.8)
- chat-numeric-prewarn.sh: UserPromptSubmit reminder when prompt mentions time/cost
(RULE 0.18 chat extension)
- chat-numeric-postflag.sh: Stop event scans last assistant message for naked numerics
without REAL/FROM-JOURNAL/ESTIMATE-HTC markers
Source: full Sonnet test-retest audit 2026-05-02 (3 parallel waves of 6 agents each)
identified hook chain bugs as HIGH severity in all 3 runs independently.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
81 lines
3.5 KiB
Bash
Executable file
81 lines
3.5 KiB
Bash
Executable file
#!/bin/bash
|
|
# PreToolUse(Edit|Write) — block unverified academic citations
|
|
#
|
|
# Rule 0.5 NO HALLUCINATION enforcer.
|
|
# Triggers on Edit.new_string / Write.content containing academic citation
|
|
# patterns. Blocks unless explicit [VERIFIED: <url>] / [UNVERIFIED] /
|
|
# retraction context is present.
|
|
#
|
|
# Incident log (session 68f86858, 2026-04-18) — all fabricated:
|
|
# Alpay-Jorgensen-Levanony 2022 JMP 63:062104
|
|
# Jaffe-Jäkel 2014 CMP 325
|
|
# Letac 2023 Lecture Notes "Cauchy Distributions on Euclidean Spaces and Groups"
|
|
# Archibald-Kratz-Meerschaert-Sabatelli 2001
|
|
|
|
set -u
|
|
|
|
INPUT=$(cat)
|
|
TOOL=$(printf '%s' "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
CONTENT=""
|
|
|
|
case "$TOOL" in
|
|
Edit)
|
|
CONTENT=$(printf '%s' "$INPUT" | jq -r '.tool_input.new_string // empty' 2>/dev/null)
|
|
;;
|
|
Write)
|
|
CONTENT=$(printf '%s' "$INPUT" | jq -r '.tool_input.content // empty' 2>/dev/null)
|
|
;;
|
|
*)
|
|
exit 0
|
|
;;
|
|
esac
|
|
|
|
[ -z "$CONTENT" ] && exit 0
|
|
|
|
# Short content unlikely to contain citation blocks
|
|
[ "${#CONTENT}" -lt 40 ] && exit 0
|
|
|
|
# Pattern A — hyphenated multi-author + year: "Foo-Bar 2014", "Alpay-Jorgensen-Levanony 2022"
|
|
PAT_A='[A-Z][a-zÀ-ÿ]+(-[A-Z][a-zÀ-ÿ]+){1,3}[, ]+(\(?(19|20)[0-9]{2}\)?)'
|
|
# Pattern B — journal+vol:page: "JMP 63:062104", "CMP 325", "Ann. Math. 149"
|
|
PAT_B='(J\. Math\. Phys\.|JMP|Comm\. Math\. Phys\.|CMP|Proc\. Amer\. Math\. Soc\.|Proc\. AMS|Ann\. Math\.|Ann\. Statist\.|J\. Multivariate Anal\.|JMVA|Phys\. Rev\. [DEL]|PRL|Adv\. Theor\. Math\. Phys\.|ATMP|J\. Geom\. Phys\.|JGP|Rev\. Mod\. Phys\.|Lett\. Math\. Phys\.|LMP)[ ]*\*?\*?[ ]*[0-9]{1,4}'
|
|
# Pattern C — "et al. YYYY" or "and X YYYY"
|
|
PAT_C='[A-Z][a-zÀ-ÿ]+( et al\.?| and [A-Z][a-zÀ-ÿ]+)[, ]+(\(?(19|20)[0-9]{2}\)?)'
|
|
|
|
HITS_A=$(printf '%s' "$CONTENT" | grep -oE "$PAT_A" 2>/dev/null | head -5 || true)
|
|
HITS_B=$(printf '%s' "$CONTENT" | grep -oE "$PAT_B" 2>/dev/null | head -5 || true)
|
|
HITS_C=$(printf '%s' "$CONTENT" | grep -oE "$PAT_C" 2>/dev/null | head -5 || true)
|
|
|
|
ALL_HITS=$(printf '%s\n%s\n%s' "$HITS_A" "$HITS_B" "$HITS_C" | grep -v '^$' || true)
|
|
[ -z "$ALL_HITS" ] && exit 0
|
|
|
|
# Allowlist: explicit verification or retraction context
|
|
ALLOW_REGEX='\[VERIFIED:|\[UNVERIFIED\]|\[FABRICATED|\[RETRACTED|\[MISATTRIBUTED|FABRICATED|RETRACTED 2026|MISATTRIBUTED|NOT FOUND|unverifiable|misattributed|does NOT exist|do NOT EXIST|are fabricated|were fabricated|\[HOOK-BYPASS:[[:space:]]*citation-verify'
|
|
if printf '%s' "$CONTENT" | grep -qE "$ALLOW_REGEX"; then
|
|
exit 0
|
|
fi
|
|
|
|
N=$(printf '%s\n' "$ALL_HITS" | wc -l | tr -d ' ')
|
|
|
|
cat >&2 <<EOF
|
|
[citation-verify] BLOCKED — $N academic citation pattern(s) without verification marker.
|
|
|
|
Sample matches:
|
|
$(printf '%s\n' "$ALL_HITS" | head -6)
|
|
|
|
RULE 0.5 NO HALLUCINATION. Prior fabrications (session 68f86858, 2026-04-18):
|
|
• "Alpay-Jorgensen-Levanony 2022 JMP 63:062104" → real is 2026 67:022302 (2 authors)
|
|
• "Jaffe-Jäkel 2014 CMP 325" → real is 3-author Jaffe-Jäkel-Martinez CMP 329
|
|
• "Letac 2023 Lecture Notes" → not in DBLP/arXiv/HAL
|
|
• "Archibald-Kratz-Meerschaert-Sabatelli 2001" → no trace
|
|
|
|
TO PROCEED (any one):
|
|
1) WebFetch/WebSearch the citation → paste DOI/URL as [VERIFIED: <url>] inline
|
|
2) Mark as [UNVERIFIED] and do NOT use as proof
|
|
3) If this is a retraction/audit, include literal: FABRICATED / RETRACTED / MISATTRIBUTED / unverifiable / NOT FOUND
|
|
|
|
Bypass (emergency only): prepend content with "[HOOK-BYPASS: citation-verify <reason>]".
|
|
EOF
|
|
|
|
# Exit code 2 = blocking error surfaced to model
|
|
exit 2
|