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
6 changes: 6 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@
"source": "./plugins/actionlint",
"category": "formatting",
"tags": ["actionlint", "github-actions", "workflow", "yaml", "linter", "hook"]
},
{
"name": "guardrails",
"source": "./plugins/guardrails",
"category": "security",
"tags": ["guard", "security", "secrets", "hardcoded-paths", "git", "cli-flags", "hook"]
}
]
}
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@
persist-credentials: false
- name: Check for machine-specific paths
uses: melodic-software/ci-workflows/.github/actions/machine-specific-paths@2275e82c502d29b468e3ee2b9c11b2de69b1677f # b6431a1 2026-06-26
with:
# The guardrails plugin bundles a path-detection pattern lib whose
# regex bodies self-match this lane's own detector.
exclude: ':(exclude)plugins/guardrails/lib/path-detection/**'

eol-renormalize:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -219,14 +223,14 @@
# silently skipping when the binaries are absent.
- name: Install plugin test toolchains
run: |
npm install -g @biomejs/biome

Check warning on line 226 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor / zizmor

adhoc-packages

ci.yml:226: ad-hoc installation of packages: installs a package outside of a lockfile
python3 -m pip install --user ruff
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Run plugin contract tests
run: scripts/run-plugin-tests.sh
# Node.js is preinstalled on ubuntu-latest; no setup-node action needed.
- name: Install Claude Code CLI
run: npm install -g @anthropic-ai/claude-code

Check warning on line 233 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor / zizmor

adhoc-packages

