Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion plugins/guardrails/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "guardrails",
"version": "0.8.0",
"version": "0.9.0",
"description": "Eight 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, (advisory) hallucinated CLI flags, (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",
Expand Down Expand Up @@ -50,6 +50,12 @@
"description": "Block Bash file-write workarounds that circumvent Write/Edit hook gates",
"default": true
},
"block_noncanonical_commit_enabled": {
"type": "boolean",
"title": "block-noncanonical-commit guard",
"description": "Block `git commit` that does not pipe its message via `-F -`; --amend, -C/-c, --fixup/--squash, -F <path>, and an in-progress merge/rebase are exempt",
"default": true
Comment thread
kyle-sexton marked this conversation as resolved.
},
"cli_flag_verify_enabled": {
"type": "boolean",
"title": "cli-flag-verify guard",
Expand Down Expand Up @@ -85,6 +91,12 @@
"title": "block-dangerous-git allow-list",
"description": "Comma-separated forms block-dangerous-git permits: push-force, reset-hard, clean-force, checkout-dot, restore-dot, checkout-force; empty blocks all",
"default": ""
},
"block_noncanonical_commit_allow": {
"type": "string",
"title": "block-noncanonical-commit allow-list",
"description": "Comma-separated form tokens to allow (currently: message-flag, which permits a bare `-m`)",
"default": ""
}
}
}
39 changes: 39 additions & 0 deletions plugins/guardrails/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,45 @@
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.9.0]

### Added

- **`block-noncanonical-commit` — `git commit` must pipe its message via `-F -`.** The advisory that
previously covered this was overridden 11 times in a single session; an advisory that is always
overridden trains the reader to filter it out. The guard enforces the *mechanic*, not the ritual:
`git commit -m "<multi-line>"` flattens newlines unpredictably across shells, and the stdin form is
what prevents it. Exempt, because no message-on-stdin form exists for them and gating them would
strand real work: `--amend`/`--no-edit`, `-C`/`-c`/`--reuse-message`/`--reedit-message`,
`--fixup`/`--squash`, `-F <path>`, and any commit taken while a merge, rebase, cherry-pick, or
revert is in progress. Kill switch `block_noncanonical_commit_enabled`; allow-list
`block_noncanonical_commit_allow` (`message-flag` permits a bare `-m`). Detection reuses the
argv-grammar-faithful parser, so `bash -lc` wrappers resolve and a commit body merely *mentioning*
`git commit -m` never fires. Aliases are expanded before the subcommand verdict — inline `-c`
(last value wins, as git applies it) and aliases persisted in git config alike — closing the hole
where `git c -m x` reads as subcommand `c` and walks straight through. `--config-env` aliases are a
documented residual: the shared parser stores their value undifferentiated from `-c`, so the
environment variable *name* arrives in place of the expansion (tracked separately). `git -C <path>` is honored when probing sequencer state, so a conflict
resolution driven at another repo reads that repo's state rather than the session cwd's.

### Fixed

- **`flag-commit-pr-skill-bypass` no longer demands `--trailer`.** The old condition required both
`-F -` **and** `--trailer`, but `/commit` omits the trailer when the resolved `trailer_policy` is
`none` — so in a repo whose convention forbids a co-author trailer, the skill's own conformant
output was flagged on every commit. The trailer is policy; only the stdin form is mechanic. This
also had to be settled before the new guard could block on the same condition: requiring
`--trailer` to pass would have permanently blocked `/commit` in that configuration.

### Changed

- **`flag-commit-pr-skill-bypass` is now `gh pr create`-only.** The `git commit` branch moved to
`block-noncanonical-commit`, so the two never double-fire on one command. `gh pr create` stays
advisory and cannot become otherwise: `/pull-request create` issues that exact command itself, and
[anthropics/claude-code#22655](https://github.com/anthropics/claude-code/issues/22655) (expose
`skill_name` to hooks) is closed as not planned — a hook cannot tell a skill-driven call from an
ad hoc one, so blocking it would deadlock the skill.

## [0.8.0]

### Changed
Expand Down
26 changes: 18 additions & 8 deletions plugins/guardrails/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# guardrails

A Claude Code plugin bundling eight **safety guards** that catch risky agent
A Claude Code plugin bundling nine **safety guards** that catch risky agent
actions the moment they happen — before a write lands or a bash command runs.
Each guard is independently toggleable, so you run exactly the subset you want.

