From e420f483dd29caf1ab896861093aa367feab22b4 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 25 Jul 2026 13:44:11 -0400 Subject: [PATCH] fix(guardrails): raise PreToolUse blocking-guard timeouts from 10/15s to 60s MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- plugins/guardrails/.claude-plugin/plugin.json | 2 +- plugins/guardrails/CHANGELOG.md | 31 +++++++++++++++++++ plugins/guardrails/hooks/hooks.json | 14 ++++----- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/plugins/guardrails/.claude-plugin/plugin.json b/plugins/guardrails/.claude-plugin/plugin.json index f658731cf4..deb0c8763f 100644 --- a/plugins/guardrails/.claude-plugin/plugin.json +++ b/plugins/guardrails/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "guardrails", - "version": "0.15.0", + "version": "0.15.1", "description": "Eleven safety guards that block secret/credential writes, hardcoded machine-specific paths, git hook-bypass attempts, irreversible git operations (force-push, reset --hard, worktree-wide checkout/restore discards), Bash file-write workarounds that circumvent Write/Edit hooks, commit subjects and gh pr create titles that violate the repo's tracked team convention (when one is declared in .claude/source-control.md), (advisory) hallucinated CLI flags, (advisory) /plugin:skill references that do not resolve, (advisory) un-throttled Workflow fan-out that risks burst 529s, and (advisory) direct git commit/gh pr create calls bypassing this marketplace's own commit/pull-request skills — each independently toggleable.", "author": { "name": "Melodic Software", diff --git a/plugins/guardrails/CHANGELOG.md b/plugins/guardrails/CHANGELOG.md index 86cc5fbfc1..9b45536bf1 100644 --- a/plugins/guardrails/CHANGELOG.md +++ b/plugins/guardrails/CHANGELOG.md @@ -3,6 +3,37 @@ All notable changes to the `guardrails` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.15.1] + +### Fixed + +- **PreToolUse blocking guards were declared with `timeout: 10`/`15` — 40-60x below the platform's + own documented `command`-hook default of 600s for `PreToolUse` (only `UserPromptSubmit` (30) and + `MessageDisplay` (10) lower it; `PreToolUse` does not — , + fetched 2026-07-25) — causing the harness to kill them before completion under real machine load + and let the guarded tool call proceed with no `permissionDecision` from that guard.** Measured at + 86.1% of PreToolUse runs killed at the declared timeout across 3,923 runs on one machine + (melodic-software/claude-code-plugins#1345). Confirmed against this session's own local + `~/.claude/projects/*/*.jsonl` transcript: a `hook_cancelled` attachment for + `block-convention-violation.sh` (`timedOut: true`, `durationMs: 10184` against `timeoutMs: 10000`) + was immediately followed by the guarded Bash tool call executing and returning a real result — the + guard's verdict was silently lost, not merely slow. Standalone timing of all seven affected guards + in this repo (no concurrent hook load) completed in well under 1.5s each, and the source contains no + network calls or unbounded loops — confirming the guards are not inherently slow; the declared + timeout was simply provisioned far below what the platform allows and below what real (contended) + runs need. `timeout` raised from 10/15 to **60** (10-40x more headroom over the every real duration + sample this investigation captured, while staying well short of the 600s platform default so a + genuinely hung process is still bounded) for the seven **blocking** PreToolUse 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`, which never block + regardless of outcome) and the PostToolUse hooks are unchanged — a missed advisory notice is not the + fail-open security defect this fix addresses. This mitigation narrows the timeout-driven fail-open + window; it does not remove it — a harness-killed hook process cannot itself report a decision, and + what should happen to the guarded tool call when a *blocking* guard is killed (deny by default vs. + today's silent fallback) is a harness-level policy question outside a plugin's control, tracked + separately. + ## [0.15.0] ### Added diff --git a/plugins/guardrails/hooks/hooks.json b/plugins/guardrails/hooks/hooks.json index 7a15235d0d..b12e8e3e02 100644 --- a/plugins/guardrails/hooks/hooks.json +++ b/plugins/guardrails/hooks/hooks.json @@ -7,13 +7,13 @@ { "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/secret-pattern-detection.sh", - "timeout": 15, + "timeout": 60, "statusMessage": "Checking for secrets..." }, { "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/hardcoded-path-check.sh", - "timeout": 15, + "timeout": 60, "statusMessage": "Checking for hardcoded paths..." } ] @@ -24,19 +24,19 @@ { "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/block-no-verify.sh", - "timeout": 10, + "timeout": 60, "statusMessage": "Checking for --no-verify bypass..." }, { "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/block-dangerous-git.sh", - "timeout": 10, + "timeout": 60, "statusMessage": "Checking for dangerous git commands..." }, { "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/block-hook-bypass.sh", - "timeout": 10, + "timeout": 60, "statusMessage": "Checking for hook-bypass attempts..." }, { @@ -53,13 +53,13 @@ { "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/block-noncanonical-commit.sh", - "timeout": 10, + "timeout": 60, "statusMessage": "Checking commit-message convention..." }, { "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/block-convention-violation.sh", - "timeout": 10, + "timeout": 60, "statusMessage": "Checking commit subject / PR title against the team convention..." } ]