Skip to content

v2.4.1

Choose a tag to compare

@karlkfi karlkfi released this 03 Aug 16:30

Production-target guard rails for Claude Code Bash commands.

Important

Two of these three fixes remove friction rather than add it — they turn a deny into a silent defer. That is the direction this project treats as needing justification, not the other way round, so each one below names the shell rule it rests on and the carve-outs that keep it from opening a hole. No target classification, verb table, or covered tool changed.

Highlights

A commit message that quotes an infrastructure command is no longer denied. The tokenizer rewrites newlines to ;, so every line of a heredoc body became its own simple command to inspect. Prose about infrastructure work quotes infrastructure commands, so a Conventional-Commit message describing a helm upgrade procedure got denied on its own body text — and the workaround was to write the message to a file first, turning every such commit into two steps. A python3 - <<'PY' script whose string literals mentioned kubectl apply hit the same wall.

A quoted delimiter (<<'EOF', <<"EOF", <<\EOF) makes the body literal text on stdin: the shell expands nothing and runs nothing from it. There is no shell behavior for the guard to miss, so scanning it can only ever produce false positives.

This is the one place the splitter removes segments instead of adding them, so it is deliberately narrow — only a quoted delimiter strips anything, only << outside quotes opens a heredoc, the closing delimiter must actually appear, and a body is never re-scanned for further openers. The shapes that merely look like heredocs cannot trigger it: <<< here-strings and $((1<<2)) left shifts carry no quoted delimiter. The reasoning is written up in design.md.

kubectl delete --help no longer denies a page that contacts nothing. Classification looked only at the verb, so asking for usage on a destructive subcommand was treated as the destructive subcommand. A help flag makes the CLI print usage and exit, so the segment now short-circuits before the evaluator runs on --help, -help, -h, or a leading help word.

Two carve-outs keep that from becoming a bypass:

  • ssh has no long options, so ssh host --help runs --help on the remote and the connection to the target happens regardless.
  • On docker/podman/nerdctl, -h is --hostname, so docker run -h box image stays an ordinary mutating run.

The exemption is per simple command, so a help page chained ahead of a real mutating command still denies on the second segment.

The friction report no longer counts other guards' overrides as prod-guard's. OVERRIDE_SIG matched the bare phrase "override acknowledged", which every sibling guard emits. Under --plugin all — or --plugin foreground-guard — another guard's downgrades landed in prod-guard's counter and printed under a hardcoded PROD_GUARD_OVERRIDE label. That is a fabricated statistic with nothing in the output to signal it was wrong.

The signature is now anchored to prod-guard's own two reason shapes, and the line is dropped from the text report under --plugin all, where a single guard's count has no place in an all-guards header. The default --plugin prod-guard is unchanged.

Upgrading

No action required. Every change narrows when the guard fires; nothing you configured against v2.4.0 needs re-vetting. The one habit worth dropping is the heredoc workaround — commit messages that quote infrastructure commands no longer need to be written to a file first.

claude plugin marketplace update prod-guard
claude plugin update prod-guard@prod-guard

Restart to apply. Both commands work headlessly and share Claude Desktop's plugin state, so they update a Desktop install too.

Everything since v2.4.0

Three fixes, closing two issues.

Fixes (3)

  • fix(parsing): skip heredoc bodies opened with a quoted delimiter by @karlkfi in #32 (part 2 of #28)
  • fix(help): defer when a segment only asks for usage text by @karlkfi in #31 (part 1 of #28)
  • fix(friction-report): attribute override downgrades to prod-guard only by @karlkfi in #30 (closes #29)

Documentation

  • docs/design.md gains Why a quoted heredoc body is the one place segments are removed — the four constraints above, and why this exception does not generalize.

Validation

389 tests pass, up from 363 at v2.4.0 — 26 new ones, the bulk of them fixtures for the two parsing changes. Stdlib unittest, no third-party dependencies, run on Python 3.10 through 3.13 in CI:

python3 -m unittest discover tests

The parsing cases matter more than the count here. Both fixes remove segments from inspection, so the tests that earn their keep are the ones asserting what still gets caught: a help page chained ahead of a mutating command, ssh host --help, docker run -h, an unterminated heredoc, an unquoted <<EOF body, and the here-string and left-shift shapes that must never strip anything. Guard decisions are exercised by invoking the script as a subprocess against a synthetic $HOME, never by running the guarded tool.

Full changelog: v2.4.0...v2.4.1