fix(guardrails): raise PreToolUse blocking-guard timeouts from 10/15s to 60s - #1379
Conversation
… to 60s The seven blocking PreToolUse guards declared `timeout: 10`/`15` — 40-60x below the platform's documented 600s default for PreToolUse command hooks (only UserPromptSubmit/MessageDisplay lower it; confirmed via fresh WebFetch of https://code.claude.com/docs/en/hooks). The harness was killing them before completion under real load (measured 86.1% of PreToolUse runs killed, #1345), and a killed guard contributes no permissionDecision, so the guarded tool call proceeds unguarded. Confirmed empirically against this session's own local jsonl transcript: a timedOut:true attachment for block-convention-violation.sh was immediately followed by the guarded Bash call executing with a real result — the fail-open is real, not merely a documentation gap. Standalone timing of all seven guards (no concurrent load) completed in well under 1.5s each, and none contain network calls or unbounded loops, confirming the guards are not inherently slow — they were simply underprovisioned. timeout raised to 60 for: secret-pattern-detection, hardcoded-path-check, block-no-verify, block-dangerous-git, block-hook-bypass, block-noncanonical-commit, block-convention-violation. The two advisory PreToolUse hooks (never block regardless) and PostToolUse hooks are unchanged — out of scope for this security-defect fix. This narrows the timeout-driven fail-open window; it does not remove it. What should happen to a guarded tool call when a blocking guard is genuinely killed is a harness-level policy question outside plugin control, filed separately. Closes #1345 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 35s —— View job PR Review: fix(guardrails): raise PreToolUse blocking-guard timeouts from 10/15s to 60sTasks
SummaryThis is a minimal, surgical fix to a real security defect. The change is correct and appropriately scoped. No blocking concerns; a few observations below.
|
|
Claude finished @kyle-sexton's task in 1m 21s —— View job Security Review — PR #1379
ScopeThree files changed: FindingsNo security vulnerabilities found. What was checked
The only change is to integer The change is security-positive: the prior 10/15s values caused blocking guards to be killed by the harness before they could return a The advisory hooks (
Patch bump only. No structural change to the manifest, no new
No secrets, credentials, real hostnames, or machine-specific paths. The GitHub Actions / supply-chain surface No workflow files touched; no action pins changed; no dependency manifests modified. SummaryThis is a pure configuration patch — seven integer values raised from 10/15 to 60. It does not introduce any injection surface, authorization gap, trust escalation, or dangerous GitHub Actions pattern. It strengthens the security posture of the plugin by reducing the probability of blocking guards failing open due to harness-imposed timeouts. |
This was generated by AI during work-loop execution.
Summary
plugins/guardrails/hooks/hooks.jsondeclaredtimeout: 10/15— 40-60x below the platform's documented 600s default forPreToolUsecommandhooks (only
UserPromptSubmit(30) andMessageDisplay(10) lower it; confirmed via WebFetch ofhttps://code.claude.com/docs/en/hooks, fetched 2026-07-25). The harness kills a hook that exceeds
its declared timeout, and a killed hook contributes no
permissionDecision, so the guarded toolcall proceeds without that guard's verdict — measured at 86.1% of PreToolUse runs on one machine
(guardrails: PreToolUse guards fail open — killed at their declared timeout on 86% of runs #1345).
timeoutto 60 for the seven blocking guards:secret-pattern-detection,hardcoded-path-check,block-no-verify,block-dangerous-git,block-hook-bypass,block-noncanonical-commit,block-convention-violation. The two advisory PreToolUse hooks(
flag-commit-pr-skill-bypass,workflow-resilience-check— never block regardless of outcome)and the PostToolUse hooks are unchanged: a missed advisory notice is not the security defect this
fix addresses.
0.15.0→0.15.1(patch, Fixed) and a CHANGELOG entry recording the evidence.Verification performed (per the item's "verify it genuinely closes the fail-open window" instruction)
session's local
~/.claude/projects/*/*.jsonlcontains ahook_cancelledattachment forblock-convention-violation.sh(timedOut: true,durationMs: 10184,timeoutMs: 10000)immediately followed by the guarded Bash tool call executing and returning a real result — the
guard's verdict was silently dropped, confirmed empirically, not inferred from documentation (the
official docs do not specify cancel/timeout behavior — confirmed by the same WebFetch above).
guard scripts (
hook-utils.sh,block-convention-violation.sh,block-hook-bypass.sh, etc.): purebash string parsing plus a bounded handful of external process spawns (
jq,git, onebashsubshell for the convention resolver) — no network calls, no unbounded loops. Then timed all seven
standalone (no concurrent hook load) with representative PreToolUse payloads against this repo:
every guard completed in under 1.5s. This rules out "the script itself hangs" and confirms the
bottleneck is process-spawn/contention overhead under real harness load, consistent with the issue's
own root-cause data (median 3.0 concurrent hook starts on killed runs vs. 1.0 on completed ones).
fix: no duplicate
guardrailsregistration in~/.claude/settings.jsonor the plugin cache, and notoolUseID in the sampled transcript fired the same guard command twice.
only edits
hooks.json, not any.shscript, but run for regression confidence:block-no-verify112/112,
block-dangerous-git251/251,block-hook-bypass203/203,block-noncanonical-commit90/90,
block-convention-violation31/31,hardcoded-path-check72/72,secret-pattern-detection42/42 — all passing, 0 failures.
hooks.json,plugin.json) parse cleanly.Scope boundary (why this is a mitigation, not a closure)
Raising the timeout narrows the fail-open window measured in #1345; it does not remove it. A
harness-killed hook process cannot itself report a decision — that is a harness-level property no
plugin can change. What should happen to a guarded tool call when a blocking guard is genuinely
killed (deny-by-default vs. today's silent fallback) is a policy decision outside this plugin's
control and outside this item's
work-class: scopedbrief. Filed as its own item rather than foldedinto this fix: #1378.
Test plan
plugins/guardrails/hooks/*.test.shfor all seven touched guards — 701/701 passing (0 failures)jq emptyonhooks.jsonandplugin.jsonCloses #1345
Related