diff --git a/plugins/claude-code/.claude-plugin/plugin.json b/plugins/claude-code/.claude-plugin/plugin.json new file mode 100644 index 0000000..79d16cc --- /dev/null +++ b/plugins/claude-code/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "foxguard", + "description": "Fast local security scanning in Claude Code: auto-scan edited files, slash commands for full scans, diff scans, post-quantum audits, and TUI triage. Powered by the foxguard Rust scanner — 170+ rules across 10 languages with cross-file taint tracking.", + "version": "0.1.0", + "author": { + "name": "PwnKit Labs", + "url": "https://foxguard.dev" + }, + "homepage": "https://foxguard.dev", + "repository": "https://github.com/PwnKit-Labs/foxguard", + "license": "MIT" +} diff --git a/plugins/claude-code/README.md b/plugins/claude-code/README.md new file mode 100644 index 0000000..1d8cdbe --- /dev/null +++ b/plugins/claude-code/README.md @@ -0,0 +1,106 @@ +# foxguard for Claude Code + +Live security scanning inside [Claude Code](https://code.claude.com). Every file Claude writes or edits is auto-scanned by [foxguard](https://foxguard.dev) — findings are fed straight back so Claude self-corrects before bad patterns land. + +## What you get + +- **PostToolUse auto-scan** — every `Write` / `Edit` / `MultiEdit` triggers `foxguard --format json` on the changed file. Medium+ findings are surfaced to Claude on stderr; clean files are silent. +- **SessionStart preamble** — Claude starts each session with foxguard's secure-coding defaults already in context. +- **Slash commands** for on-demand scans: + - `/foxguard:setup` — verify install, set severity threshold + - `/foxguard:scan [path]` — full scan, grouped and triaged by severity + - `/foxguard:diff-scan [base]` — only what this branch introduces vs `main` + - `/foxguard:pq-audit [path]` — post-quantum readiness with CNSA 2.0 deadlines + - `/foxguard:secrets [path]` — hardcoded secrets, tokens, private keys + - `/foxguard:triage [args]` — instructions for the interactive TUI +- **`secure-coding` skill** — model-invoked remediation guidance for command exec, SQL, path traversal, SSRF, secrets, randomness, crypto, and deserialization. + +## Install + +### 1. Install foxguard + +Pick whichever fits: + +```sh +# Prebuilt binary — fastest +curl -fsSL https://foxguard.dev/install.sh | sh + +# Homebrew +brew install pwnkit-labs/foxguard/foxguard + +# npm (global or zero-install) +npm i -g foxguard # or just: npx foxguard + +# cargo +cargo install foxguard +``` + +Verify with `foxguard --version`. + +### 2. Install the plugin + +Until the plugin is published to a marketplace, load it directly from this repo: + +```sh +claude --plugin-dir /path/to/foxguard/plugins/claude-code +``` + +Or add to your settings to load it permanently. Once installed, run `/foxguard:setup` to confirm it's wired up. + +## Configuration + +Environment variables: + +| Var | Default | Purpose | +| :-- | :-- | :-- | +| `FOXGUARD_HOOK_SEVERITY` | `medium` | Minimum severity for the auto-scan. One of `low|medium|high|critical`. | + +Set in your shell profile or in Claude Code's environment to tune noise. + +## How the auto-scan looks + +After Claude edits a file, if foxguard finds an issue, Claude sees something like: + +``` +foxguard found 1 issue(s) in src/auth.py (severity >= medium): + + [CRITICAL] py/no-command-injection at line 42 + os.system() called with dynamic argument — risk of command injection + CWE-78 + > os.system("ls " + user_input) + +Fix these before continuing. Run `/foxguard:scan` for the full repo or `/foxguard:triage` for the interactive TUI. +``` + +Claude is expected to fix the finding (or explain why it's a false positive) before moving on. + +## Layout + +``` +plugins/claude-code/ +├── .claude-plugin/plugin.json # manifest +├── hooks/hooks.json # PostToolUse + SessionStart +├── scripts/ +│ ├── scan-edited-file.sh # the PostToolUse scanner +│ └── secure-defaults.txt # SessionStart preamble +├── skills/ +│ ├── setup/SKILL.md # /foxguard:setup +│ ├── scan/SKILL.md # /foxguard:scan +│ ├── diff-scan/SKILL.md # /foxguard:diff-scan +│ ├── pq-audit/SKILL.md # /foxguard:pq-audit +│ ├── secrets/SKILL.md # /foxguard:secrets +│ ├── triage/SKILL.md # /foxguard:triage +│ └── secure-coding/SKILL.md # model-invoked remediation guidance +└── README.md +``` + +## Notes + +- The hook intentionally never blocks Claude on its own machinery: missing binary, parse errors, or unreadable inputs all exit `0`. Only real findings exit `2`. +- The hook calls `foxguard` from `PATH` first, then falls back to `npx --yes foxguard`. If neither is available it stays silent — run `/foxguard:setup` to fix that. +- `--severity medium` is the default cutoff. Drop to `low` for stricter coverage; raise to `high` for noisier projects. +- foxguard's exit codes: `0` clean, `1` findings, `2` error. The hook checks for findings via the JSON, not the exit code, so a piped error doesn't trigger a false alarm. + +## License + +MIT — same as foxguard. diff --git a/plugins/claude-code/hooks/hooks.json b/plugins/claude-code/hooks/hooks.json new file mode 100644 index 0000000..3cf28c8 --- /dev/null +++ b/plugins/claude-code/hooks/hooks.json @@ -0,0 +1,25 @@ +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Write|Edit|MultiEdit|NotebookEdit", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/scripts/scan-edited-file.sh" + } + ] + } + ], + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "cat \"${CLAUDE_PLUGIN_ROOT}/scripts/secure-defaults.txt\"" + } + ] + } + ] + } +} diff --git a/plugins/claude-code/scripts/scan-edited-file.sh b/plugins/claude-code/scripts/scan-edited-file.sh new file mode 100755 index 0000000..f0d191b --- /dev/null +++ b/plugins/claude-code/scripts/scan-edited-file.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# PostToolUse hook: scan the file Claude just wrote/edited and feed findings back. +# +# Reads the Claude Code hook input JSON from stdin, extracts tool_input.file_path, +# runs `foxguard --format json` on it, and emits a compact summary on stderr with +# exit 2 when medium+ findings exist so Claude treats it as actionable feedback. +# Stays silent (exit 0) on clean files, missing tools, or unreadable inputs — a +# scanner hook should never block Claude on its own machinery. + +set -uo pipefail + +input=$(cat) + +file_path=$(printf '%s' "$input" | jq -r '.tool_input.file_path // .tool_input.path // empty' 2>/dev/null) + +[ -z "$file_path" ] && exit 0 +[ ! -f "$file_path" ] && exit 0 + +# Resolve foxguard binary: prefer PATH, fall back to npx. +if command -v foxguard >/dev/null 2>&1; then + fg=(foxguard) +elif command -v npx >/dev/null 2>&1; then + fg=(npx --yes foxguard) +else + exit 0 +fi + +min_severity="${FOXGUARD_HOOK_SEVERITY:-medium}" + +findings=$("${fg[@]}" --format json --severity "$min_severity" "$file_path" 2>/dev/null) || true + +[ -z "$findings" ] && exit 0 +[ "$findings" = "[]" ] && exit 0 + +count=$(printf '%s' "$findings" | jq 'length' 2>/dev/null) +[ -z "$count" ] || [ "$count" = "0" ] && exit 0 + +{ + printf 'foxguard found %s issue(s) in %s (severity >= %s):\n\n' "$count" "$file_path" "$min_severity" + printf '%s' "$findings" | jq -r '.[] | " [\(.severity | ascii_upcase)] \(.rule_id) at line \(.line)\n \(.description)\n \(.cwe // "")\n > \(.snippet)\n"' + printf '\nFix these before continuing. Run `/foxguard:scan` for the full repo or `/foxguard:triage` for the interactive TUI.\n' +} >&2 + +exit 2 diff --git a/plugins/claude-code/scripts/secure-defaults.txt b/plugins/claude-code/scripts/secure-defaults.txt new file mode 100644 index 0000000..e684666 --- /dev/null +++ b/plugins/claude-code/scripts/secure-defaults.txt @@ -0,0 +1,10 @@ +foxguard is active. Apply these secure-coding defaults when writing or editing code: + +- Never concatenate user input into shell, SQL, or path strings. Use parameterized APIs (`subprocess` arg lists, prepared statements, `pathlib.Path` joins). +- Never hardcode secrets, tokens, or keys. Read from env vars or a secrets manager. Treat any string that looks like a credential as a finding. +- Validate and canonicalize paths before file ops; reject paths that escape the intended root after resolution. +- For HTTP fetchers, validate the destination host against an allowlist or block private IP ranges to avoid SSRF. +- Prefer post-quantum-ready primitives where the project already supports them; flag legacy RSA / ECDSA / ECDH usage in security-sensitive paths. +- Use `crypto.randomBytes` / `secrets` / `OsRng` for security-sensitive randomness — never `Math.random`, `rand()`, or `random.random()`. + +After every Write/Edit, foxguard scans the touched file. If findings come back, fix them before continuing rather than working around them. Use `/foxguard:scan` for a full repo scan, `/foxguard:diff-scan` for changed-only review, `/foxguard:pq-audit` for post-quantum readiness, and `/foxguard:triage` for the interactive TUI. diff --git a/plugins/claude-code/skills/diff-scan/SKILL.md b/plugins/claude-code/skills/diff-scan/SKILL.md new file mode 100644 index 0000000..f7ac640 --- /dev/null +++ b/plugins/claude-code/skills/diff-scan/SKILL.md @@ -0,0 +1,12 @@ +--- +description: Scan only changes vs a base branch — surfaces what this branch introduced +disable-model-invocation: true +--- + +Run a diff scan to show only findings introduced on the current branch. + +1. Determine the base branch. If the user passed one as `$ARGUMENTS`, use it. Otherwise default to `main`, falling back to `master` if `main` doesn't exist (`git rev-parse --verify main`). +2. Run via Bash: `foxguard diff "$BASE" . --format json --severity medium`. +3. Parse the JSON output the same way as `/foxguard:scan` — group by file and severity, show rule id, line, description, and snippet. +4. Specifically frame the report as "what this branch adds." If the count is zero, say so plainly — this branch introduces no new findings vs `$BASE`. +5. For critical/high findings, propose fixes. Make clear that pre-existing findings on `$BASE` are not shown — those need a full `/foxguard:scan`. diff --git a/plugins/claude-code/skills/pq-audit/SKILL.md b/plugins/claude-code/skills/pq-audit/SKILL.md new file mode 100644 index 0000000..5c66367 --- /dev/null +++ b/plugins/claude-code/skills/pq-audit/SKILL.md @@ -0,0 +1,15 @@ +--- +description: Post-quantum cryptography audit — find legacy RSA/ECDSA/ECDH usage with CNSA 2.0 deadlines +disable-model-invocation: true +--- + +Run a post-quantum readiness audit. + +1. Take an optional path from `$ARGUMENTS`, default to `.`. +2. Run via Bash: `foxguard pqc "$PATH_OR_DOT" --format json --severity medium`. +3. Parse the JSON. Each finding may include a `cnsa2Deadline` field — surface that prominently. CNSA 2.0 mandates deprecation of classical asymmetric crypto in security-sensitive paths by specific dates; findings nearer those deadlines are higher priority. +4. Report: + - Total PQ-vulnerable call sites + - Breakdown by primitive (RSA, ECDSA, ECDH, etc.) — derive from `rule_id` or `description` + - For each finding: `file:line`, the legacy primitive, the recommended PQ replacement (e.g., ML-KEM, ML-DSA, hybrid suites) +5. Note that classical primitives may still be acceptable in non-security paths (test fixtures, signed-binary verification of trusted releases). Ask the user about context before recommending wholesale rewrites. diff --git a/plugins/claude-code/skills/scan/SKILL.md b/plugins/claude-code/skills/scan/SKILL.md new file mode 100644 index 0000000..0b0ec0c --- /dev/null +++ b/plugins/claude-code/skills/scan/SKILL.md @@ -0,0 +1,15 @@ +--- +description: Run a full foxguard security scan on the repository or a specific path +disable-model-invocation: true +--- + +Run a full foxguard scan and walk the user through any findings. + +1. If the user passed a path as `$ARGUMENTS`, scan that. Otherwise scan `.`. +2. Run via Bash: `foxguard --format json --severity medium "$PATH_OR_DOT"`. Capture stdout — exit 1 just means findings were detected, that's expected. +3. Parse the JSON array. Group findings by `file`, then by `severity` (critical → high → medium). For each finding show: `[SEVERITY] rule_id` at `file:line` with the description and the `snippet`. +4. Summarize at the top: total count, breakdown by severity, top 3 files by finding count. +5. For each critical/high finding, propose a concrete fix the user can accept. Don't apply edits until the user confirms. +6. If the JSON output is large (>50 findings), suggest `/foxguard:triage` for the interactive TUI instead of scrolling text. + +If the scan errors (exit 2), report the error verbatim and suggest `/foxguard:setup`. diff --git a/plugins/claude-code/skills/secrets/SKILL.md b/plugins/claude-code/skills/secrets/SKILL.md new file mode 100644 index 0000000..da5237c --- /dev/null +++ b/plugins/claude-code/skills/secrets/SKILL.md @@ -0,0 +1,16 @@ +--- +description: Scan for hardcoded secrets, API tokens, and private keys +disable-model-invocation: true +--- + +Run a secrets-focused scan. + +1. Take an optional path from `$ARGUMENTS`, default to `.`. +2. Run via Bash: `foxguard secrets "$PATH_OR_DOT" --format json`. +3. Parse the JSON. Secret values are redacted by foxguard — do not try to reconstruct them. +4. Report each finding: `file:line`, the `rule_id` (e.g., `secret/aws-access-key`, `secret/github-pat`), and the risk class (cloud key, VCS token, payment, generic high-entropy, private key, etc.). +5. For each finding propose the right remediation: + - Cloud / SaaS tokens: rotate immediately at the issuer, then move to env vars or a secrets manager. + - Private keys: regenerate, never just delete from history without rotation. + - High-entropy strings that are NOT secrets: add to a baseline via `foxguard secrets --write-baseline .foxguard-secrets-baseline.json` — explain this is a suppression, not a fix. +6. Remind the user that removing a leaked secret from the working tree does NOT remove it from git history. If the file was ever committed, the secret must be considered compromised. diff --git a/plugins/claude-code/skills/secure-coding/SKILL.md b/plugins/claude-code/skills/secure-coding/SKILL.md new file mode 100644 index 0000000..63be04e --- /dev/null +++ b/plugins/claude-code/skills/secure-coding/SKILL.md @@ -0,0 +1,52 @@ +--- +description: Apply foxguard's secure-coding patterns when writing or fixing security-sensitive code. Use proactively when handling user input, shelling out, building SQL, doing file I/O, fetching URLs, generating randomness, or touching crypto. +--- + +Use this guidance when writing or fixing code that touches any of these surfaces. Match the language patterns to what foxguard's rules look for so the auto-scan stays clean. + +## Command execution + +- Never pass concatenated strings to a shell. Use argument lists. + - Python: `subprocess.run(["git", "log", branch], check=True)` — never `subprocess.run(f"git log {branch}", shell=True)`. + - Node: `execFile("git", ["log", branch])` — never `exec(`git log ${branch}`)`. + - Go: `exec.Command("git", "log", branch)` — never `exec.Command("sh", "-c", "git log "+branch)`. +- If a shell is genuinely required, validate the input against a strict allowlist regex first. + +## SQL + +- Use parameterized queries everywhere. `cursor.execute("SELECT * FROM u WHERE id = ?", (uid,))` — not f-strings or `%`. +- ORMs are fine; raw `query.format(...)` / `+` concat is not. + +## Path traversal + +- Resolve and bound paths: `Path(root).resolve() / Path(name).name` — strip directory components from untrusted names. +- Reject inputs that contain `..`, absolute paths, or null bytes before joining. + +## SSRF + +- For outbound HTTP from user-supplied URLs: parse the URL, resolve the host, and reject private/loopback/link-local ranges (`10.0.0.0/8`, `127.0.0.0/8`, `169.254.0.0/16`, `172.16.0.0/12`, `192.168.0.0/16`, `::1`, `fc00::/7`, `fe80::/10`) and metadata endpoints (`169.254.169.254`). +- Prefer an allowlist over a denylist when the destinations are known. + +## Secrets + +- Read from env / secrets manager, never from string literals. `os.getenv("API_TOKEN")` — not `API_TOKEN = "sk-..."`. +- Even in tests, prefer fixtures from env or a vault. High-entropy strings in code will be flagged. +- Never log secrets. Redact before logging. + +## Randomness + +- Security-sensitive: `secrets.token_urlsafe`, `crypto.randomBytes`, `OsRng`, `crypto/rand`. Never `random.random()`, `Math.random()`, `rand()`, `math/rand`. +- Non-security (jitter, sampling): the fast PRNGs are fine. + +## Crypto + +- New code: AES-GCM or ChaCha20-Poly1305 for symmetric; Ed25519 for signatures; X25519 for key agreement (until PQ rollout). Never DES, 3DES, RC4, MD5, SHA-1. +- Asymmetric crypto in long-lived security paths: prefer hybrid suites (X25519+ML-KEM) where the toolchain supports it. Use `/foxguard:pq-audit` to find legacy usage. +- Password hashing: argon2id or bcrypt. Never plain SHA-2 or unsalted hashes. + +## Deserialization + +- Never `pickle.load`, `yaml.load` (use `safe_load`), `Marshal.load`, or `unserialize` on untrusted input. +- For JSON, deserialize into a typed schema (pydantic, zod, serde) — don't trust the shape. + +When you finish a fix, the PostToolUse hook will re-scan the file and confirm it's clean. If a finding persists, read the rule's description carefully — the remediation is usually one of the patterns above. diff --git a/plugins/claude-code/skills/setup/SKILL.md b/plugins/claude-code/skills/setup/SKILL.md new file mode 100644 index 0000000..4e4aedd --- /dev/null +++ b/plugins/claude-code/skills/setup/SKILL.md @@ -0,0 +1,19 @@ +--- +description: Verify foxguard is installed and ready for the Claude Code plugin +disable-model-invocation: true +--- + +Walk the user through getting foxguard installed and the plugin working: + +1. Run `foxguard --version` via the Bash tool. If it succeeds, report the version and tell the user the plugin is ready — every Write/Edit will now be auto-scanned. +2. If `foxguard` is not on PATH, offer the install options in this order: + - **Prebuilt binary (fastest)**: `curl -fsSL https://foxguard.dev/install.sh | sh` + - **Homebrew** (macOS): `brew install pwnkit-labs/foxguard/foxguard` + - **npm**: `npm i -g foxguard` or zero-install via `npx foxguard` + - **cargo**: `cargo install foxguard` + Ask which the user prefers; do NOT install without confirmation. +3. Recommend the user run `foxguard init` inside their repo to add a pre-commit hook so foxguard also catches issues outside Claude Code sessions. +4. Mention the env vars they can tune: + - `FOXGUARD_HOOK_SEVERITY` — minimum severity for auto-scan (default `medium`; values: `low|medium|high|critical`) + +Finish with a one-line confirmation of which version is active and which severity threshold the auto-scan is using. diff --git a/plugins/claude-code/skills/triage/SKILL.md b/plugins/claude-code/skills/triage/SKILL.md new file mode 100644 index 0000000..3a51484 --- /dev/null +++ b/plugins/claude-code/skills/triage/SKILL.md @@ -0,0 +1,16 @@ +--- +description: Open the foxguard interactive TUI for triaging findings +disable-model-invocation: true +--- + +Launch the foxguard TUI for interactive triage. + +1. Tell the user the TUI is an interactive terminal app and Claude cannot drive it — they need to interact with it directly in their terminal. +2. Suggest they run one of these in their own terminal (NOT via your Bash tool, which can't render a TUI): + - `foxguard tui` — full repo + - `foxguard tui --changed` — staged/unstaged files only + - `foxguard tui --diff main` — diff vs main + - `foxguard tui --secrets` — secrets mode + - `foxguard tui --explain` — show dataflow traces in the detail pane +3. Pass through any path or flags from `$ARGUMENTS`. +4. After they finish triaging, offer to follow up with `/foxguard:scan` to re-verify the codebase is clean.