From 4fc06bb98812e479a8286152cf762ed97013aabf Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Wed, 15 Jul 2026 11:06:36 -0400 Subject: [PATCH 1/3] fix: attribution footer drift, checklist staleness, commit-skill bypass lint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes decisions #38, #71, #72 (Decisions Log: https://claude.ai/code/artifact/232ecdce-8316-4880-8c0a-dc3c7dcf3a63). - #38 (formatting-create-md-attribution-footer-drift): create.md's PR-body heredoc now emits the attribution footer it documents elsewhere. - #71 (naming-work-items-checklist-template-staleness): templates/checklist.md reconciled to add.md's actual current behavior (--force, --type native Issue Type resolution, --agent-ready body template, --recurring title prefix), all previously undocumented in the checklist. - #72 (tooling-gov-commit-skill-composition-lint): new guardrails hook flag-commit-pr-skill-bypass.sh — advisory PreToolUse/Bash guard flagging direct `git commit`/`gh pr create` calls that bypass the marketplace's own /commit and /pull-request skills, gated on the consuming project actually having source-control enabled (fails quiet otherwise). 19/19 tests pass. Co-Authored-By: Claude Sonnet 5 --- plugins/guardrails/.claude-plugin/plugin.json | 6 +- plugins/guardrails/README.md | 24 +- .../hooks/flag-commit-pr-skill-bypass.sh | 207 ++++++++++++++++++ .../hooks/flag-commit-pr-skill-bypass.test.sh | 118 ++++++++++ plugins/guardrails/hooks/hooks.json | 5 + .../skills/pull-request/reference/create.md | 2 + .../skills/work-items/templates/checklist.md | 7 +- 7 files changed, 361 insertions(+), 8 deletions(-) create mode 100755 plugins/guardrails/hooks/flag-commit-pr-skill-bypass.sh create mode 100755 plugins/guardrails/hooks/flag-commit-pr-skill-bypass.test.sh diff --git a/plugins/guardrails/.claude-plugin/plugin.json b/plugins/guardrails/.claude-plugin/plugin.json index abf116e89..f0c777cab 100644 --- a/plugins/guardrails/.claude-plugin/plugin.json +++ b/plugins/guardrails/.claude-plugin/plugin.json @@ -1,12 +1,12 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "guardrails", - "version": "0.3.2", - "description": "Six safety guards that block secret/credential writes, hardcoded machine-specific paths, git hook-bypass attempts, Bash file-write workarounds that circumvent Write/Edit hooks, (advisory) hallucinated CLI flags, and (advisory) un-throttled Workflow fan-out that risks burst 529s — each independently toggleable.", + "version": "0.4.0", + "description": "Seven safety guards that block secret/credential writes, hardcoded machine-specific paths, git hook-bypass attempts, 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", "email": "info@melodicsoftware.com" }, "license": "MIT", - "keywords": ["guard", "security", "secrets", "hooks", "pretooluse", "git", "hardcoded-paths", "cli-flags"] + "keywords": ["guard", "security", "secrets", "hooks", "pretooluse", "git", "hardcoded-paths", "cli-flags", "pull-request"] } diff --git a/plugins/guardrails/README.md b/plugins/guardrails/README.md index 57998069d..818533b4f 100644 --- a/plugins/guardrails/README.md +++ b/plugins/guardrails/README.md @@ -1,6 +1,6 @@ # guardrails -A Claude Code plugin bundling six **safety guards** that catch risky agent +A Claude Code plugin bundling seven **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. @@ -14,9 +14,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. | The four blocking guards feed their stderr message back to Claude as -actionable fix guidance. The two advisory guards surface their findings the same +actionable fix guidance. The three advisory guards surface their findings the same way but always allow the operation. ### Scope notes @@ -45,6 +46,16 @@ way but always allow the operation. (`echo "$(python3 -c 'import pathlib …')"`) is **not** caught — the strip treats the quoted span as inert. Same friction-guard, not-a-sandbox posture as `block-no-verify`. +- **`flag-commit-pr-skill-bypass` is a nudge, not a gate.** Detection is a + 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 + inline fallback when skill discovery is broken. ## Per-hook kill switches @@ -60,6 +71,7 @@ guard without touching the others. | block-hook-bypass | `HOOK_BLOCK_HOOK_BYPASS_ENABLED` | | cli-flag-verify | `HOOK_CLI_FLAG_VERIFY_ENABLED` | | workflow-resilience-check | `HOOK_WORKFLOW_RESILIENCE_CHECK_ENABLED` | +| flag-commit-pr-skill-bypass | `HOOK_FLAG_COMMIT_PR_SKILL_BYPASS_ENABLED` | Set them in your settings `env` block: @@ -88,6 +100,14 @@ repo-specific policy of their own: (`claude gh dotnet docker npm kubectl terraform az aws`); override with `HOOK_CLI_FLAG_VERIFY_BINS=bin1,bin2,…` and skip specific binaries with `HOOK_CLI_FLAG_VERIFY_SKIP_BINS=bin1,bin2`. +- **Skill-availability gating.** `flag-commit-pr-skill-bypass` reads + `enabledPlugins` from the consuming project's own `.claude/settings.json` + (`.claude/settings.local.json` as an override, only for a key already present + in `settings.json` — CC ignores a local-only key per + [anthropics/claude-code#27247](https://github.com/anthropics/claude-code/issues/27247)) + to confirm `source-control@…` is actually enabled before advising toward its + skills. Missing/uncertain state (no settings file, no jq, key absent) fails + quiet — never advises toward a skill the project doesn't have installed. ## Telemetry (opt-in) diff --git a/plugins/guardrails/hooks/flag-commit-pr-skill-bypass.sh b/plugins/guardrails/hooks/flag-commit-pr-skill-bypass.sh new file mode 100755 index 000000000..8e5fbf2fa --- /dev/null +++ b/plugins/guardrails/hooks/flag-commit-pr-skill-bypass.sh @@ -0,0 +1,207 @@ +#!/usr/bin/env bash +# PreToolUse hook: advisory guard for direct `git commit` / `gh pr create` +# calls that bypass this marketplace's own canonical `/commit` and +# `/pull-request create` skills (source-control plugin). +# Triggered on Bash tool calls. +# +# SCOPE — only fires when the source-control plugin's skills are actually +# available in the consuming project (`enabledPlugins["source-control@…"]` +# resolves `true` in the consuming project's `.claude/settings.json`, with +# `.claude/settings.local.json` honored as an override only for a key that +# already exists in `settings.json` — CC ignores a local-only key, see +# anthropics/claude-code#27247). Uncertain state (no jq, no settings file, key +# absent) fails QUIET (exit 0, no advisory) — an advisory firing on unknown +# state is noise, not signal. +# +# WHAT IT FLAGS: +# git commit — invoked WITHOUT the canonical mechanic's two markers +# (`-F -` piping the message via stdin, and a `--trailer` +# carrying the Co-Authored-By line). A manual `git commit` +# that already replicates the canonical shape (e.g. a +# hand-typed heredoc form) stays quiet — the guard targets the +# anti-pattern (`git commit -m "..."`) /commit exists to +# prevent, not "you didn't literally type /commit". +# gh pr create — invoked at all. /pull-request create's value is PROCESS +# (rebase onto default, unrelated-changes triage, closing- +# keyword gate, attribution footer) — there is no command-shape +# signature to gate on, so every direct invocation is flagged. +# Low-noise in practice: `gh pr create` runs once per PR. +# +# NON-BLOCKING (advisory): exits 0 always. Emits additionalContext naming the +# skill to prefer; never blocks the call — create.md itself documents a +# legitimate inline fallback when skill discovery is broken, so blocking here +# would be wrong. +# +# SCOPE (documented residual, same posture as block-hook-bypass's stripper): +# detection is a literal-stripped top-level regex match, not a full argv- +# grammar parser (contrast block-no-verify.sh) — proportionate to an advisory +# that never blocks. It does not evaluate shell variable / command +# substitution, and a determined author can construct a form that evades the +# match. This is a nudge toward the canonical skills, not an enforcement gate. +# +# Kill switch: HOOK_FLAG_COMMIT_PR_SKILL_BYPASS_ENABLED=false + +set -uo pipefail + +# shellcheck source=hook-utils.sh +source "$(dirname "${BASH_SOURCE[0]}")/hook-utils.sh" + +hook::check_enabled "FLAG_COMMIT_PR_SKILL_BYPASS" + +# High-res start stamp for the telemetry envelope. EPOCHREALTIME is Bash 5.0+; +# on older bash it is unset, so default to empty and skip telemetry. +start=${EPOCHREALTIME:-} + +# jq is required both to parse the tool payload and to read enabledPlugins. +# Fail OPEN (silent, advisory-only guard) when it is absent. +if ! command -v jq >/dev/null 2>&1; then + exit 0 +fi + +# Read inherited fd0 directly (bare cat) — NEVER `/dev/null | tr -d '\r') +[[ -n "$COMMAND" ]] || exit 0 + +# Privacy-safe telemetry subject: `Bash:` with leading `sudo` / +# env-assignment prefixes stripped and the token basenamed. Never the full +# command. +bash_subject() { + local cmd="$1" tok + tok="${cmd%%[[:space:]]*}" + while [[ "$tok" == "sudo" || "$tok" == *=* ]] \ + && [[ -n "$cmd" && "$cmd" == *[[:space:]]* ]]; do + cmd="${cmd#*[[:space:]]}" + cmd="${cmd#"${cmd%%[![:space:]]*}"}" + tok="${cmd%%[[:space:]]*}" + done + printf 'Bash:%s' "${tok##*/}" +} + +SUBJECT=$(bash_subject "$COMMAND") + +# Emit one telemetry envelope per run. Advisory guards always report status +# "ok" (they never block); the finding signal rides in `data.forms` — category +# labels only ("git-commit-bypass" / "gh-pr-create-bypass"), never the matched +# command text. +emit_tel() { + [[ -n "$start" ]] || return 0 + hook::telemetry_enabled || return 0 + local forms_json="[]" + if ((${#FORMS[@]} > 0)); then + forms_json=$(printf '%s\n' "${FORMS[@]}" | jq -R . | jq -s . 2>/dev/null) || forms_json="[]" + fi + local data + data=$(jq -n --arg subject "$SUBJECT" --argjson forms "$forms_json" \ + '{tool:"Bash",subject:$subject,forms:$forms}' 2>/dev/null) \ + || data='{"tool":"Bash","subject":"","forms":[]}' + hook::emit_telemetry "flag-commit-pr-skill-bypass" "PreToolUse" "ok" "$start" "$data" "${CLAUDE_PROJECT_DIR:-}" +} + +# Is the source-control plugin's skills surface available to this session? +# Resolves the consuming project's own settings, never this plugin's cache +# location. Returns 1 (unknown/absent → stay silent) on any uncertain step. +source_control_enabled() { + local root settings local_settings base_val local_val + root=$(hook::repo_root "${CLAUDE_PROJECT_DIR:-.}") + settings="$root/.claude/settings.json" + local_settings="$root/.claude/settings.local.json" + [[ -f "$settings" ]] || return 1 + + base_val=$(jq -r ' + (.enabledPlugins // {}) | to_entries[] + | select(.key | startswith("source-control@")) + | .value + ' "$settings" 2>/dev/null | head -1) + [[ -n "$base_val" ]] || return 1 + + local_val="" + if [[ -f "$local_settings" ]]; then + local_val=$(jq -r ' + (.enabledPlugins // {}) | to_entries[] + | select(.key | startswith("source-control@")) + | .value + ' "$local_settings" 2>/dev/null | head -1) + fi + + local effective="$base_val" + [[ -n "$local_val" ]] && effective="$local_val" + [[ "$effective" == "true" ]] +} + +source_control_enabled || exit 0 + +# Strip single- and double-quoted literal spans, and drop heredoc bodies +# wholesale, so the top-level invocation scan below sees shell syntax, not +# payload text (a commit body mentioning "git commit -m" in prose is not a +# false positive). Same posture as block-hook-bypass.sh's stripper. +strip_literals() { + local cmd="$1" line result="" in_heredoc=0 delim="" trimmed + local heredoc_start_re='(^|[^<])<<-?[[:space:]]*([^[:space:]<>]+)' + + while IFS= read -r line || [[ -n "$line" ]]; do + if ((in_heredoc)); then + trimmed="${line#"${line%%[![:space:]]*}"}" + trimmed="${trimmed%"${trimmed##*[![:space:]]}"}" + [[ "$trimmed" == "$delim" ]] && in_heredoc=0 + continue + fi + if [[ "$line" =~ $heredoc_start_re ]]; then + delim="${BASH_REMATCH[2]}" + delim="${delim#\\}" + delim="${delim#\'}" + delim="${delim%\'}" + delim="${delim#\"}" + delim="${delim%\"}" + line="${line%%<<*}${line#*"${BASH_REMATCH[0]}"}" + in_heredoc=1 + fi + line=$(printf '%s' "$line" | sed "s/'[^']*'//g" | sed -E 's/"([^"\\]|\\.)*"//g') + result+="${line}"$'\n' + done <<<"$cmd" + printf '%s' "${result%$'\n'}" +} + +STRIPPED_LC="$(strip_literals "$COMMAND")" +STRIPPED_LC="${STRIPPED_LC,,}" +COMMAND_LC="${COMMAND,,}" + +MESSAGES=() +FORMS=() + +# `git commit` at a command position (start of string, or after a control +# operator), without the canonical `-F -` stdin-message form AND a `--trailer` +# carrying the Co-Authored-By line. Either marker present → the caller already +# replicates the skill's mechanic; stay quiet. +if [[ "$STRIPPED_LC" =~ (^|[[:space:];&|()]+)git[[:space:]]+commit([[:space:]]|$) ]]; then + if ! [[ "$COMMAND_LC" =~ (^|[[:space:]])(-f|--file)[[:space:]]+-([[:space:]]|$) ]] \ + || ! [[ "$COMMAND_LC" =~ --trailer ]]; then + MESSAGES+=("direct \`git commit\` (missing the canonical \`-F -\` stdin form + \`--trailer\` Co-Authored-By line) — prefer the \`/commit\` skill (source-control plugin), which encapsulates the heredoc mechanic, Conventional Commits pre-check, and attribution trailer.") + FORMS+=("git-commit-bypass") + fi +fi + +# `gh pr create` at a command position. No command-shape signature separates +# skill-driven from ad hoc — /pull-request create's value is process (rebase, +# unrelated-changes triage, closing-keyword gate, attribution footer), so any +# direct invocation is flagged. +if [[ "$STRIPPED_LC" =~ (^|[[:space:];&|()]+)gh[[:space:]]+pr[[:space:]]+create([[:space:]]|$) ]]; then + MESSAGES+=("direct \`gh pr create\` — prefer \`/pull-request create\` (source-control plugin), which rebases onto the default branch, triages unrelated uncommitted changes, gates on a closing-issue keyword, and assembles the attribution footer before creating the PR.") + FORMS+=("gh-pr-create-bypass") +fi + +if ((${#MESSAGES[@]} == 0)); then + emit_tel + exit 0 +fi + +CONTEXT="Commit/PR skill composition (advisory): " +for m in "${MESSAGES[@]}"; do + CONTEXT+="$m " +done +hook::emit_additional_context PreToolUse "${CONTEXT% }" + +emit_tel +exit 0 diff --git a/plugins/guardrails/hooks/flag-commit-pr-skill-bypass.test.sh b/plugins/guardrails/hooks/flag-commit-pr-skill-bypass.test.sh new file mode 100755 index 000000000..4b2f4bca3 --- /dev/null +++ b/plugins/guardrails/hooks/flag-commit-pr-skill-bypass.test.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +# Contract test for flag-commit-pr-skill-bypass.sh (guardrails plugin). +# +# Black-box: pipes PreToolUse Bash JSON on stdin against a fixture consuming +# project (a temp dir with its own .claude/settings.json), asserts on the +# emitted additionalContext. The hook is advisory (always exit 0) — these +# cases verify WHEN it speaks (source-control enabled + bypass shape) and WHEN +# it stays silent (source-control absent/disabled, canonical shape already +# used, kill switch, no jq). Self-contained — no host-repo assertion library. + +set -uo pipefail + +HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +HOOK="$HOOK_DIR/flag-commit-pr-skill-bypass.sh" +TEST_TMPDIR="$(mktemp -d)" +trap 'rm -rf "$TEST_TMPDIR"' EXIT + +# shellcheck source=guardrails-test-helpers.sh +source "$HOOK_DIR/guardrails-test-helpers.sh" + +# make_project [settings.local.json-value] -> project dir +# Writes a fixture consuming project with .claude/settings.json (and an +# optional settings.local.json override) so the hook resolves source-control +# enablement from files, never this repo's own settings. +make_project() { + local dir enabled="${1:-}" local_val="${2:-}" + dir="$(mktemp -d -p "$TEST_TMPDIR")" + mkdir -p "$dir/.claude" + if [[ -n "$enabled" ]]; then + jq -n --argjson v "$enabled" '{enabledPlugins:{"source-control@melodic-software":$v}}' \ + >"$dir/.claude/settings.json" + else + printf '{}' >"$dir/.claude/settings.json" + fi + if [[ -n "$local_val" ]]; then + jq -n --argjson v "$local_val" '{enabledPlugins:{"source-control@melodic-software":$v}}' \ + >"$dir/.claude/settings.local.json" + fi + printf '%s' "$dir" +} + +ENABLED_PROJECT="$(make_project true)" +DISABLED_PROJECT="$(make_project false)" +NO_KEY_PROJECT="$(make_project)" + +run_hook() { + local input="$1" project="$2" + shift 2 + env CLAUDE_PROJECT_DIR="$project" "$@" bash "$HOOK" <<<"$input" 2>&1 +} + +# --- source-control enabled: bypass shapes fire ------------------------------ +out=$(run_hook "$(command_json 'git commit -m "quick fix"')" "$ENABLED_PROJECT") +assert_contains "git commit -m fires" "$out" "git commit" +assert_contains "names the /commit skill" "$out" "/commit" + +out=$(run_hook "$(command_json 'gh pr create --title x --body y')" "$ENABLED_PROJECT") +assert_contains "gh pr create fires" "$out" "gh pr create" +assert_contains "names the /pull-request skill" "$out" "/pull-request create" + +# --- source-control enabled: canonical shape stays silent -------------------- +out=$(run_hook "$(command_json 'git commit -F - --cleanup=verbatim --trailer "Co-Authored-By: Claude "')" "$ENABLED_PROJECT") +assert_silent "canonical -F - + --trailer stays silent" "$out" + +out=$(run_hook "$(command_json 'git commit --file - --trailer "Co-Authored-By: Claude"')" "$ENABLED_PROJECT") +assert_silent "canonical --file - + --trailer stays silent" "$out" + +# --- non-bypass commands stay silent ----------------------------------------- +out=$(run_hook "$(command_json 'git status')" "$ENABLED_PROJECT") +assert_silent "unrelated git command stays silent" "$out" + +out=$(run_hook "$(command_json "echo 'reminder: use git commit -m for quick fixes'")" "$ENABLED_PROJECT") +assert_silent "prose mentioning git commit -m stays silent" "$out" + +# --- source-control NOT available: stays silent regardless of shape ---------- +out=$(run_hook "$(command_json 'git commit -m "quick fix"')" "$DISABLED_PROJECT") +assert_silent "source-control explicitly disabled stays silent" "$out" + +out=$(run_hook "$(command_json 'git commit -m "quick fix"')" "$NO_KEY_PROJECT") +assert_silent "source-control key absent stays silent" "$out" + +NO_SETTINGS_PROJECT="$(mktemp -d -p "$TEST_TMPDIR")" +out=$(run_hook "$(command_json 'git commit -m "quick fix"')" "$NO_SETTINGS_PROJECT") +assert_silent "no .claude/settings.json at all stays silent" "$out" + +# --- settings.local.json override -------------------------------------------- +OVERRIDE_OFF="$(make_project true false)" +out=$(run_hook "$(command_json 'git commit -m "quick fix"')" "$OVERRIDE_OFF") +assert_silent "settings.local.json false overrides settings.json true" "$out" + +OVERRIDE_ON="$(make_project false true)" +out=$(run_hook "$(command_json 'git commit -m "quick fix"')" "$OVERRIDE_ON") +assert_contains "settings.local.json true overrides settings.json false" "$out" "git commit" + +# --- kill switch — disabled path is a clean no-op even on a bypass shape ----- +out=$(run_hook "$(command_json 'git commit -m "quick fix"')" "$ENABLED_PROJECT" \ + HOOK_FLAG_COMMIT_PR_SKILL_BYPASS_ENABLED=false) +assert_silent "kill switch off → no-op despite bypass shape" "$out" + +# --- empty stdin → silent (graceful no-op) ----------------------------------- +out=$(env CLAUDE_PROJECT_DIR="$ENABLED_PROJECT" bash "$HOOK" <<<"" 2>&1) +assert_silent "empty stdin is a no-op" "$out" + +# --- telemetry: fired case emits an `ok` envelope with a category label ------ +TEL="$(mktemp -p "$TEST_TMPDIR")" +SINK="$(make_sink "cat >>\"$TEL\"")" +env HOOK_TELEMETRY_SINK="$SINK" CLAUDE_PROJECT_DIR="$ENABLED_PROJECT" \ + bash "$HOOK" <<<"$(command_json 'git commit -m "quick fix"')" >/dev/null 2>&1 || true +if wait_for_sink "$TEL"; then + assert_contains "telemetry: hook id" "$(jq -r '.hook' "$TEL")" "flag-commit-pr-skill-bypass" + assert_contains "telemetry: status ok (advisory never blocks)" "$(jq -r '.status' "$TEL")" "ok" + assert_contains "telemetry: subject Bash:git" "$(jq -r '.data.subject' "$TEL")" "Bash:git" + assert_contains "telemetry: form git-commit-bypass" "$(jq -r '.data.forms[0]' "$TEL")" "git-commit-bypass" +else + bad "telemetry: no envelope written on advisory fire" +fi + +report diff --git a/plugins/guardrails/hooks/hooks.json b/plugins/guardrails/hooks/hooks.json index 30e0f57df..f9ba0d491 100644 --- a/plugins/guardrails/hooks/hooks.json +++ b/plugins/guardrails/hooks/hooks.json @@ -28,6 +28,11 @@ "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/block-hook-bypass.sh", "timeout": 10 + }, + { + "type": "command", + "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/flag-commit-pr-skill-bypass.sh", + "timeout": 10 } ] }, diff --git a/plugins/source-control/skills/pull-request/reference/create.md b/plugins/source-control/skills/pull-request/reference/create.md index 1d8c4fa3a..f75f0a199 100644 --- a/plugins/source-control/skills/pull-request/reference/create.md +++ b/plugins/source-control/skills/pull-request/reference/create.md @@ -149,6 +149,8 @@ TEMPLATE=$(cat <<'EOF' ## Test plan - ... + +🤖 Generated with [Claude Code](https://claude.com/claude-code) EOF ) diff --git a/plugins/work-items/skills/work-items/templates/checklist.md b/plugins/work-items/skills/work-items/templates/checklist.md index 19e1a5d73..898cc0f39 100644 --- a/plugins/work-items/skills/work-items/templates/checklist.md +++ b/plugins/work-items/skills/work-items/templates/checklist.md @@ -12,9 +12,10 @@ Copy into `//work-items-checklist.md` (default `.work/`). Per- ## Action: add -- [ ] Pre-flight: search-before-create (adapter: "Search items", `--state all`) — pivot if open/closed match exists -- [ ] Compose title (`: `); write the body to a temp file with the Write tool (argv-safe — never inline generated text); build labels -- [ ] `tools/work-item-tracker/work-item-tracker.sh create-item --title '...' --body "$(cat "$BODY_FILE")" --labels '...'` +- [ ] Pre-flight: search-before-create (adapter: "Search items", `--state all`) — pivot if open/closed match exists (skip if `--force`) +- [ ] Resolve issue type from `--type` (default `task`) — native GitHub Issue Type on org repos (passed via `--type`, not a label); coarse `type: bug`/`type: feature`/`type: task` label on personal/non-org repos +- [ ] Build labels list; build body (default template, or agent-brief template if `--agent-ready`); write the body to a temp file with the Write tool (argv-safe — never inline generated text) +- [ ] `tools/work-item-tracker/work-item-tracker.sh create-item --title '{title}' --body "$(cat "$BODY_FILE")" --type '{type}' --labels '...'` (`--type` on org repos only; `[Maintenance]` title prefix when `--recurring`) - [ ] Capture item ID/number for cross-reference ## Action: start From 1d1841e8980cc2871f1bd4df704f0b089977b94b Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Wed, 15 Jul 2026 14:12:11 -0400 Subject: [PATCH 2/3] fix: drop tracker-ref from flag-commit-pr-skill-bypass.sh comment The SCOPE comment cited `anthropics/claude-code#27247` inline, which trips this repo's own comment-hygiene `tracker-ref:repo-issue` rule (outstanding work belongs in the issue tracker, not code comments). The issue is already documented in the guardrails README and the claude-config-audit known-issues reference; the hook comment only needed the behavioral fact, not the citation. --- plugins/guardrails/hooks/flag-commit-pr-skill-bypass.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/guardrails/hooks/flag-commit-pr-skill-bypass.sh b/plugins/guardrails/hooks/flag-commit-pr-skill-bypass.sh index 8e5fbf2fa..111c79fa0 100755 --- a/plugins/guardrails/hooks/flag-commit-pr-skill-bypass.sh +++ b/plugins/guardrails/hooks/flag-commit-pr-skill-bypass.sh @@ -8,10 +8,9 @@ # available in the consuming project (`enabledPlugins["source-control@…"]` # resolves `true` in the consuming project's `.claude/settings.json`, with # `.claude/settings.local.json` honored as an override only for a key that -# already exists in `settings.json` — CC ignores a local-only key, see -# anthropics/claude-code#27247). Uncertain state (no jq, no settings file, key -# absent) fails QUIET (exit 0, no advisory) — an advisory firing on unknown -# state is noise, not signal. +# already exists in `settings.json` — CC ignores a local-only key). Uncertain +# state (no jq, no settings file, key absent) fails QUIET (exit 0, no +# advisory) — an advisory firing on unknown state is noise, not signal. # # WHAT IT FLAGS: # git commit — invoked WITHOUT the canonical mechanic's two markers From e24fff2c90c35f67ff149b93cf3a2d9b7b7e1f66 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Thu, 16 Jul 2026 04:55:18 -0400 Subject: [PATCH 3/3] fix: regenerate README catalog for guardrails guard count --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index db191311d..647cbf483 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Browse and manage with `/plugin`. To refresh after updates: `/plugin marketplace ### Security -- [`guardrails`](plugins/guardrails) — Six safety guards that block secret/credential writes, hardcoded machine-specific paths, git hook-bypass attempts, Bash file-write workarounds that circumvent Write/Edit hooks, (advisory) hallucinated CLI flags, and (advisory) un-throttled Workflow fan-out that risks burst 529s — each independently toggleable. +- [`guardrails`](plugins/guardrails) — Seven safety guards that block secret/credential writes, hardcoded machine-specific paths, git hook-bypass attempts, 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. ### Workflow