ci.yml:233: ad-hoc installation of packages: installs a package outside of a lockfile
- name: Validate plugin and catalog manifests
run: scripts/validate-plugins.sh

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Browse and manage with `/plugin`. To refresh after updates: `/plugin marketplace
| [`desktop-notification`](plugins/desktop-notification) | Hook | Alerts you when Claude Code needs input via an audible bell, an OSC 9 terminal notification, and an OS-native toast (macOS/Linux) on permission and idle prompts. |
| [`powershell-format`](plugins/powershell-format) | Hook | Formats and lints PowerShell on edit via PSScriptAnalyzer, only when the repo opts in with a `PSScriptAnalyzerSettings.psd1`, using the consuming repo's own analyzer settings. |
| [`actionlint`](plugins/actionlint) | Hook | Lints GitHub Actions workflow files (`.github/workflows/*.yml`/`.yaml`) on edit via the `actionlint` already on your `PATH` — advisory findings, never blocking. |
| [`guardrails`](plugins/guardrails) | Hook | Bundles four independently-toggleable PreToolUse safety guards: secret-pattern detection, hardcoded machine-path check, git hook-bypass blocking (`--no-verify`, `core.hooksPath`, `LEFTHOOK=0`), and advisory CLI-flag verification. |

Install one: `/plugin install <plugin-name>@melodic-software`.

Expand Down
4 changes: 4 additions & 0 deletions docs/conventions/hook-telemetry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,7 @@ producers without coordinating with them or each other.
|----------|--------------|-------------|
| `markdown-formatter` plugin | `markdown-format` | `data/markdown-format.schema.json` |
| `desktop-notification` plugin | `desktop-notification` | `data/desktop-notification.schema.json` |
| `guardrails` plugin | `secret-pattern-detection` | `data/secret-pattern-detection.schema.json` |
| `guardrails` plugin | `hardcoded-path-check` | `data/hardcoded-path-check.schema.json` |
| `guardrails` plugin | `cli-flag-verify` | `data/cli-flag-verify.schema.json` |
| `guardrails` plugin | `block-no-verify` | `data/block-no-verify.schema.json` |
23 changes: 23 additions & 0 deletions docs/conventions/hook-telemetry/data/block-no-verify.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://raw.githubusercontent.com/melodic-software/claude-code-plugins/main/docs/conventions/hook-telemetry/data/block-no-verify.schema.json",
"title": "block-no-verify telemetry data",
"description": "Per-hook `data` payload for the block-no-verify guard. Discovered from the envelope `hook` value \"block-no-verify\". Evolves additive-only. Carries NO full command string.",
"type": "object",
"required": ["tool", "subject", "form"],
"additionalProperties": true,
"properties": {
"tool": {
"type": "string",
"description": "Always \"Bash\" — this guard matches only Bash tool calls."
},
"subject": {
"type": "string",
"description": "Privacy-safe command subject `Bash:<first-token>` with leading sudo / env-assignment prefixes stripped and the token basenamed. NEVER the full command or its arguments."
},
"form": {
"type": "string",
"description": "The bypass form when blocked: \"no-verify\" | \"hooksPath\" | \"hook-manager-env\" | \"too-long\" (command exceeded the parse cap and was blocked fail-closed). Empty string when the command was allowed (status ok)."
}
}
}
24 changes: 24 additions & 0 deletions docs/conventions/hook-telemetry/data/cli-flag-verify.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://raw.githubusercontent.com/melodic-software/claude-code-plugins/main/docs/conventions/hook-telemetry/data/cli-flag-verify.schema.json",
"title": "cli-flag-verify telemetry data",
"description": "Per-hook `data` payload for the cli-flag-verify advisory hook. Discovered from the envelope `hook` value \"cli-flag-verify\". Evolves additive-only.",
"type": "object",
"required": ["tool", "file", "findings"],
"additionalProperties": true,
"properties": {
"tool": {
"type": "string",
"description": "Claude Code tool that triggered the hook. May be an empty string — this advisory hook resolves only the file path, not the tool name."
},
"file": {
"type": "string",
"description": "Path of the scanned file, relative to the consuming repo root when resolvable."
},
"findings": {
"type": "array",
"items": { "type": "string" },
"description": "Unknown (likely-hallucinated) CLI invocations, each `<bin> [<subcmd>...] <flag>` (public CLI identifiers, not secret). Empty array when every scanned flag verified (status ok)."
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://raw.githubusercontent.com/melodic-software/claude-code-plugins/main/docs/conventions/hook-telemetry/data/hardcoded-path-check.schema.json",
"title": "hardcoded-path-check telemetry data",
"description": "Per-hook `data` payload for the hardcoded-path-check guard. Discovered from the envelope `hook` value \"hardcoded-path-check\". Evolves additive-only. Carries NO matched path — only category labels.",
"type": "object",
"required": ["tool", "file", "violations"],
"additionalProperties": true,
"properties": {
"tool": {
"type": "string",
"description": "Claude Code tool that triggered the hook (Write, Edit, or NotebookEdit)."
},
"file": {
"type": "string",
"description": "Path of the write target, relative to the consuming repo root when resolvable."
},
"violations": {
"type": "array",
"items": { "type": "string" },
"description": "Path-category labels detected in the new content (e.g. \"Linux user path detected\", \"Windows user path detected\"), one per category. NEVER the matched machine-specific path. Empty array on a clean scan (status ok)."
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://raw.githubusercontent.com/melodic-software/claude-code-plugins/main/docs/conventions/hook-telemetry/data/secret-pattern-detection.schema.json",
"title": "secret-pattern-detection telemetry data",
"description": "Per-hook `data` payload for the secret-pattern-detection guard. Discovered from the envelope `hook` value \"secret-pattern-detection\". Evolves additive-only. Carries NO secret material — only category labels.",
"type": "object",
"required": ["tool", "file", "violations"],
"additionalProperties": true,
"properties": {
"tool": {
"type": "string",
"description": "Claude Code tool that triggered the hook (Write, Edit, or NotebookEdit)."
},
"file": {
"type": "string",
"description": "Path of the write target, relative to the consuming repo root when resolvable."
},
"violations": {
"type": "array",
"items": { "type": "string" },
"description": "Secret-category labels detected in the new content (e.g. \"AWS Access Key\", \"GitHub PAT\"), one per matched pattern. NEVER the secret value or the matched line. Empty array on a clean scan (status ok)."
}
}
}
13 changes: 13 additions & 0 deletions docs/conventions/hook-telemetry/examples/block-no-verify.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"schema_version": "1.0",
"timestamp": "2026-07-10T14:26:55Z",
"hook": "block-no-verify",
"hook_event": "PreToolUse",
"status": "blocked",
"duration_ms": 9,
"data": {
"tool": "Bash",
"subject": "Bash:git",
"form": "no-verify"
}
}
13 changes: 13 additions & 0 deletions docs/conventions/hook-telemetry/examples/cli-flag-verify.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"schema_version": "1.0",
"timestamp": "2026-07-10T14:25:12Z",
"hook": "cli-flag-verify",
"hook_event": "PostToolUse",
"status": "ok",
"duration_ms": 512,
"data": {
"tool": "",
"file": "docs/setup.md",
"findings": ["claude --max-turns"]
}
}
13 changes: 13 additions & 0 deletions docs/conventions/hook-telemetry/examples/hardcoded-path-check.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"schema_version": "1.0",
"timestamp": "2026-07-10T14:23:41Z",
"hook": "hardcoded-path-check",
"hook_event": "PreToolUse",
"status": "blocked",
"duration_ms": 74,
"data": {
"tool": "Edit",
"file": "scripts/deploy.sh",
"violations": ["Linux user path detected"]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"schema_version": "1.0",
"timestamp": "2026-07-10T14:22:07Z",
"hook": "secret-pattern-detection",
"hook_event": "PreToolUse",
"status": "blocked",
"duration_ms": 260,
"data": {
"tool": "Write",
"file": "src/config.env",
"violations": ["AWS Access Key", "GitHub PAT"]
}
}
11 changes: 11 additions & 0 deletions plugins/guardrails/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "guardrails",
"version": "0.1.0",
"description": "Four PreToolUse safety guards that block secret/credential writes, hardcoded machine-specific paths, git hook-bypass attempts, and (advisory) hallucinated CLI flags — each independently toggleable.",
"author": {
"name": "Melodic Software",
"email": "info@melodicsoftware.com"
},
"keywords": ["guard", "security", "secrets", "hooks", "pretooluse", "git", "hardcoded-paths", "cli-flags"]
}
109 changes: 109 additions & 0 deletions plugins/guardrails/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# guardrails

A Claude Code plugin bundling four **PreToolUse 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.

## The guards

| Guard | Event / matcher | Behavior | What it catches |
|-------|-----------------|----------|-----------------|
| **secret-pattern-detection** | PreToolUse · Write \| Edit \| NotebookEdit | **Blocks** (exit 2) | High-confidence secret/credential patterns (AWS/GitHub/GitLab/Slack/Stripe/OpenAI keys, PEM private keys) in new file content. |
| **hardcoded-path-check** | PreToolUse · Write \| Edit \| NotebookEdit | **Blocks** (exit 2) | Hardcoded machine-specific paths — Windows drive-letter homes, macOS/Linux user homes, machine-specific repo checkout roots. |
| **block-no-verify** | PreToolUse · Bash | **Blocks** (exit 2) | Git hook-bypass attempts on `git commit` / `git push`: `--no-verify` / `-n`, `core.hooksPath=` assignment, and `LEFTHOOK=0` / `LEFTHOOK_*=false` env-var prefixes (including inside compound `cd … && …` commands). |
| **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. |

The three blocking guards feed their stderr message back to Claude as
actionable fix guidance. The advisory guard surfaces its findings the same way
but always allows the edit.

### Scope notes

- **Hook-manager coverage.** `block-no-verify` recognizes the **lefthook**
env-var disable prefix (`LEFTHOOK=0` / `LEFTHOOK_*=false`). Other managers'
disable env vars (husky, pre-commit, …) are **not** matched — but the
manager-agnostic `--no-verify` / `-n` and `core.hooksPath=` checks catch those
bypasses regardless of which manager runs the hooks.
- **Argv-grammar-faithful matching (and its residual).** `block-no-verify`
parses the command the way the shell builds argv — segmenting on unquoted
operators and tokenizing each segment honoring `'…'`, `"…"`, `$'…'` (ANSI-C),
and backslash escapes. It detects literal `git commit`/`git push`
`--no-verify` / `-n` / `core.hooksPath=` across quoting, escaping, wrappers
(`env -i git …`, `nice git …`, `sudo -u x git …`), and git global options
(`git -C <dir> commit …`). A `--no-verify` inside a quoted `-m` value stays a
message, not a flag. It does **not** evaluate shell variable / command
substitution (`$VAR`, `$(…)`, `$IFS`) — a determined author can construct an
expansion-based bypass. **This is a friction guard against accidental/casual
bypass, not a sandbox.** (A command longer than 16 KB is not parsed and is
blocked fail-closed.)

## Per-hook kill switches

Each guard is toggled by its own env var (default **on**; set to `false` for a
clean no-op). This per-hook control is the bundle's core contract — disable one
guard without touching the others.

| Guard | Kill switch |
|-------|-------------|
| secret-pattern-detection | `HOOK_SECRET_PATTERN_DETECTION_ENABLED` |
| hardcoded-path-check | `HOOK_HARDCODED_PATH_CHECK_ENABLED` |
| block-no-verify | `HOOK_BLOCK_NO_VERIFY_ENABLED` |
| cli-flag-verify | `HOOK_CLI_FLAG_VERIFY_ENABLED` |

Set them in your settings `env` block:

```json
{ "env": { "HOOK_HARDCODED_PATH_CHECK_ENABLED": "false" } }
```

## Consumer seams

The guards scope and tune themselves to **your** repository — they ship no
repo-specific policy of their own:

- **Project scoping.** `secret-pattern-detection` and `hardcoded-path-check`
only police files under `$CLAUDE_PROJECT_DIR`; a write into a sibling repo is
that repo's concern. Secret scanning fails **closed** — if the project root
cannot be resolved, it scans anyway.
- **Gitignore is the allowlist.** `hardcoded-path-check` skips any file
`git check-ignore` matches against your `$CLAUDE_PROJECT_DIR` — put
machine-local files (`settings.local.json`, `.venv/`, …) in your
`.gitignore` and they are exempt automatically.
- **Secret allowlist.** A generic built-in allowlist exempts dependency caches
(`.venv/`, `node_modules/`), `.env.example` / `.sample` / `.template`
placeholders, `tests/fixtures` / `tests/testdata` trees, `settings.local.json`,
`CLAUDE.local.md`, and hook scripts.
- **CLI-flag tuning.** `cli-flag-verify` checks a default binary set
(`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`.

## Telemetry (opt-in)

Every guard emits one structured [hook-telemetry](../../docs/conventions/hook-telemetry/README.md)
envelope per run to whatever `HOOK_TELEMETRY_SINK` names — carrying `status`
(`blocked` on a guard block, `ok` otherwise), `duration_ms`, and a privacy-safe
`data` payload (category **labels** only — never a secret value, matched path,
or full command). Unset `HOOK_TELEMETRY_SINK` → no-op; the guards behave exactly
as before.

## Requirements

- **bash 5.0+** and **jq** — the guards' runtime. Without **jq**, each guard
fails **open** (disabled) and prints a one-line stderr notice — never a silent
disable.
- On Windows, **Git Bash** (the hooks run via Git Bash's bash).
- `cli-flag-verify` runs `<bin> --help` for the binaries it scans; findings
require those binaries on PATH (missing binaries are skipped, never flagged).

## Install

```shell
/plugin marketplace add melodic-software/claude-code-plugins
/plugin install guardrails@melodic-software
```

## License

[MIT](../../LICENSE).
Loading
Loading