Back-Road-Creative/rigscore

GitHub: Back-Road-Creative/rigscore

rigscore 是一款本地离线运行的 AI 开发环境配置健康度检查工具,用于评估 AI Agent 配置的安全性与一致性。

Stars: 0 | Forks: 0

# rigscore **A configuration hygiene checker for your AI development environment.** One command. 28 checks — 13 scored plus 15 advisory. A hygiene score out of 100. Know where you stand before something breaks. npx github:Back-Road-Creative/rigscore ╭────────────────────────────────────────╮ │ │ │ rigscore v2.1.0 │ │ AI Dev Environment Hygiene Check │ │ │ ╰────────────────────────────────────────╯ Scanning /home/user/my-project ... ✓ CLAUDE.md governance.......... [pattern] 10/10 ✓ Claude settings safety........ [mechanical] 8/8 ✓ Cross-config coherence........ [keyword] 14/14 ✓ Credential storage hygiene.... [mechanical] 6/6 ↷ Deep source secrets........... [pattern] N/A ✓ Docker security............... [mechanical] 6/6 ✓ Secret exposure............... [mechanical] 8/8 ✓ Git hooks..................... [mechanical] 2/2 ✓ Infrastructure security....... [mechanical] 5/6 ⚠ Instruction effectiveness..... [keyword] advisory ✗ MCP server configuration...... [mechanical] 0/14 ✓ Permissions hygiene........... [mechanical] 4/4 ↷ Site security................. [mechanical] N/A ✓ Skill file safety............. [pattern] 10/10 ✓ Unicode steganography......... [pattern] 4/4 ↷ Windows/WSL security.......... [mechanical] N/A ✓ Skill ↔ governance coherence.. [keyword] advisory ✓ Workflow maturity............. [keyword] advisory ↷ Network exposure.............. [mechanical] N/A ╭────────────────────────────────────────╮ │ │ │ HYGIENE SCORE: 78/100 │ │ Grade: B │ │ Posture: Standard │ │ Practice: 82/100 (B) │ │ │ ╰────────────────────────────────────────╯ mechanical = deterministic config check · pattern = regex/structural · keyword = presence detection CRITICAL (1) ✗ MCP server "filesystem" has broad filesystem access: / → Scope filesystem access to your project directory only. ## Why this exists rigscore scores AI-agent config hygiene and catches contradictions between what your governance file claims and what your actual configuration does — in one local command, no account, no API token, passable as a CI gate with a single `--fail-under` threshold. Most AI-agent security scanning today is either finding-stream static analysis or manual review. rigscore fills a narrower slot: a single hygiene score with an A–F grade, a cross-config **coherence** pass that compares governance claims to observed behavior (MCP scope, Docker privileges, approval gates), and a CI-gate exit code. It runs fully offline by default; `--online` is opt-in for site probes and MCP supply-chain verification. It's the thing you run before you stand up a SARIF pipeline or adopt an enterprise scanner — though it also ships offline MCP rug-pull pinning (`mcp-hash`/`mcp-pin`/`mcp-verify`), an in-scan CycloneDX 1.6 AI-BOM (`--cyclonedx`) covering the MCP grant surface, and a two-axis Security + Practice score most scanners don't. If your CLAUDE.md says "never access `/etc`" and your MCP config mounts `/`, rigscore tells you. **What rigscore checks:** MCP scope and supply chain, cross-config coherence, skill-file injection vectors, governance quality, Claude settings bypass combos, secret exposure in config files, container and devcontainer isolation, Unicode steganography, git hooks, file permissions, credential storage, and (advisory) instruction effectiveness, skill↔governance coherence, workflow maturity, Windows/WSL boundary, site security, network exposure, agent output schemas, documentation coverage, loop governance, spec goals, CI agent caps, agent memory hygiene, AI-use disclosure, and sandbox posture. Run it. See the score. Fix what's broken. ## What the grades mean Every check in rigscore's output carries an enforcement-grade label so you can see *how* each point was earned, not just how many. The label is display-only — it does not affect scoring. - **`[mechanical]`** — deterministic config inspection. Parses JSON/YAML/file state and compares to known-bad constants, file modes, or structural invariants. Cannot be gamed by wording. 15 of 28 checks. - **`[pattern]`** — regex or structural scan against file content. Secret signatures, Unicode codepoint classes, markdown structural patterns. Resistant to simple wording tricks but evadable by novel obfuscation. 7 of 28 checks. - **`[keyword]`** — presence of words or phrases in governance prose. These checks pass if the right words appear, which means a CLAUDE.md can keyword-stuff its way to green while substantively reversing intent. 6 of 28 checks — see [`THREAT-MODEL.md`](THREAT-MODEL.md) for the gameability gap and [`test/keyword-gaming.test.js`](test/keyword-gaming.test.js) for concrete bypasses. Reproduce the grade breakdown (grades are static per check, so this is config-independent): node bin/rigscore.js --json . | jq -r '.results|group_by(.enforcementGrade)[]|"\(.[0].enforcementGrade): \(length)"' The 13-scored / 15-advisory split comes from the default weights in `src/constants.js`. Scan a directory with no `.rigscorerc.json` to see it un-skewed — rigscore's own rc file disables four checks, which zeroes their weight in a self-scan: node bin/rigscore.js --json "$(mktemp -d)" \ | jq '{total:(.results|length), scored:([.results[]|select(.weight>0)]|length), advisory:([.results[]|select(.weight==0)]|length)}' ## Install and run No setup. No accounts. No data leaves your machine. # Run on the current directory npx github:Back-Road-Creative/rigscore # Run on a specific project npx github:Back-Road-Creative/rigscore /path/to/project # Output as JSON (for CI integration) npx github:Back-Road-Creative/rigscore --json # SARIF output (for GitHub Advanced Security) npx github:Back-Road-Creative/rigscore --sarif # CI mode (SARIF + no color + no CTA) npx github:Back-Road-Creative/rigscore --ci --fail-under 80 # Generate a README badge npx github:Back-Road-Creative/rigscore --badge # Scan a monorepo (recursive mode) npx github:Back-Road-Creative/rigscore . --recursive --depth 2 # Run a single check npx github:Back-Road-Creative/rigscore --check docker-security # Deep source secret scanning npx github:Back-Road-Creative/rigscore --deep # Online checks (site-security, MCP supply-chain verification) npx github:Back-Road-Creative/rigscore --online # Force refresh of the MCP registry cache (implies --online) npx github:Back-Road-Creative/rigscore --refresh-mcp-registry # Auto-fix safe issues (dry run) npx github:Back-Road-Creative/rigscore --fix # Apply auto-fixes npx github:Back-Road-Creative/rigscore --fix --yes # Apply auto-fixes AND install the starter packs that target your red checks npx github:Back-Road-Creative/rigscore --fix --yes --install-packs # Use a scoring profile npx github:Back-Road-Creative/rigscore --profile minimal # Watch mode — re-run on config changes npx github:Back-Road-Creative/rigscore --watch # Install a pre-commit hook npx github:Back-Road-Creative/rigscore --init-hook **Using the [pre-commit](https://pre-commit.com) framework?** Add rigscore to your `.pre-commit-config.yaml` (it's backed by `.pre-commit-hooks.yaml` at this repo root): repos: - repo: https://github.com/Back-Road-Creative/rigscore rev: v2.1.0 hooks: - id: rigscore # args: [--fail-under=80, --ci] # optional: tune the gate This is **framework-managed** — pre-commit clones and installs rigscore itself (`language: node`, no npm publish needed) — as opposed to `rigscore --init-hook` above, a **native git-hook installer** that writes a pinned `npx` line straight into your repo's `.git/hooks/pre-commit`. ## Platform notes | Platform | Status | Notes | |---|---|---| | **macOS** | Supported | CI runs `macos-latest`. Homebrew Node is fine. `infrastructure-security` returns N/A (Linux-only). | | **Linux** | Supported | Native. All checks apply, including `infrastructure-security`. | | **WSL2** | Supported | Treat as Linux. See WSL2 gotcha below. | | **Docker Desktop** | Supported | Pull the image, bind-mount the target dir. See [CI Integration](#ci-integration) and the `Dockerfile` in the repo. | | **Git Bash / MSYS2** | Unsupported | POSIX-only permission checks fail silently. Use WSL. | | **Windows native** | Out of scope | POSIX permission assumptions and shell-command calls mean behavior can't be made reliable. Use WSL2. | **WSL2 gotcha — NTFS-mounted paths.** When you scan a project under `/mnt/c/...` (Windows-owned NTFS), POSIX permission bits don't map cleanly. `permissions-hygiene` may emit spurious world-readable / mixed-UID findings on files owned by the Windows side. Workaround: keep projects inside the WSL filesystem (e.g., `~/projects/...`) and scan there. If you must scan a `/mnt/` path, suppress the affected finding via `--ignore permissions-hygiene/sensitive-file-world-readable` or via `.rigscorerc.json` `suppress:`. ## Distribution - **GitHub-only.** rigscore is distributed via `npx github:Back-Road-Creative/rigscore`. It is **not** published to npm. See `CLAUDE.md` for the full rationale — in short: npm publish was intentionally dropped in v0.8.0 (commit #62) to keep the supply-chain surface tight. The tool is the sort of thing you want to audit before running; pulling straight from GitHub makes the audit trail obvious and avoids a second supply-chain hop. - **Docker image (GHCR).** The Docker Publish workflow (`.github/workflows/docker-publish.yml`) builds and publishes `ghcr.io/back-road-creative/rigscore:` automatically on `v*.*.*` tag pushes. It is also callable via `workflow_dispatch` for manual and dry-run builds. Pull a published tag as `ghcr.io/back-road-creative/rigscore:`. - **GitHub Action.** `action.yml` at the repo root exposes rigscore as a composite GitHub Action. Reference it from a workflow as `uses: Back-Road-Creative/rigscore@v2.1.0`. The action **requires an exact `vX.Y.Z` tag** — floating refs (`@v1`, `@v2`, `@main`) are rejected at run time to prevent supply-chain drift, and no moving major tag is published. Copy-paste job: name: rigscore on: [push, pull_request] jobs: rigscore: runs-on: ubuntu-latest permissions: contents: read security-events: write # required for upload-sarif steps: - uses: actions/checkout@v4 - uses: Back-Road-Creative/rigscore@v2.1.0 # MUST be an exact vX.Y.Z tag — floating refs are rejected with: fail-under: '70' upload-sarif: true - **Cross-platform support.** CI runs against `ubuntu-latest` and `macos-latest` across Node `18.17`, `20`, and `22` (`.github/workflows/ci.yml`). WSL users get the Linux path. **Windows native is out of scope** — POSIX-only permission checks and shell-command assumptions make it a separate workstream, not a v1.0.0 deliverable. ## What it checks ### 1. MCP server configuration (14 points) {#mcp-permissions} [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) lets AI agents connect to external tools via servers. Each server exposes capabilities — filesystem access, API calls, database queries. The security risk is in the permissions. rigscore scans MCP configs across all major clients: Claude (`.mcp.json`, `.vscode/mcp.json`), Cursor (`~/.cursor/mcp.json`), Cline (`~/.cline/mcp_settings.json`), Continue (`~/.continue/config.json`), Windsurf (`~/.windsurf/mcp.json`), Zed (`~/.config/zed/settings.json`), Amp (`~/.amp/mcp.json`), Gemini CLI (`.gemini/settings.json`), opencode (`opencode.json`, servers under the `mcp` key), Claude Desktop (`~/.claude/claude_desktop_config.json`), Amazon Q Developer (`.amazonq/mcp.json`, `.amazonq/default.json`, `~/.aws/amazonq/`), Roo Code (`.roo/mcp.json`), Cody (`cody.mcpServers` in `.vscode/settings.json`), JetBrains Junie (`.junie/mcp/mcp.json`, `~/.junie/mcp/mcp.json`), Warp (`.warp/.mcp.json`, `~/.warp/.mcp.json`), Kiro (`.kiro/settings/mcp.json`, `~/.kiro/settings/mcp.json`), Qwen Code (`.qwen/settings.json`, `~/.qwen/settings.json`), Crush (`.crush.json`, `crush.json`, `~/.config/crush/crush.json` — servers under the `mcp` key), OpenClaw (`~/.openclaw/openclaw.json` — servers under the nested `mcp.servers` key), and Antigravity (`~/.gemini/antigravity/mcp_config.json`). **What rigscore looks for:** - Transport type: `stdio` (local, safer) vs. `sse` (network, riskier) - Wildcard environment passthrough (`env: {...process.env}`) — exposes all your env vars to the server - Filesystem scope: is the server limited to project directories, or does it have access to `/`? - Version pinning: are packages locked to specific versions, or using `@latest`? - Cross-client configuration drift: are the same servers configured differently across clients? - Typosquatting detection: is a package name suspiciously close to a known MCP server? **Supply chain risk:** An MCP server installed as `@latest` today could push a malicious update tomorrow. Version pinning prevents this. {#mcp-supply-chain} **MCP registry augmentation (`--online`):** when `--online` is passed, rigscore augments the hand-curated typosquat list with data from the official MCP registry at `https://registry.modelcontextprotocol.io/v0/servers`. The response is cached at `~/.cache/rigscore/mcp-registry.json` (XDG convention; overridable via `XDG_CACHE_HOME` **only when it resolves inside `$HOME`** — a value pointing outside your home directory is silently ignored and the cache falls back to `~/.cache`) with a 24-hour TTL. If the refetch fails after expiry, the stale cache is still used and an INFO finding surfaces the state. The registry only **augments** the hand-curated list — it never replaces it. Pass `--refresh-mcp-registry` to force a refetch (implies `--online`). **Air-gapped / offline pre-populate:** place a JSON file at `~/.cache/rigscore/mcp-registry.json` with the shape `{"fetchedAt": "", "data": { ... raw /v0/servers response ... }}`. rigscore will treat it like any other cache entry and respect the 24h TTL. An air-gapped host can refresh the file from a machine with network access on whatever cadence it chooses. **What to fix:** Scope filesystem servers to your project directory only. Remove wildcard env passthrough — pass only the specific variables each server needs. Pin all server packages to exact versions. Prefer `stdio` transport unless you specifically need network access. ### 2. Cross-config coherence (14 points) {#coherence-check} The coherence check is rigscore's second pass — it compares what your governance file *claims* against what your actual configuration *does*. This catches contradictions that no single check can see. **What rigscore looks for:** - Governance claims "no external network" but MCP uses network transport - Governance claims "path restrictions" but MCP has broad filesystem access - Governance claims "forbidden actions" but Docker is running privileged - MCP configuration drifts across AI clients without governance guidance - Governance claims anti-injection rules but skill files contain injection patterns - Compound risk: data exfiltration patterns combined with broad filesystem access - `settings.json` disables approval gates that CLAUDE.md requires **Compound risk penalty:** If the coherence check finds a CRITICAL-severity contradiction, 10 points are deducted from the overall score on top of the per-check penalty. This reflects the systemic nature of governance failures. ### 3. Skill file safety (10 points) {#skill-file-injection} Skill files (`.cursorrules`, `.windsurfrules`, `.continuerules`, `.roorules`, `.goosehints`, `.junie/guidelines.md`, `copilot-instructions.md`, `AGENTS.md`, `.aider.conf.yml`) tell AI agents how to behave. They're also a prompt injection vector — malicious instructions embedded in skill files can override agent behavior. **What rigscore looks for:** - Instruction override patterns ("ignore previous instructions", "disregard", "new system prompt") - Shell execution instructions embedded in skill files - External URL references (potential data exfiltration) - Base64 or encoded content (obfuscated payloads) - File permissions (writable by others?) **What to fix:** Audit all skill files for unexpected instructions. Lock file permissions so only you can modify them. Be cautious with skill files from untrusted sources — treat them like executable code, because that's effectively what they are. **Scope:** By default rigscore only scans project-level skill/command dirs (cwd) — `.claude/commands`, `.claude/skills`, and the per-client command dirs (`.opencode/commands`, `.gemini/commands`), all derived from the client registry. Home-level skills are user-global and not attributable to a project, so they do not deduct from the project score. Pass `--include-home-skills` to also scan every registered client's home dir (`~/.claude/skills`, `~/.claude/commands`, `~/.codex/prompts`, `~/.config/opencode/commands`, `~/.gemini/commands`); findings from home files are labeled with a `~/` prefix. ### 4. CLAUDE.md governance (10 points) {#why-claude-md-matters} Your CLAUDE.md file tells AI agents what they can and can't do. Without one, your agent operates with no explicit rules — it can access any file, run any command, and make any API call that its underlying permissions allow. rigscore recognizes governance files for all major AI coding clients: CLAUDE.md, `.cursorrules`, `.windsurfrules`, `.clinerules`, `.continuerules`, `.roorules`, `.goosehints`, `.junie/guidelines.md`, `QWEN.md`, `CRUSH.md`, `copilot-instructions.md`, `AGENTS.md`, and `.aider.conf.yml`. It also scans the modern **directory-form** rule sets by default — `.cursor/rules/*.mdc`, `.windsurf/rules/`, `.clinerules/` (directory), `.github/instructions/*.instructions.md`, `.amazonq/rules/*.md` (Amazon Q), and `.kiro/steering/*.md` (Kiro) — so a repo governed only by those is not mis-scored as ungoverned. **What rigscore looks for:** - Does a governance file exist in the project root? - Does it contain forbidden action rules with proper negation context? - Does it have human-in-the-loop approval gates? - Does it restrict file and directory access? - Does it restrict network and API access? - Does it include anti-injection instructions? - Is the governance file tracked in git (not ephemeral)? - Does it define TDD/pipeline lock, Definition of Done, and git workflow rules? **A good CLAUDE.md is not a wishlist** — it should define specific, enforceable boundaries. rigscore checks that your governance file documents key security dimensions; enforcement depends on your tooling (hooks, permissions, container isolation). {#claude-md-hardening} **Starter templates** for each AI client (Cursor, Cline, Continue, Windsurf, Aider) live in [`docs/examples/`](docs/examples/) — copy the one for your client and customize. **What to fix:** Create a governance file with explicit execution boundaries, forbidden actions, file access restrictions, and approval gates. Be specific — "don't access sensitive files" is too vague. List the exact directories and operations that are off-limits. ### 5. Claude settings safety (8 points) {#claude-settings} `.claude/settings.json` controls what Claude Code can do autonomously. Certain settings — individually or in combination — can eliminate human oversight entirely. **What rigscore looks for:** - `enableAllProjectMcpServers` — auto-trusts all MCP servers without per-project approval - `skip-permissions` — disables the permission gate for file and command operations - Hook configurations that shell out to arbitrary commands - Bypass combos: pairings of settings that together eliminate all security gates ### 6. Deep source secrets (8 points, `--deep`) {#deep-scanning} When enabled with `--deep`, rigscore recursively scans your source files for hardcoded secrets. This goes beyond the root config file scanning and checks `.js`, `.ts`, `.py`, `.go`, `.rb`, `.java`, `.yaml`, `.json`, `.toml`, `.sh`, and `.env.*` files. **What rigscore looks for:** - ~45 secret patterns: API keys from Anthropic, OpenAI (current `sk-proj-`/`sk-svcacct-` and legacy `sk-` keys), AWS (including STS tokens), GitHub (classic + OAuth + user/server/refresh tokens + fine-grained `github_pat_`), GitLab, Slack, Stripe, SendGrid, Twilio, Firebase/Google API keys, Google OAuth client secrets (`GOCSPX-`), DigitalOcean, Mailgun, npm, PyPI, Hugging Face, Shopify, Databricks, MongoDB, Vercel, Supabase, Cloudflare, Railway, PlanetScale, Neon, Linear, Replicate, Tavily, webhook signing secrets, AGE encryption keys, Datadog, 1Password CLI references, HashiCorp Vault, JFrog Artifactory, and Docker registry auth tokens. Patterns are anchored and length-bounded to keep false positives low; for git-history and entropy-based deep scans, pair rigscore with a git-history / entropy secret scanner. - Comment vs. hardcoded distinction (commented/example keys are `info`, real keys are `critical`) - Skips test files, node_modules, .git, vendor, dist, build directories **What to fix:** Move secrets to `.env` files or a secrets manager. Use environment variables in your application code. ### 7. Secret exposure (8 points) {#env-security} API keys, tokens, and credentials in the wrong places are the most common security failure in any codebase — and AI development makes it worse because agents read config files, skill files, and environment variables as part of their normal operation. **What rigscore looks for:** - `.env` files present but not in `.gitignore` - API key patterns in config files, governance files, and the `env` maps of committed MCP configs (skill files are covered by the skill-file safety check, not here) - `.env` file permissions (world-readable vs. user-only) - SOPS encryption detection **What to fix:** Add `.env` to `.gitignore` immediately. Set `.env` permissions to `600` (user read/write only). Never hardcode API keys in governance or config files. Use environment variables and pass them explicitly. ### 8. Credential storage hygiene (6 points) {#credential-storage} Checks where credentials actually live — env vars in the right files, committed secrets in the wrong ones. Broader pattern coverage than the secret exposure check. ### 9. Container security (6 points) {#docker-isolation} Containers provide isolation for AI agent workloads — but misconfigured containers can actually increase your attack surface instead of reducing it. rigscore scans **Docker Compose**, **Podman Compose**, **Kubernetes manifests**, and **devcontainer.json** configurations. Compose `include` directives are followed and analyzed. K8s manifests are scanned in the project root and common subdirectories (`k8s/`, `kubernetes/`, `manifests/`, `deploy/`), including multi-document YAML files. **What rigscore looks for:** - Docker socket (`/var/run/docker.sock`) mounted in containers — this is a container escape vector {#docker-socket-risk} - `privileged: true` — gives the container full host access - Volume/hostPath mounts to sensitive host directories — the exact-match set is `/`, `/etc`, `/root`, `/home` - Host network mode — bypasses container network isolation - Missing `user` directive (container runs as root) - Missing `cap_drop: [ALL]` (retains default Linux capabilities) - Missing `no-new-privileges` security option - Missing memory limits - K8s-specific: `hostPID`, `hostIPC`, `allowPrivilegeEscalation`, missing `runAsNonRoot` - Devcontainer: `--privileged` in runArgs, dangerous capability additions **What to fix:** Never mount the Docker socket unless absolutely necessary. Never run containers in privileged mode. Scope volume mounts to project directories only. Add `user`, `cap_drop: [ALL]`, and `no-new-privileges` to every service. Set memory limits. ### 10. Infrastructure security (6 points) {#infrastructure-security} Host-level controls that sit beneath your project: root-owned git hooks, a git wrapper that cannot be bypassed, a shell safety guard, and immutable governance directories. These are the backstop when per-project hooks or settings are missing or tampered with. This check is Linux-only — it returns N/A on macOS and Windows. **What rigscore looks for:** - A managed git hooks directory (default: the repository's own, resolved via `git rev-parse --git-path hooks`; point it at a root-owned system dir such as `/opt/git-hooks/` via `.rigscorerc.json` → `paths.hooksDir`) - That directory is owned by root - Required hooks present and executable: `pre-commit`, `pre-push`, `commit-msg` - A git safety wrapper at `/usr/local/bin/git` (configurable) that is root-owned and strips `--no-verify` to prevent hook bypass - A shell safety guard at `/etc/profile.d/safety-gates.sh` (configurable) that blocks dangerous patterns like `chmod 777` - Immutable flag (`chattr +i`) on any directories listed in `.rigscorerc.json` → `paths.immutableDirs` (empty by default — nothing is inspected here unless you configure it) - `permissions.deny` list in `~/.claude/settings.json` includes dangerous patterns: `git push --force`, `git reset --hard`, `rm -rf`, `git push origin main`, `git push origin master` - A `sandbox-gate` hook registered under `PreToolUse` to gate Write/Edit/Bash ### 11. Unicode steganography detection (4 points) {#unicode-steganography} Checks skill files and CLAUDE.md for hidden characters that render identically to legitimate text but redirect agent behavior. Covers the attack surface from the ToxicSkills and Rules File Backdoor incidents. **What rigscore looks for:** - Greek/Cyrillic/Armenian/Georgian/Cherokee lookalikes that render as Latin letters - Zero-width joiners and zero-width non-joiners - Bidirectional control characters (bidi overrides) - Tag characters (U+E0000–U+E007F) — an invisible ASCII-shadow channel ### 12. Git hooks (2 points) {#git-hooks-for-ai} Git hooks are your last line of defense before code leaves your machine. Without pre-commit hooks, secrets, broken governance files, and unreviewed changes go straight to the repo. **What rigscore looks for:** - Pre-commit hooks present (`.git/hooks/pre-commit` or a hook manager like Husky/lefthook) - Claude Code hooks (`.claude/settings.json` with hook configuration) - Push URL guards (`.git/config` with `pushurl = no_push`) - External hook directories from config **What to fix:** Install [Husky](https://github.com/typicode/husky) or [lefthook](https://github.com/evilmartians/lefthook) and add pre-commit hooks that scan for secret patterns and validate governance files. ### 13. Permissions hygiene (4 points) {#permissions-hygiene} File permissions are the foundation of access control. Misconfigured permissions on SSH keys, secret files, or governance files can undermine every other security measure. **What rigscore looks for:** - SSH directory permissions (`~/.ssh` should be 700) - SSH private key permissions (should be 600) - World-readable sensitive files in the project (`.pem`, `.key`, `*credentials*`) - Governance file ownership consistency (mixed UIDs may indicate unauthorized modifications) **What to fix:** Run `chmod 700 ~/.ssh` and `chmod 600 ~/.ssh/id_*`. Ensure sensitive files are not world-readable. Verify all governance files are owned by the same user. **Platform note:** Permission checks are POSIX-only. On Windows, rigscore reports a SKIPPED finding and recommends manual verification with `icacls`. ### 14. Windows/WSL security (advisory, 0 points) {#windows-security} On Windows, rigscore checks for WSL-specific security risks. This is an advisory check — it doesn't affect the score but surfaces important configuration issues. **What rigscore looks for:** - WSL interop settings — warns if Windows PATH leaks into WSL (`appendWindowsPath=true`) - `.wslconfig` firewall and networking mode - Windows Defender exclusions that include project directories or `node_modules` - NTFS permissions advisory for sensitive files **Platform note:** Runs on Windows hosts *and* on WSL2 Linux guests (the WSL arm is marker-based — it fires when the kernel release string contains `microsoft`, so a WSL guest is graded even though it is Linux). Returns N/A only on plain Linux and macOS. Weight 0 means it never affects the score. ### 15. Network exposure (advisory, 0 points) {#network-exposure} Advisory check that detects AI services bound to `0.0.0.0` instead of `127.0.0.1`. Scans MCP config URLs, Docker port bindings, Ollama config, and live listeners. ### 16. Site security (advisory, 0 points, `--online`) {#site-security} Probes deployed sites listed in `.rigscorerc.json` under `sites: [...]`. Only runs when `--online` is passed; otherwise returns N/A. **What rigscore looks for:** - Critical security headers: `content-security-policy`, `x-frame-options`, `x-content-type-options`, `strict-transport-security` - Advisory headers: `referrer-policy`, `permissions-policy` - Server fingerprinting via `X-Powered-By` and versioned `Server` headers - Sensitive paths that should not be publicly reachable (`.env`, `.git/config`, `backup.zip`, `wp-admin/`, `phpmyadmin/`, etc.) - PII leakage in rendered HTML (emails, phone numbers, internal IP ranges) - Hardcoded API keys in the served page source - `` build-tool disclosure - SSL certificate expiry (critical if expired, warning if <30 days) ### 17. Instruction effectiveness (advisory, 0 points) {#instruction-effectiveness} Audits the instruction files that feed the agent — CLAUDE.md, `~/.claude/CLAUDE.md`, and files under `.claude/commands/` and `.claude/skills/`. Measures quality, not security, so it is advisory. **What rigscore looks for:** - Context budget: total estimated tokens across all instruction files versus a 200K reference window (warns above 20%, info above 10%); flags individual files above ~5K tokens - Bloat: files over 500 lines (warning) or 300 lines (info) - Vague directives: phrases like "use your best judgment", "as appropriate", "figure it out", "when it makes sense" - Contradictions between `always`/`must` and `never`/`must not` directives on the same topic - Dead file references in backtick paths and markdown links (paths that no longer exist) - Redundancy across instruction files ### 18. Skill ↔ governance coherence (advisory, 0 points) {#skill-coherence} Scans every SKILL.md under `.claude/skills/` and `.claude/commands/` (both project and `~/.claude/`) and checks that each skill is aware of the constraints that governance claims to enforce. Advisory. **What rigscore looks for:** - Skills that perform git/ship/push operations but don't acknowledge manual merge workflow requirements (`gh-merge-approved`, `brc-merge-approved`) - Skills that perform write/edit/scaffold operations but don't acknowledge layer write restrictions on `_governance/` and `_foundation/` - Skills that overwrite files without mentioning WIP protection (untracked files in `_active/svc-*` have no backup) - Skills that push/commit/ship without mentioning branch protection (no force push, no direct push to main/master) - Hook ↔ settings conflicts where a PreToolUse hook blocks a pattern that `settings.json` allow-lists **Opt-in by default.** The detections above are driven by `.rigscorerc.json` → `skillCoherence.constraints` and `skillCoherence.hookSettingsConflicts`, both of which ship **empty** — a stock install emits nothing from this check until you configure the constraints your workspace enforces. The `gh-merge-approved` / `_governance/` / `_active/svc-*` examples are the conventions this repo configures, not built-in defaults. ### 19. Workflow maturity (advisory, 0 points) {#workflow-maturity} Classifies the project's workflow artefacts against the AI development taxonomy and surfaces graduation signals — skills that should become code, pipelines that should be split, orphan memory files. Advisory. **What rigscore looks for:** - Pipeline step overload: single modules with too many `# Stage N` / `# Step N` / `# Phase N` markers, or stage directories that suggest a monolithic pipeline should be decomposed - Skills that have matured past the LLM-driven stage and should be graduated to deterministic code - Orphan memory files that are not linked from `MEMORY.md` (`workflow-maturity/memory-orphan`) ### 20. Agent output schemas (advisory, 0 points) {#agent-output-schemas} Scans `.claude/agents/*.md` (project and `~/.claude/`) and verifies that every agent that claims to emit JSON output (e.g. body contains "Return ONLY a JSON" or an "## Output Format" section) declares a parseable ```` ```json ```` fenced example block. Codifies the convention documented in `_active/lib-skill-utils/AGENT_OUTPUT_SCHEMAS.md` so fan-out orchestrators have a reliable contract to parse against. **What rigscore looks for:** - Agents that announce JSON output but contain no `` ```json `` fenced example block - Agents whose `` ```json `` fenced blocks fail `JSON.parse` (placeholder values left unquoted, trailing commas, comments) ### 21. Documentation coverage (advisory, 0 points) {#documentation} Enforces the docs-first gate: every module in `src/checks/` must have a matching page under `docs/checks/` with the canonical sections (Purpose, Triggers, Weight rationale, Fix semantics, SARIF, Example) filled in, and every doc must correspond to a real check. Advisory, and only active in rigscore itself or plugin repos that mirror its layout. See [`docs/checks/documentation.md`](docs/checks/documentation.md). **What rigscore looks for:** - Check modules in `src/checks/` with no matching `docs/checks/.md` - Doc pages that are missing required H2 sections or have empty section bodies - Doc pages whose stated weight drifts from `src/constants.js` - Doc H1 titles that do not match the check id - Orphan doc pages with no corresponding check module ### 22. Loop governance (advisory, 0 points) {#loop-governance} Scores how safely a repo runs **agent loops** — anywhere the project drives an agent CLI repeatedly with no human in the loop. An agent loop must be **bounded** (iteration cap, turn budget, or timeout) and must be able to **stop**. Most repos have no agent-loop surface and return N/A. First check of the **Practice** pillar. See [`docs/checks/loop-governance.md`](docs/checks/loop-governance.md). **What rigscore looks for:** - Agent invoked inside a loop with no bound — no iteration counter, no `--max-turns`, no `timeout` - Loops with no terminal state to test for — stoppable only by killing them - Agents on a cron line with nothing bounding one unattended tick - `--dangerously-skip-permissions` in any scanned script **Scope note:** agent jobs in `.github/workflows/**` belong to `ci-agent-caps` and are deliberately ignored here, so one uncapped CI job is not double-counted. ### 23. Spec goals (advisory, 0 points) {#spec-goals} Scores whether a repo drives its agents from written goals and specs. Maps to ASI01 — an agent with no stated goal has nothing to be hijacked *away from*. Recognises four layouts (GitHub Spec Kit, AGENTS.md, Kiro, OpenSpec), each gated on its marker dir rather than a bare `specs/` dir. See [`docs/checks/spec-goals.md`](docs/checks/spec-goals.md). **What rigscore looks for:** - A governing goal file that is still an unfilled boilerplate template - Specs that were never decomposed into executable tasks - Goal-file staleness: a 90-day gap against the newest spec, by local git committer date (offline, read-only) ### 24. CI agent caps (advisory, 0 points) {#ci-agent-caps} An agent running unattended in CI — repo write credentials, no human at the keyboard — is the highest-blast-radius agent surface most teams have. Every CI job invoking an agent should declare a turn cap, a job timeout, and tool scoping. See [`docs/checks/ci-agent-caps.md`](docs/checks/ci-agent-caps.md). **What rigscore looks for:** - Bypass flags in a workflow (`--dangerously-skip-permissions`, `--yolo`, `--sandbox danger-full-access`, etc.) — CRITICAL - Agent jobs with no `timeout-minutes` (silently inheriting GitHub's 360-minute default) - Agent invocations with no turn cap (`--max-turns`) or with unrestricted tools ### 25. Agent memory hygiene (advisory, 0 points) {#memory-hygiene} Agent memory is re-injected every turn, so bloat is billed per request and junk competes with governance for the model's attention. No incumbent convention exists for memory layout — this check defines one. Budget default is configurable via `.rigscorerc.json` (`memoryHygiene.budgetBytes`). See [`docs/checks/memory-hygiene.md`](docs/checks/memory-hygiene.md). **What rigscore looks for:** - The auto-loaded memory bundle exceeding a byte budget (default 40,000) - Memory files that are empty, or a bare stub with frontmatter and no body - Single home per rule: the same rule line written verbatim into both a governance file and a memory file, so editing one copy silently fails to take effect ### 26. AI-use disclosure (advisory, 0 points) {#ai-disclosure} Projects that accept AI-assisted contributions are increasingly expected to say so. Applicable **only when the repo shows an AI surface** (a governance file, a `.claude/` dir, an MCP config, or an agent CI job); a repo with no AI surface owes no disclosure and returns N/A. Advisory because the conventions are still forming. See [`docs/checks/ai-disclosure.md`](docs/checks/ai-disclosure.md). **What rigscore looks for:** - An AI surface with no AI-use policy anywhere (`AI_POLICY.md`, or a generative-AI section in `CONTRIBUTING.md`) - A PR template that carries no generative-AI declaration - No PR template in any location GitHub reads (INFO — the weakest of the three signals) ### 27. Sandbox posture (advisory, 0 points) {#sandbox-posture} Every agent CLI expresses "how much can this agent do without asking me" differently. This check reduces an agent's sandbox config to a single posture — `restricted` / `partial` / `unrestricted` — and flags the dangerous combinations, principally an agent that is both free of approval prompts and able to reach the network. The client registry (`src/clients.js`) is the surface list, so a new client is picked up with no change to this check's logic. See [`docs/checks/sandbox-posture.md`](docs/checks/sandbox-posture.md). **What rigscore looks for:** - Codex CLI (`.codex/config.toml`): `approval_policy`, `sandbox_mode`, `[sandbox_workspace_write] network_access` - Claude Code (`.claude/settings.json` + `settings.local.json`): whether any `permissions.deny` rules exist at all, plus `defaultMode`. Which *allow* entries are dangerous is `claude-settings`' job; this check never re-grades them. - Gemini CLI (`.gemini/settings.json`): `general.defaultApprovalMode` (`default` / `auto_edit` / `plan` / `yolo`) - opencode (`opencode.json`): the `permission` block — whether `bash` (or the `*` catch-all) auto-runs (`allow`) vs prompts (`ask`) / blocks (`deny`) - Cursor (committed `.cursor/permissions.json`): a `"*"` / `"*:*"` wildcard in `terminalAllowlist` / `mcpAllowlist` that auto-runs everything Windsurf is deliberately **not** graded: its Turbo-mode auto-execute level and allow/deny lists live only in the Windsurf Settings UI / global config, with no committed in-repo file rigscore can read at cwd. ### 28. Semantic tool-description judge (advisory, 0 points, `--semantic`) {#semantic-tools} Opt-in check for **obfuscated MCP tool poisoning** — a hidden directive paraphrased into a tool description so static regex checks miss it. Runs **only** with `--semantic`; a default scan makes zero external calls from it. For each tool description (read from `tools/list` snapshot files listed under `paths.mcpToolsSnapshot` in `.rigscorerc.json` — the same JSON you pipe into `rigscore mcp-hash`), it asks your **own first-party agent CLI** (`claude -p`, `gemini`, `codex exec`, … — never an API key or SDK client) to classify the text benign vs. suspicious. The command is configurable via `semantic.command` in `.rigscorerc.json` (default `["claude", "-p"]`; the judge prompt is appended as the final argument). Each description is wrapped in a data-only frame and the judge is told to treat it as data, not instructions, so a poisoned description cannot hijack the judge. If that binary is not on PATH the check skips gracefully (no finding, no crash). See [`docs/checks/semantic-tools.md`](docs/checks/semantic-tools.md). ## Scoring | Score | Grade | Meaning | |-------|-------|---------| | 90-100 | A | Strong hygiene posture | | 75-89 | B | Good foundation, some gaps | | 60-74 | C | Moderate risk, needs attention | | 40-59 | D | Significant gaps | | 0-39 | F | Critical issues, fix immediately | Scoring uses an additive deduction model with moat-heavy weighting — AI-specific checks (MCP, coherence, skill files, governance) account for ~48% of the score: | Check | Weight | Category | |-------|--------|----------| | MCP server configuration | 14 | supply-chain | | Cross-config coherence | 14 | governance | | Skill file safety | 10 | supply-chain | | CLAUDE.md governance | 10 | governance | | Claude settings safety | 8 | governance | | Deep source secrets | 8 | secrets | | Secret exposure | 8 | secrets | | Credential storage hygiene | 6 | secrets | | Docker security | 6 | isolation | | Infrastructure security | 6 | process | | Unicode steganography | 4 | governance | | Permissions hygiene | 4 | process | | Git hooks | 2 | process | | Windows/WSL security | 0 | isolation (advisory) | | Network exposure | 0 | isolation (advisory) | | Site security | 0 | isolation (advisory) | | Instruction effectiveness | 0 | governance (advisory) | | Skill ↔ governance coherence | 0 | governance (advisory) | | Workflow maturity | 0 | governance (advisory) | | Documentation coverage | 0 | process (advisory) | | Agent output schemas | 0 | governance (advisory) | | Loop governance | 0 | process (advisory) | | Spec goals | 0 | governance (advisory) | | CI agent caps | 0 | process (advisory) | | Agent memory hygiene | 0 | governance (advisory) | | AI-use disclosure | 0 | governance (advisory) | | Sandbox posture | 0 | isolation (advisory) | | Semantic tool-description judge | 0 | supply-chain (advisory) | - **CRITICAL** findings zero out their sub-check entirely - **WARNING** findings deduct 15 points each (1 WARNING = 85, 2 = 70, 3 = 55...) - **INFO** findings deduct 2 points each, with a floor of 50 when no WARNINGs are present - **PASS** and **SKIPPED** findings have no score impact **Compound risk penalty:** When the coherence check finds a CRITICAL contradiction, 10 additional points are deducted from the overall score — reflecting the systemic nature of governance failures. **Coverage scaling (what the `Coverage: N of M checks applicable (weight W/100)` line means):** Checks that find nothing to scan are marked N/A and excluded from the weighted average — their weight is redistributed proportionally across the remaining applicable checks. The final overall score is then multiplied by `min(1, W / 100)`, where W is the total applicable check weight. This scaling is **continuous and always applied — there is no threshold and no step.** A project where only part of the check suite can reach a verdict should not be able to claim a perfect 100 — partial coverage means partial confidence. Put plainly: **W is your reachable ceiling.** If only 80 of the 100 points of check weight apply, then even an all-passing scan caps at 80/100 — and the report says so on the coverage line (`— score scaled ×0.80`). Full coverage (W ≥ 100) is a no-op. The compound risk penalty above is applied *after* scaling, as a flat 10-point deduction (it is not itself scaled). Characterization tests pin the exact behavior in `test/scoring-coverage.test.js`; see the design note at the top of `src/scoring.js` for the rationale. ### The Practice score (second axis) Every run computes a second 0–100 axis — the **Practice** score — printed under the hygiene score in the box above and exposed in `--json` as `practiceScore`. Where the hygiene (Security) score measures configuration *safety*, Practice measures *operator practice*: it is built from the Practice-pillar advisory checks (`loop-governance`, `spec-goals`, `ci-agent-caps`, `memory-hygiene`). Those checks are weight-0 on the Security axis, so the Practice score is fully independent — it never moves your hygiene score. A repo with no practice surface to grade — no agent loops, specs, CI agent jobs, or memory files — reports `Practice: n/a`, never `0/100`, so a project that is simply out of scope for these checks is not penalised. See [`docs/practice-score.md`](docs/practice-score.md) for the per-check math. **Scoring profiles:** Five built-in profiles: - `default` — balanced AI dev environment audit (WEIGHTS from `src/constants.js`). - `minimal` — AI-moat checks only (mcp-config 30, coherence 30, skill-files 20, governance-docs 20; rest 0). - `ci` — CI pipelines (identical to `default` today). - `home` — single-user dev boxes / `~/` scans. Governance / skill-files / MCP emphasized; infra / docker / windows off so coverage-scaling doesn't punish N/A infra surfaces. - `monorepo` — same weights as `default` with hints for `--recursive --depth 3`. See `docs/profiles/` for full weight tables. Custom weights can be set in `.rigscorerc.json`. Advisory (weight-0) checks NEVER affect the overall score — they are excluded from the coverage-penalty denominator so adding a new advisory cannot shift existing scores. ### Promoting advisory checks Advisory checks ship at weight `0` — they never affect the score. If your team cares about a specific advisory signal (e.g. prompt bloat from `instruction-effectiveness`, workflow maturity, skill↔governance coherence), promote it via `.rigscorerc.json`: { "weights": { "instruction-effectiveness": 5 } } Promoting an advisory to a non-zero weight moves it out of the advisory lane and into the scored lane in full. Specifically: - It now contributes to the overall score with whatever weight you set. - It now counts toward **coverage** — the denominator that drives coverage-scaling for low-applicability projects. Before promotion, a weight-0 check is explicitly excluded from coverage math (see `src/scoring.js` — `scoringApplicable` filter). After promotion, an N/A verdict on that check (e.g. a project with no skills dir for `skill-coherence`) will redistribute weight like any other N/A check. - If you promote an advisory across a whole org, document it in your `.rigscorerc.json` comment so the delta from the public default score is traceable. Demoting a scored check (setting `weights.: 0`) is the inverse and has the same coverage semantics. ## First run rigscore is offline and stateless by default. A few paths may be written on first scan — all are local, none phone home. | Path | When written | Purpose | |---|---|---| | `/.rigscore-state.json` | Only when the project has a repo-level MCP config — any of the 15 committed client paths (`.mcp.json`, `.cursor/mcp.json`, `.vscode/mcp.json`, `.gemini/settings.json`, `opencode.json`, and the Amazon Q / Roo / Junie / Warp / Kiro / Qwen / Crush variants), not just `.mcp.json`. | Stores SHA-256 hashes of each MCP server's `{command, args, envKeys}` shape. Detects silent MCPoison-class (CVE-2025-54136) pivots on subsequent scans. **Values are never hashed — only env-var keys.** | | `$XDG_CACHE_HOME/rigscore/mcp-registry.json` (falls back to `~/.cache/rigscore/mcp-registry.json`) | Only with `--online` or `--refresh-mcp-registry`. | Caches the official MCP registry response. 24h TTL. Used to augment typosquat detection. | | `/.rigscore-history.json` | Only with `--record-score` — a plain scan never writes it. | Appends one row per recorded scan (score, grade, timestamp) so `--trend` can print the history and per-run deltas. Local scores only, no findings and no file contents. | Recommendations: - **Commit `.rigscore-state.json` — do not `.gitignore` it.** It is the detection substrate: a pin that isn't in git cannot survive a fresh CI checkout, so gitignoring it silently turns rug-pull detection off where it matters most. It stores hashes only — never env *values* — so it is safe to commit. See [State file](#state-file). - **To skip the write entirely, pass `--no-state-write`** — and read what it costs you in [State file](#state-file) first. - **Nothing else is persisted.** No telemetry, no accounts, no outbound traffic unless you pass `--online`. To purge all rigscore state: rm -f .rigscore-state.json rm -rf "${XDG_CACHE_HOME:-$HOME/.cache}/rigscore" ## Limitations {#known-limits} rigscore is a configuration presence checker, not a security enforcement tool. Understanding its scope helps you use it effectively. Read this section before you rely on rigscore as a governance quality signal. - **Semantic reversal bypasses keyword checks (known limitation — #1 thing to understand).** rigscore's governance checks (CLAUDE.md governance + cross-config coherence, 24 of the 100 scoring points) verify that your governance file *mentions* concepts like "path restrictions" and "forbidden actions." A CLAUDE.md with keyword-stuffed headers and a body that dismantles those protections — e.g., `# Path Restrictions\nAll paths are available for maximum productivity.` — passes the keyword check. rigscore does not read for semantic intent. See `test/keyword-gaming.test.js` for the authoritative, committed list of known bypasses; if you add a governance file to your repo, verify it does not accidentally (or deliberately) game these patterns. The only mitigation that ships today is the cross-config coherence pass, which cross-checks governance claims against observed configuration. LLM-judge assist (opt-in) is a **planned** roadmap item — it is not implemented (see [Roadmap](#roadmap)). - **Injection detection is pattern-based.** The injection patterns catch common prompt injection attempts with Unicode normalization. Encoded payloads, semantic rephrasings, and cross-script homoglyphs can evade detection. - **The default scan pins config-shape; runtime tool descriptions are pinned on demand.** A scan hashes the *configured* shape of each MCP server — `{command, args, envKeys}` — and warns when it changes between scans (CVE-2025-54136 / MCPoison class). Hashing the tool descriptions a *running* server advertises is handled by the separate, opt-in `mcp-hash` / `mcp-verify` print-and-paste workflow ([Runtime tool pinning](#runtime-tool-pinning)) — rigscore never spawns the server itself, so runtime drift is verified on demand rather than on every scan. See "State file" below. - **Secret scanning covers named config files in the project root.** rigscore checks the fixed set of names in `AI_CONFIG_FILES` (`src/constants.js`) — every known governance file plus `config.json`, `secrets.yaml`, `.env`, and similar. For deep recursive scanning, use `--deep`. For git history scanning, use a dedicated git-history secret scanner. - **Point-in-time snapshots only.** No continuous monitoring or git history scanning. Use `--json` or `--sarif` for CI pipeline integration. - **Score is shape-dependent.** Overall score reflects only the checks applicable to the project shape. rigscore ships 28 checks; an npm package sees most of them as N/A (no `.mcp.json`, no Dockerfile, no `.claude/skills/`, no `~/.ssh` to scan from CI, etc.) and scores accordingly. rigscore scores *itself* 37/100 in CI for this reason — only 10 of its own 28 checks are applicable — not because the project is broken. See [Dogfooding](#dogfooding) below. ## Dogfooding rigscore runs on rigscore in CI. Transparency about what that score means: - **Self-score: 37/100 (Grade F).** This is the real score, not a vanity baseline. rigscore is an npm package, so only **10 of its 28 checks are applicable (weight 46/100)** — the rest legitimately return N/A (no MCP config, no Docker, no skill files, no `.claude/settings.json`, etc.). The score is scaled by applicable coverage — the applicable weight over 100, capped at 1 — so partial coverage always means partial confidence (`src/scoring.js`), which is the intended behavior. Reproduce the CI number locally with a neutralised `$HOME` (what a runner sees): HOME=$(mktemp -d) node bin/rigscore.js --no-cta --profile default . If your project is seeing similar — see [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md) for the diagnostic walk-through. - **CI threshold: `--fail-under 30`.** Calibrated to the measured baseline (37) with a 7-point regression buffer. The public default is 70 — the gap is not a vanity choice, it reflects the project-shape reality above. A lower fail-under than the public default is normal for projects that don't exercise the full check surface; document yours the same way. - **`.rigscorerc.json` disables four checks** (`infrastructure-security`, `skill-coherence`, `workflow-maturity`, `agent-output-schemas`) that require workspace-oriented artifacts rigscore doesn't ship. These are per-check disables at the profile level, not ignore-rules on individual findings — the distinction matters when auditing the config. ## Roadmap Planned work. Nothing in this section ships today — if it is listed here, it is **not** implemented. - **LLM-judge assist (opt-in).** A semantic pass over governance files to catch the [semantic-reversal weakness](#known-limits) that keyword checks cannot see — a CLAUDE.md whose headers name the right concepts while its body dismantles them. Would be strictly opt-in and off by default, so rigscore stays fully offline and API-key-free out of the box. ## Options npx github:Back-Road-Creative/rigscore # Scan current directory npx github:Back-Road-Creative/rigscore /path/to/project # Scan a specific project npx github:Back-Road-Creative/rigscore --json # JSON output for CI/scripting npx github:Back-Road-Creative/rigscore --sarif # SARIF output for security tools npx github:Back-Road-Creative/rigscore --cyclonedx # CycloneDX 1.6 AI-BOM: MCP servers + their grant surface (permission scopes), skills, rules (see docs/cyclonedx.md) npx github:Back-Road-Creative/rigscore --report compliance # Group findings by compliance-framework control (see docs/compliance.md; not supported with --recursive) npx github:Back-Road-Creative/rigscore --ci # CI mode (--sarif --no-color --no-cta) npx github:Back-Road-Creative/rigscore --fail-under 80 # Fail if score < 80 (default: 70) npx github:Back-Road-Creative/rigscore --profile minimal # AI-only scoring profile npx github:Back-Road-Creative/rigscore --badge # Generate a score badge (format set by --badge-format) npx github:Back-Road-Creative/rigscore --badge --badge-format svg # Badge format: markdown (default, shields.io image snippet), endpoint (shields.io Endpoint Badge JSON for a self-updating README), or svg (self-contained, no network). Also works with --recursive, badging the monorepo average npx github:Back-Road-Creative/rigscore --junit # JUnit XML — one testcase per check (Jenkins, Azure Pipelines, GitLab test reports) npx github:Back-Road-Creative/rigscore --code-quality # GitLab Code Quality report (CodeClimate JSON) npx github:Back-Road-Creative/rigscore --no-color # Plain text output npx github:Back-Road-Creative/rigscore --cta # Opt in to the promotional CTA (off by default) npx github:Back-Road-Creative/rigscore --no-cta # Deprecated alias — CTA is already off by default; kept for back-compat npx github:Back-Road-Creative/rigscore --check # Run a single check by ID (if that check is N/A for the repo — e.g. --check docker-security with no Dockerfile — the score is `n/a` and the run exits 0, not a red 0/100) npx github:Back-Road-Creative/rigscore --recursive # Scan subdirectories as projects npx github:Back-Road-Creative/rigscore -r --depth 2 # Recursive scan, 2 levels deep npx github:Back-Road-Creative/rigscore --deep # Deep source secret scanning npx github:Back-Road-Creative/rigscore --online # Enable online checks (site-security, MCP supply chain) npx github:Back-Road-Creative/rigscore --refresh-mcp-registry # Force refetch of the MCP registry cache (implies --online; bypasses 24h TTL) npx github:Back-Road-Creative/rigscore --semantic # Opt-in semantic MCP tool-description judge (semantic-tools check; shells to a first-party agent CLI — `claude -p` default, configurable via semantic.command; no API key; skips if that binary absent) npx github:Back-Road-Creative/rigscore --include-home-skills # Also scan every registered client's home dir (~/.claude, ~/.codex/prompts, ~/.config/opencode/commands, ~/.gemini/commands, …) (default: off — project scope only) npx github:Back-Road-Creative/rigscore --fix # Show auto-fixable issues (dry run) npx github:Back-Road-Creative/rigscore --fix --yes # Apply safe auto-remediations (edits existing files only — never scaffolds new ones) npx github:Back-Road-Creative/rigscore --fix --yes --install-packs # Also install the starter packs that target your red checks (creates new files) npx github:Back-Road-Creative/rigscore --watch # Watch for changes, re-run automatically npx github:Back-Road-Creative/rigscore --init-hook # Install pre-commit hook npx github:Back-Road-Creative/rigscore --ignore "env-exposure/env-not-gitignored,skill-files/shell-exec" # Suppress findings by finding ID (exact match, case-insensitive, comma-separated). See docs/FINDING_IDS.md for the stable ID list. Title-substring still works as a legacy fallback. # Suppression is never silent: whether it comes from --ignore or a .rigscorerc.json `suppress:` entry, rigscore reports how many findings were muted and their ids — in the human report (so it shows up in a CI log), in JSON (`suppressed: { count, ids }`), and in SARIF (`runs[0].properties.suppressedCount` / `suppressedIds`). The findings are still removed from scoring; the muting is just visible, not only in a config diff. This holds in `--recursive` / `--profile monorepo` mode too: each project's own `.rigscorerc.json` `suppress:` is honored and rescored per project. npx github:Back-Road-Creative/rigscore --verbose # Also show passing checks (info/skipped findings already print by default) npx github:Back-Road-Creative/rigscore --quiet # Summary only — score, grade, posture and finding counts (pre-commit hooks, terse CI logs) npx github:Back-Road-Creative/rigscore --record-score # Append this scan's score to .rigscore-history.json (opt-in; nothing is recorded without it) npx github:Back-Road-Creative/rigscore --trend # Print the recorded score history and per-run deltas, then exit (read-only; runs no checks) npx github:Back-Road-Creative/rigscore --version # Version info npx github:Back-Road-Creative/rigscore --help # Show help ### Config inheritance (`extends`) A `.rigscorerc.json` can inherit from one or more shared baselines with an `extends` key, so a single hardened config is reusable across many repos: // rigscore-base.json (shared, hosted in a repo every project can reach on disk) { "profile": "default", "network": { "safeHosts": ["registry.internal"] }, "suppress": ["permissions-hygiene/sensitive-file-world-readable"] } // a project's .rigscorerc.json — inherits the baseline, adds its own bits { "extends": "../rigscore-base.json", "sites": ["https://project-a.example"] } Rules: - **Local paths only.** `extends` is a string or an array of strings. A relative path resolves against the directory of the file that declared it; an absolute path works too. **URLs (`http://`, `https://`) are rejected, never fetched** — rigscore makes no external calls by default. (Node-module resolution like `extends: "some-pkg/base"` is not supported yet; it may arrive in a future release.) - **Precedence.** An extended base is *lower* precedence than the file that extends it — the extending file's own keys win. Within an `extends` array, **later entries override earlier** ones (the ESLint convention). Arrays such as `network.safeHosts` and `suppress` still concatenate-and-dedupe across every layer, per the merge policy above. - **Recursive & cycle-safe.** A base may itself `extends` another base; the chain resolves depth-first. A cycle (a → b → a) is caught and raises a configuration error naming the loop rather than looping forever. - **Errors.** A missing target, a cycle, or a URL each fail the scan with a configuration error (exit `2`). Both `~/.rigscorerc.json` and a project `.rigscorerc.json` may declare their own `extends`. ### Exit codes rigscore exits with a stable code so CI can branch cleanly: | Code | Meaning | |---|---| | `0` | Scan completed. Score is at or above `--fail-under`. In baseline/diff mode: no new findings vs baseline. | | `1` | Scan completed. Score is below `--fail-under`, OR (baseline mode) new findings were detected. | | `2` | Configuration error — malformed `.rigscorerc.json`, unknown `--profile`, invalid target directory, an unreadable **or corrupt/malformed** baseline file (unparseable JSON or no findings array fails closed — never silently re-minted), or bad input to `mcp-hash` / `mcp-pin` / `mcp-verify`. In a git repo the baseline gate reads the copy **committed at HEAD** (`git show HEAD:`, like `--verify-state`), so a deleted/corrupt working-tree baseline can't launder findings. This also covers HEAD-deletion: a baseline that was tracked but **removed at HEAD** (a PR that `git rm`'d it) fails closed here rather than silently re-minting a fresh baseline over the new findings — distinguished from a genuine first run (never tracked, still mints + exit `0`) by git history. Regenerate with `--baseline-refresh` then commit. | | `3` | Reserved for subcommand pre-conditions. Currently used by `rigscore mcp-verify ` when no runtime tool hash is pinned for that server. The main scan path does not emit `3`. | | `4` | Runtime tool-description **drift** detected by `rigscore mcp-verify ` — the server's current `tools/list` hash differs from the pinned snapshot (CVE-2025-54136 "MCP rug pull" class). Re-pin with `mcp-hash \| mcp-pin` if the change is intentional. The main scan path does not emit `4`. | A runtime crash inside a check surfaces as exit `2` with `Error: scan failed: ...` on stderr; it does not currently use a distinct code. CI authors should treat non-zero as failure and branch only on `0` vs `1` for score-gating logic. ### Baseline mode and `diff` Baseline mode gates on *new* findings rather than an absolute score — useful when you want CI to block regressions without first driving an existing repo to a clean score. # First run writes the baseline; later runs report ONLY findings new vs it, # and exit 1 if any appeared. rigscore --baseline .rigscore-baseline.json . # Intentionally accept the current findings as the new baseline, then commit it. rigscore --baseline .rigscore-baseline.json --baseline-refresh . # Standalone diff of two findings files (exit 1 if `current` adds any). rigscore diff old-baseline.json new-findings.json In a git repo the baseline is read from the copy **committed at HEAD** (like `--verify-state`), so a deleted or corrupt working-tree baseline can't launder findings. `--baseline-refresh` is the sanctioned way to (re)write the working-tree file for review and commit. Exit codes `0`/`1`/`2` follow the table above. ### Watch mode `--watch` re-runs rigscore automatically when relevant files change. It monitors governance files, MCP configs, `.env`, Docker Compose files, git hooks, and `.rigscorerc.json`. Changes are debounced (500ms) to avoid rapid re-scans. npx github:Back-Road-Creative/rigscore --watch npx github:Back-Road-Creative/rigscore --watch --verbose ### Pre-commit hook `--init-hook` installs a git pre-commit hook that runs rigscore before each commit: npx github:Back-Road-Creative/rigscore --init-hook This creates (or appends to) `.git/hooks/pre-commit` with `npx github:Back-Road-Creative/rigscore --fail-under 70 --no-cta || exit 1`. If the hook already contains rigscore, it skips installation. ### Recursive mode For monorepos and multi-project workspaces, `--recursive` discovers project subdirectories and scans each independently. A directory is considered a project if it contains any recognizable marker file (package.json, pyproject.toml, Dockerfile, docker-compose.yml, CLAUDE.md, .env, etc.). # Scan all projects one level deep npx github:Back-Road-Creative/rigscore . --recursive # Scan two levels (e.g., workspace/_active/svc-foo) npx github:Back-Road-Creative/rigscore . -r --depth 2 # JSON output with per-project breakdown npx github:Back-Road-Creative/rigscore . -r --depth 2 --json # SARIF output with one run per project npx github:Back-Road-Creative/rigscore . -r --depth 2 --sarif The overall score uses the **average** across all discovered projects. Hidden directories, `node_modules`, `venv`, and `__pycache__` are automatically skipped. Recursive scanning runs projects concurrently (4 at a time) for performance. ### Auto-fix `--fix` identifies safe, reversible remediations and shows what would be changed: # Dry run — see what would be fixed npx github:Back-Road-Creative/rigscore --fix # Apply fixes npx github:Back-Road-Creative/rigscore --fix --yes **Safe fixes only** — most fixers self-register from their check modules (`getRegisteredFixes()`); a few mechanical ones that span several checks live in `src/fixer.js` itself. Today that is 17 fixers: - *Secrets / permissions:* add `.env` to `.gitignore`; add `*.pem`, `*.key` to `.gitignore`; `chmod 600` on `.env` files; `chmod 700` on `~/.ssh`; `chmod 600` on SSH private keys; `chmod 644` on world-writable skill files. - *MCP:* disable the `enableAllProjectMcpServers` auto-approve bypass in `.claude/settings.json`; strip an `ANTHROPIC_BASE_URL` / `ANTHROPIC_API_BASE` redirect (CVE-2026-21852) from a committed MCP server env. - *Docker:* remove `privileged: true`; remove a Docker-socket volume mount; add `cap_drop: [ALL]`; add `security_opt: [no-new-privileges:true]`. - *Coherence:* append a governance-declaration stub for an undeclared MCP server (append-only — never rewrites existing prose). - *Unicode:* strip zero-width, bidi-override and tag characters out of governance and committed-config files (invisible codepoints only — visible homoglyphs are left alone, since rewriting them is lossy). - *Git hooks:* `chmod +x` a `pre-commit` / `pre-push` hook git is silently skipping because it is not executable. - *Permissions:* fill a starter `permissions.deny` list into a `.claude/settings.json` that has none — only into a settings file you already have, and never over a deny list you already wrote. - *Credentials:* append a `${VAR}` placeholder for a plaintext client-config credential to an `.env.example` you already have, so the secret has somewhere to move to. rigscore never modifies existing governance file content, and never creates a file the repo did not already have — the last two fixers above edit an existing `.claude/settings.json` / `.env.example` and are a no-op skip when that file is absent. Creating one is a pack install, behind `--install-packs`. #### `--install-packs` — scaffolding is a separate opt-in `--yes` means *"don't prompt me"*. It does **not** mean *"scaffold governance files I never asked for"* — so `--fix --yes` only remediates checks that are already red, and creates no file the repo did not already have. A [starter pack](docs/init-packs.md) is the other kind of remediation: it drops a whole baseline in (`.claude/settings.json`, a pre-commit hook, `AGENTS.md`). `--fix` still *offers* the packs that target your red checks — listing them costs nothing — but installing them needs its own consent: # Offered, not installed: names the packs and the flag that would install them npx github:Back-Road-Creative/rigscore --fix --yes # Install them too npx github:Back-Road-Creative/rigscore --fix --yes --install-packs `--install-packs` only widens what `--yes` may write — it never writes on its own, so `--fix --install-packs` (no `--yes`) is still a dry run. An install never overwrites one of your values: a file you are missing is written, and a json/yaml config you already have is **hardened in place** by the additive merge engine — the pack's absent keys are merged in (reported `merged`), a value you already set is kept (`kept your existing `), and a corrupt or non-mergeable dest falls back to `skipped (exists)`, left byte-for-byte. This is the same non-destructive behavior as `rigscore init -- --merge`. To install one specific pack, or to overwrite a file wholesale, use `rigscore init -- [--force]`. ### Plugins rigscore auto-discovers `rigscore-check-*` packages from `node_modules`. Plugins extend rigscore with custom checks without modifying the core. **Creating a plugin:** // rigscore-check-my-custom/index.js export default { id: 'my-custom', name: 'My Custom Check', category: 'governance', // governance | secrets | isolation | supply-chain | process async run(context) { const findings = []; // Your check logic here // context.cwd, context.homedir, context.config available return { score: 100, findings }; }, }; **Plugin weights:** By default, plugin checks have weight 0 (advisory). Set custom weights in `.rigscorerc.json`: { "weights": { "my-custom": 5 } } Plugins must export `id`, `name`, `category` (strings), and `run` (async function). Invalid plugins produce a warning to stderr but don't crash the scan. Scoped packages (`@org/rigscore-check-*`) are also discovered. ## CI Integration rigscore is **not GitHub-only** — any CI that can run Node (or a container) can gate on it. The CLI prints its results and exits with a stable code, so the runner needs nothing rigscore-specific: it branches on the exit code alone. `--ci` bundles the CI-friendly defaults (`--sarif --no-color --no-cta`); pair it with `--fail-under N` to set the gate threshold (default `70`). | Exit code | CI meaning | |---|---| | `0` | Score is at or above `--fail-under` — the job passes. | | `1` | Score is below `--fail-under` — fail the job. | | `2` | Config/usage error (bad flag, missing target dir, malformed `.rigscorerc.json`). | Branch score-gating on `0` vs `1` only, and treat every other non-zero as an error. The full table (including the baseline-mode and `mcp-verify` codes `3` and `4`) is under [Exit codes](#exit-codes) above. ### GitHub Actions Use the rigscore GitHub Action: - uses: Back-Road-Creative/rigscore@v2.1.0 with: fail-under: 70 upload-sarif: true Or run directly: - run: npx github:Back-Road-Creative/rigscore --ci --fail-under 70 ### GitLab CI No plugin needed — call the CLI from a `.gitlab-ci.yml` job. The exit code drives pass/fail, and the SARIF stream can be captured as an artifact: rigscore: image: node:20 script: - npx -y github:Back-Road-Creative/rigscore --ci --fail-under 70 > rigscore.sarif artifacts: when: always paths: - rigscore.sarif Swap `image: node:20` for `ghcr.io/back-road-creative/rigscore:` to skip the `npx` fetch. The same shape works on any other CI platform (CircleCI, Jenkins, Bitbucket Pipelines, Woodpecker): run the CLI, let its exit code gate the job. ### SARIF rigscore outputs SARIF v2.1.0 compatible with GitHub Advanced Security: npx github:Back-Road-Creative/rigscore --sarif > results.sarif ## Privacy rigscore runs entirely on your local machine by default. No telemetry, no accounts, no API calls. The `--online` flag opts in to outbound HTTP probes for the site-security check and MCP supply-chain verification — those explicitly reach out, and only to the URLs and packages you have already configured. For details on what rigscore writes to disk on first scan and how to purge it, see the [First run](#first-run) section above. ## State file When your project has a repo-level MCP config — any of the 15 committed client configs, not just `.mcp.json` — rigscore writes `.rigscore-state.json` at the project root on each scan. It records a SHA-256 hash of each MCP server's `{command, args, envKeys}` (env **keys only** — values are never hashed, so no secrets leak). On subsequent scans, a changed hash for an existing server name fires a WARN — catching MCPoison-class (CVE-2025-54136) silent pivots of trusted MCP servers. **This is the one file a scan writes.** Every other check is read-only. **Commit `.rigscore-state.json`. Do not `.gitignore` it.** The instinct on seeing a new generated file is to ignore it — here that silently disables the protection. The pin *is* the detection substrate: `mcp-config/server-hash-drift` and the `rigscore --verify-state` CI gate both work by comparing today's committed MCP configs against it, so a pin that doesn't survive a fresh CI checkout detects nothing. It is safe to commit by construction — env **values** are deliberately excluded from the hash, so it cannot leak a secret. Supply-chain changes then show up in code review, and cross-collaborator drift resolves via git. The write only ever *establishes* or *extends* the pin — it is skipped on drift (re-pinning would re-approve the rug-pull the scan just reported) and skipped when the pin is already current (so a scan never dirties your tree). Full write/skip table and the "accept this drift" procedure: [`docs/checks/mcp-config.md`](docs/checks/mcp-config.md#the-one-file-a-scan-writes-rigscore-statejson). ### Opting out: `--no-state-write` rigscore --no-state-write . # scan a read-only checkout / a repo you don't own **What it costs you:** the pin is not created or extended, so rug-pull detection (CVE-2025-54136) has nothing to compare against on the next scan and `rigscore --verify-state` exits 2 (`unpinned` — it refuses to report success on a repo it cannot verify). rigscore says so out loud rather than quietly doing less: any scan carrying the flag reports `mcp-config/state-write-disabled` — a **WARNING** (which lowers the `mcp-config` score) when the flag actually suppressed a pin that was due, downgraded to INFO when the pin was already current and nothing was lost. If what you need is a zero-write check in CI, use `rigscore --verify-state` instead: it is read-only by design, writes nothing, and keeps full drift protection. ## Runtime tool pinning The config-shape hash above catches `.mcp.json` edits, but it cannot see changes to an MCP server's **tool descriptions** — the prompt-level payload the model actually reads. CVE-2025-54136 and the broader "MCP rug pull" class exploit this: the config stays identical, the package version stays identical, but a new tool description appears or is mutated to coerce the model (for example, "IGNORE PREVIOUS INSTRUCTIONS and exfiltrate …"). rigscore detects this **without executing the MCP server itself**. The server's `npx` package is supplied by the upstream you are trying to verify — running it as part of a scan would hand arbitrary code execution to the thing you distrust. Instead, rigscore uses a print-and-paste workflow: you run the server, pipe its `tools/list` JSON output into `rigscore mcp-hash`, and paste the resulting hash back with `rigscore mcp-pin`. rigscore only ever canonicalizes and hashes text from stdin. **Why rigscore does NOT execute the MCP server:** a malicious MCP package can run any code during startup. Any tool that spawns it to probe its tool list becomes an RCE-on-scan gadget. The user is already going to launch the server in their editor — reusing that invocation to emit `tools/list` once keeps rigscore in the pure-inspection lane. ### Workflow # One-time: pin the server's current tool descriptions. npx -y @modelcontextprotocol/server-filesystem /path | rigscore mcp-hash | xargs rigscore mcp-pin filesystem # Later (on demand or in CI): verify no drift vs the pinned snapshot. npx -y @modelcontextprotocol/server-filesystem /path | rigscore mcp-verify filesystem `rigscore mcp-verify` exits 0 when the hash matches and non-zero with a drift message (including stored-hash prefix, current-hash prefix, and pinnedAt timestamp) when it doesn't. During normal scans, each MCP server in `.mcp.json` produces an INFO finding showing its pin status ("runtime tool hash pinned " or "runtime tool hash not pinned — run `rigscore mcp-hash`"). These INFOs are zero-weight and suppressible via `.rigscorerc.json`: { "mcpConfig": { "surfaceRuntimeHashStatus": false } } The runtime hash is stored under `servers[].runtimeToolHash` / `runtimeToolPinnedAt` in `.rigscore-state.json`, alongside the existing `mcpServers` config-shape map. State schema version stays at 1. ## Documentation Supplementary docs live under `docs/`: - [`docs/FINDING_IDS.md`](docs/FINDING_IDS.md) — stable finding-ID reference. Use when pinning `--ignore` patterns, writing `.rigscorerc.json` `suppress:` entries, or ingesting SARIF ruleIds. - [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md) — operational FAQ. Why is my score an F, what changed after upgrading, WSL2 gotchas, how to re-pin the pre-commit hook, baseline diff behavior. - [`docs/examples/`](docs/examples/) — starter skill / rules templates for Cursor, Cline, Continue, Windsurf, and Aider. Calibrated to pass rigscore's default profile. - [`docs/checks/`](docs/checks/) — per-check reference with Purpose, Triggers, Weight rationale, Fix semantics, SARIF, and Example. One page per module in `src/checks/`. - [`docs/profiles/`](docs/profiles/) — weight tables for each scoring profile (`default`, `minimal`, `ci`, `home`, `monorepo`). - [`docs/compliance.md`](docs/compliance.md) — how each check maps to compliance-framework controls (the mapping `--report compliance` renders). - [`docs/practice-score.md`](docs/practice-score.md) — the Practice axis: which checks feed it and how the second score is computed. Prefer the terminal? `rigscore explain ` prints the relevant `docs/checks/` page (finding-specific section when available) — e.g. `rigscore explain governance-docs/no-governance-file`. ## Contributing Issues and PRs welcome. If you find a check that's missing or a false positive, [open an issue](https://github.com/Back-Road-Creative/rigscore/issues). ### Fixture dogfood The repo ships a deliberately-imperfect fixture project at `test/fixtures/scored-project/` that exercises most of the check surface. The assertion suite in `test/fixture-dogfood.test.js` imports the in-process scanner API and locks: - total actionable finding count (±4 tolerance) - overall score (documented band) - a handful of specific critical findings by id (title-substring fallback) Rigscore's self-scan returns N/A on ~half the check surface — the dogfood fixture fills that gap so behavioral regressions surface as test failures instead of silently changing the real self-score. Run the fixture suite by itself: npm run test:fixture To regenerate the locked count/score band when a check intentionally changes, re-run with the characterization switch and commit the diff: UPDATE_FIXTURES=1 npm run test:fixture See `test/fixtures/scored-project/README.md` and `EXPECTED-FINDINGS.md` for the fixture layout and the intended finding list. ### Adding a check Each check is a module in `src/checks/` that exports a standard interface: export default { id: 'my-check', name: 'My new check', category: 'governance', // governance | secrets | isolation | supply-chain | process async run(context) { // context.cwd = working directory // context.homedir = user home directory // context.config = loaded .rigscorerc.json config return { score: 0-100, findings: [{ severity: 'critical', // critical | warning | info | skipped | pass title: 'What was found', detail: 'Why it matters', remediation: 'How to fix it', learnMore: 'https://...' // optional — rendered as link in terminal output }] } } } Weights are defined in `src/constants.js` WEIGHTS map (single source of truth). Check modules do not define their own weight. ## Verify this tool A security tool with no verification story is its own theater problem. Every signed release ships with a Sigstore-backed build-provenance attestation, a CycloneDX SBOM, and a public bypass-test catalog. You should not have to trust the author — verify the artifact. The current signed release is `v2.1.0`. All commands below reference it; substitute whatever tag you're auditing. ### 1. Tag signature git verify-tag v2.1.0 Tag signing is opt-in for maintainers. The authoritative provenance signal is the **build attestation** (next section), not the tag — the attestation is generated by GitHub Actions on a clean runner against a specific commit, which is a stronger claim than any local signing key. ### 2. Provenance attestation (canonical) gh release download v2.1.0 --repo Back-Road-Creative/rigscore --pattern '*.tgz' gh attestation verify rigscore-2.1.0.tgz --owner Back-Road-Creative One-liner equivalent (downloads, verifies, cleans up): scripts/verify-release.sh v2.1.0 A green result means: this exact tarball was built by GitHub Actions on `Back-Road-Creative/rigscore`, at the commit pointed to by `v2.1.0`, by the workflow `.github/workflows/release-provenance.yml`, and signed via Sigstore keyless OIDC against the GitHub Actions identity. No long-lived signing keys are involved. The Rekor transparency log holds the entry; the certificate's signer identity binds the artifact to a specific workflow run. What this rules out: a maintainer cannot silently publish a tarball built on their laptop, swap a release asset post-hoc, or backdate a release. The signer identity in the attestation must match the workflow path on this repo. ### 3. SBOM review The release bundles a CycloneDX 1.5 SBOM at `sbom.cdx.json`. Pull it directly: gh release download v2.1.0 --repo Back-Road-Creative/rigscore --pattern 'sbom.cdx.json' cat sbom.cdx.json | jq '.components[].name' | sort -u Rigscore has **two** runtime dependencies, full stop: - `chalk` — terminal coloring - `yaml` — YAML parsing for governance files The SBOM lists ~50 components total because it includes the dev/test transitive graph (vitest, esbuild, chai). Every entry not under `dependencies` in `package.json` is dev-only. To see runtime-only: node -e "const p=require('./package.json');console.log(Object.keys(p.dependencies))" A two-dependency runtime surface is intentional. It means the supply-chain attack surface for a rigscore install is the smaller of GitHub itself and those two well-known packages. ### 4. Reproducible bundle hash You can rebuild the tarball locally and compare hashes: git clone https://github.com/Back-Road-Creative/rigscore.git cd rigscore && git checkout v2.1.0 nvm use 20 # match the Node version the release workflow used npm ci # package-lock.json is the anchor npm pack sha256sum rigscore-2.1.0.tgz For `v2.1.0`, the published tarball SHA256 is: 957718ac0aaaa1cdb31b0f9d54e762cca583845ad45373e3f3f6278ed6f6313f rigscore-2.1.0.tgz Compare against the asset: gh release download v2.1.0 --repo Back-Road-Creative/rigscore --pattern '*.tgz' sha256sum rigscore-2.1.0.tgz **Caveats.** `npm pack` is reproducible across the same Node minor version when `package-lock.json` is committed (it is). Cross-minor or different `npm` versions can produce slightly different tar headers. If your local hash differs and the attestation in step 2 verifies, trust the attestation — it ties the bytes to the actual GitHub Actions build. The hash recipe is a belt-and-braces sanity check, not the primary signal. ### 5. Public bypass tests We publish the cases rigscore intentionally does NOT catch: - [`test/keyword-gaming.test.js`](test/keyword-gaming.test.js) — checks that match on string presence rather than semantics. - [`test/mcp-evasion.test.js`](test/mcp-evasion.test.js) — MCP server configurations that slip past the typosquat / scope checks. - [`test/injection-evasion.test.js`](test/injection-evasion.test.js) — prompt-injection patterns the unicode and skill-file checks do not flag. - [`test/ansi-injection.test.js`](test/ansi-injection.test.js) — ANSI-control-sequence injection in governance files. These tests are public so you can audit our limits. They lock the current behavior — each asserts that a specific evasion is *not* caught, so a future PR that closes one of them flips its assertion from "not detected" to detected. Do not assume rigscore catches anything that is not asserted in the test suite. ### 6. Threat model The full attack-surface and out-of-scope catalog lives in: - [`THREAT-MODEL.md`](THREAT-MODEL.md) — what rigscore inspects, what it doesn't, and the trust boundaries. - [`docs/known-limits.md`](docs/known-limits.md) — concrete examples of attacks rigscore will not detect, with pointers to complementary tools. If you need a check rigscore does not implement, file an issue with a fixture. The project's bias is to land characterization tests for known gaps before claiming the gap is closed. ## License MIT ## Author Built by [Joe Petrucelli](https://headlessmode.com) — technologist, AI agent security, 25 years building and securing enterprise systems.
标签:DevSecOps, MITM代理, SOC Prime, 上游代理, 人工智能, 代码安全, 开发工具, 数据可视化, 本地扫描, 漏洞枚举, 用户模式Hook绕过, 自定义脚本, 请求拦截, 配置检查