Skip to content

feat(guard-pack): all six guards in one Node process - #45

Merged
karanb192 merged 1 commit into
mainfrom
feat/guard-pack
Aug 18, 2026
Merged

feat(guard-pack): all six guards in one Node process#45
karanb192 merged 1 commit into
mainfrom
feat/guard-pack

Conversation

@karanb192

Copy link
Copy Markdown
Owner

Closes #41 (the last open item from it; #43 shipped the pluginization).

Why

Installing the six guard plugins individually costs six Node startups per matching tool call, about 35 ms each. Measured on the bench, that stack is 199 ms of guard latency per Bash call. The pack runs all six checks in one process for 38 ms median: 5.2x less, same protection.

How it works

One PreToolUse registration on Bash|Read|Edit|MultiEdit|Write. The runner requires the six guard modules and evaluates them in cheap-first order (config-guard, block-dangerous-commands, protect-secrets, protect-tests, git-safety, case-insensitive-guard; the filesystem-touching and git-spawning guards go last). It mirrors each guard's own main(): same tool filters, same escape hatches (CONFIG_GUARD_ALLOW skips only config-guard), same ask-mode semantics per guard (protect-tests and git-safety stay deny-only), and the winning verdict is emitted in that guard's exact output format, suffixed (via guard-pack). A guard that throws is logged and skipped, so one broken guard can never switch off the other five.

lib/ holds byte-identical copies of the six plugin scripts (a plugin install caches only its own directory, so the pack must carry its copies); a test pins every copy against plugins/<name>/<name>.js, same pattern as the bouncer fix, so they cannot drift.

Env passthrough is inherent, not reimplemented: the modules read HOOK_SAFETY_LEVEL (uniform across the pack; per-guard levels need individual installs, the README says so), HOOK_ASK_*, and CONFIG_GUARD_ALLOW themselves.

Bench

The bench also gains the two tool-call-path rows it was missing since #43 (config-guard, and the instructions-audit PreToolUse enforcement arm measured with no lockdown flag), plus guard-pack. RESULTS regenerated on the same machine:

Row Median (ms)
six guards separately (sum) 198.6
guard-pack 38.0

Tests

26 new tests (drift pins for all six lib copies, verdict format and ordering through the pack, env passthrough including ask-mode and the escape hatch, a case-collision test that adapts to whether the volume folds case so it holds on Linux CI, robustness on malformed payloads). Suite: 1570 pass, 0 fail. claude plugin validate passes for the plugin and the marketplace. Marketplace and README move to 20 plugins with a do-not-double-install warning.

Note: this branch will need a trivial rebase if #44 (dash stragglers) merges first; both touch .claude-plugin/marketplace.json.

Installing the guards individually costs six Node startups per
matching tool call; the pack pays one. Measured on the bench: 38 ms
median for the pack vs 199 ms for the six guards separately.

The runner mirrors each guard's own main(): same tool filters, same
env vars (HOOK_SAFETY_LEVEL uniformly, HOOK_ASK_*, CONFIG_GUARD_ALLOW),
same reason format with a (via guard-pack) suffix, first blocking
verdict wins, and a guard that throws is logged and skipped so one
broken guard cannot switch off the other five. lib/ copies are pinned
byte-identical to the individual plugin scripts by a test.

Bench grows rows for config-guard, the instructions-audit PreToolUse
arm, and guard-pack; RESULTS regenerated. Marketplace and README go
to 20 plugins; badge synced to 1570.
@karanb192
karanb192 merged commit 66f84f9 into main Aug 18, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ship the classic hook-scripts as installable plugins

1 participant