Expand All @@ -15,9 +15,10 @@ Each guard is independently toggleable, so you run exactly the subset you want.
| **block-hook-bypass** | PreToolUse · Bash | **Blocks** (exit 2) | Bash file-write workarounds that circumvent the Write/Edit hook gates — `cat > file`, `echo … > file`, and `python3 -c` with file-write indicators. Executable-token detection ignores quoted prose/commit text that merely mentions the pattern. |
| **cli-flag-verify** | PostToolUse · Write \| Edit | **Advisory** (exit 0) | Hallucinated CLI flags — a `--flag` written as a command that does not exist in the binary's actual `--help` output. Surfaces via `additionalContext`, never blocks. |
| **workflow-resilience-check** | PreToolUse · Workflow | **Advisory** (exit 0) | Un-throttled Workflow fan-out — a script calling `parallel()` / `pipeline()` with no wave-cap throttle (`inWaves` / `inWavesPipeline`) and no retry wrapper (`agentRetry`), which risks a burst 529 under wide Opus fan-out. Surfaces a resilience checklist via `additionalContext`, never blocks. |
| **flag-commit-pr-skill-bypass** | PreToolUse · Bash | **Advisory** (exit 0) | Direct `git commit` (missing the canonical `-F -` stdin form + `--trailer` Co-Authored-By line) or any `gh pr create`, bypassing this marketplace's own `/commit` / `/pull-request create` skills. Only fires when the consuming project's own `.claude/settings.json` enables the `source-control` plugin — silent otherwise. Surfaces via `additionalContext`, never blocks. |
| **block-noncanonical-commit** | PreToolUse · Bash | **Blocks** (exit 2) | `git commit` that does not pipe its message via `-F -` / `--file -` — `-m` flattens newlines unpredictably across shells. Exempt: `--amend`, `-C`/`-c`/`--reuse-message`/`--reedit-message`, `--fixup`/`--squash`, `-F <path>`, and any commit taken while a merge/rebase/cherry-pick/revert is in progress. Resolves `bash -lc` wrappers and git aliases (inline `-c` and persisted config alike). |
| **flag-commit-pr-skill-bypass** | PreToolUse · Bash | **Advisory** (exit 0) | Any `gh pr create`, bypassing this marketplace's own `/pull-request create` skill. Only fires when the consuming project's own `.claude/settings.json` enables the `source-control` plugin — silent otherwise. Surfaces via `additionalContext`, never blocks. |

The five blocking guards feed their stderr message back to Claude as
The six blocking guards feed their stderr message back to Claude as
actionable fix guidance. The three advisory guards surface their findings the same
way but always allow the operation.

Expand Down Expand Up @@ -56,13 +57,21 @@ way but always allow the operation.
literal-stripped top-level regex match, not a full argv-grammar parser — it
does not evaluate shell variable / command substitution, and a determined
author can construct a form that evades it. It cannot tell "the skill ran
this exact command" from "someone hand-typed the same shape" for
`git commit` it targets the anti-pattern (`-m` without the canonical
`-F -` + `--trailer`), not literal `/commit` invocation; for `gh pr create`
there is no command-shape signature at all, so every direct call is flagged.
Always advisory (never blocks) — `create.md` itself documents a legitimate
this exact command" from "someone hand-typed the same shape", and for
`gh pr create` there is no command-shape signature at all, so every direct
call is flagged. It stays advisory and cannot become otherwise:
`/pull-request create` issues that exact command itself, so blocking it would
deadlock the skill being advertised. `create.md` also documents a legitimate
inline fallback when skill discovery is broken.

- **`block-noncanonical-commit` gates shape, not skill invocation.** No hook can
see which skill (if any) originated a Bash call, so "did you run `/commit`" is
not an available condition — and shape is the better target regardless, since
it enforces an outcome verifiable in `git log`. It deliberately does not
require `--trailer`: `/commit` omits the trailer under a resolved
`trailer_policy` of `none`, so demanding it would block the skill's own
conformant output in repos whose convention forbids co-author trailers.

## Per-hook kill switches

Each guard is toggled by its own `userConfig` boolean (default **on**; set to
Expand All @@ -76,6 +85,7 @@ contract — disable one guard without touching the others.
| block-no-verify | `block_no_verify_enabled` |
| block-dangerous-git | `block_dangerous_git_enabled` |
| block-hook-bypass | `block_hook_bypass_enabled` |
| block-noncanonical-commit | `block_noncanonical_commit_enabled` |
| cli-flag-verify | `cli_flag_verify_enabled` |
| workflow-resilience-check | `workflow_resilience_check_enabled` |
| flag-commit-pr-skill-bypass | `flag_commit_pr_skill_bypass_enabled` |
Expand Down
Loading
Loading