MCPcopy Index your code
hub / github.com/alpha-omega-security/scrutineer

github.com/alpha-omega-security/scrutineer @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
2,368 symbols 9,711 edges 196 files 799 documented · 34%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

scrutineer

A local tool for scanning open source repositories for security vulnerabilities and managing the disclosure process. You add a repo by URL, scrutineer runs a pipeline of claude-code skills against it, and presents the results in a web UI where you can triage findings, identify maintainers, and track disclosures.

Quick start

You need Go 1.26+ and Docker running.

git clone https://github.com/alpha-omega-security/scrutineer
cd scrutineer

Authenticate Claude with one of two options:

Option A: Claude Code subscription (Max, Pro, Team, or Enterprise) -- generate a long-lived OAuth token with the Claude CLI:

claude setup-token
export CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-...

Option B: Anthropic API key from console.anthropic.com:

export ANTHROPIC_API_KEY=sk-ant-api03-...

Then start scrutineer:

export ANTHROPIC_BASE_URL=https://...  # optional: custom API endpoint
go run ./cmd/scrutineer -skills ./skills

Then open http://127.0.0.1:8080.

Large batches pause and resume automatically at a Claude rate-limit wall, with no extra configuration. When claude-code (running with a Claude subscription token) reports an account-level rate/usage limit, scrutineer pauses the remaining queued scans, reads the reset time from the rate_limit_event claude-code emits in its own output, and re-queues the paused batch after that reset. If no reset is reported (for example an API-key account, which does not emit those events), the batch stays paused for manual resume. The most recent per-window status is shown on the /usage page.

Scrutineer detects Docker and starts using it automatically: each scan runs in an ephemeral container with a read-only source mount and an egress allowlist proxy. The runner image (ghcr.io/alpha-omega-security/scrutineer-runner) is pulled on first use, so the first scan is slower while it downloads. If Docker isn't available scans run directly on the host with no isolation; see the Security section before doing that.

Click Add repository in the sidebar, paste a git HTTPS URL, and scrutineer enqueues the triage skill. To scan a maintained branch instead of the default, fill the Branch field (it suggests the remote's branches as you type and also accepts a tag or commit), or append a /tree/<branch> suffix to the URL; the suffix also works one-per-line when bulk-importing. Triage then enqueues the rest of the pipeline in parallel. Metadata and package lookups finish in seconds; the security deep-dive takes a few minutes depending on repo size. Open the repo page and switch to the Scans tab to watch progress, or wait for the Findings tab to fill in.

To onboard a whole GitHub org at once, open Add multipleImport a whole org and enter the org (or user) login. Scrutineer fetches every repository and queues each one with the default scan set, skipping forks and archived repos unless you opt in. Duplicates already in the database are skipped. Set GITHUB_TOKEN to raise GitHub's unauthenticated rate limit when importing large orgs.

You can also scan a directory on disk, useful before pushing, or for code not hosted on a git forge. Paste an absolute path (/path/to/project) in the same Add repository field. Scrutineer copies the directory into a per-scan workspace and runs the default skill set; skills that need a forge URL or ecosyste.ms enrichment (advisories, exposure, fork, maintainers, metadata, packages, public-issue, report-upstream) are skipped automatically. Symlinks are recreated as-is rather than dereferenced during the copy; in container mode their targets then resolve inside the container, so host files reached only through such a link are not visible to skills. Under --no-container the kernel dereferences them normally, so only point scrutineer at trees you trust.

The optional analysis tools (semgrep, zizmor, git-pkgs, brief) are bundled in the runner image, so you don't need them installed locally when the container runner is in use.

Git authentication

Scrutineer shells out to git clone with no explicit token passing, so it uses whatever credentials are already configured on the host: SSH keys, credential helpers, gh auth login, a .netrc file, or the macOS keychain.

To scan private repos, make sure git clone https://github.com/org/repo works in your terminal before adding the URL to scrutineer. If it does, scrutineer can clone it too.

Common setups:

# GitHub CLI (easiest)
gh auth login

# Git credential helper
git config --global credential.helper store   # or osxkeychain / manager-core

# SSH-based clone URLs are not supported -- scrutineer only accepts https:// URLs.
# Use a credential helper to authenticate HTTPS clones instead.

When running inside Docker (docker run ...), the container has no access to host credentials. Mount a credential store or set GIT_ASKPASS to provide access to private repos from inside the container.

When the containerised runner is active (the default when a container runtime is available), each scan runs in a separate container but the clone happens on the host before the source is mounted in. Host credentials are used for the clone; the container never sees them.

Features

Scanning and analysis

  • Skill-based scan pipeline -- every scan is a claude-code skill on disk (SKILL.md + schema + optional scripts). The default pipeline for a new repo is itself a skill (triage) that enqueues the others; edit its SKILL.md to change what runs
  • Structured findings -- vulnerability reports parsed into a database with severity, CWE, location (linked to source), affected versions, and a six-step analysis trace
  • Threat model view -- the project's security contract (components, entry-point trust table, properties provided and disclaimed, known non-findings) rendered from the threat-model scan, falling back to the deep-dive's boundaries and sink inventory on older repositories
  • Dependency exploration -- dependency and dependent tables with one-click import to scan any package's source repository
  • Package registry data -- downloads, dependents, versions, and registry links for every published package
  • Known advisories -- existing CVEs and security advisories pulled automatically
  • Maintainer identification -- model-backed skill combining commit history, issue/PR activity, and registry ownership to identify who to contact for disclosure
  • CWE catalogue -- embedded MITRE CWE data with tooltips on finding tables and full descriptions on finding pages
  • Reachability analysis -- trace sinks found in dependencies through application code to see which are actually reachable
  • Rescan dedup -- findings carry a content fingerprint so re-running a scan updates existing rows instead of creating duplicates; same-fingerprint hits within one scan collapse to a single finding with a +N expandable location list, and findings that stop appearing are marked "not seen" with a miss count

Triage and disclosure workflow

  • Finding workflow -- guided triage flow from new through verification, disclosure, and publication with human gates at each step
  • Cheap-classifier pre-sort -- the revalidate skill auto-enqueues for High/Critical deep-dive findings and every imported finding, emitting true_positive / false_positive / already_fixed / uncertain plus an optional severity adjustment; a true_positive on a High/Critical finding chains into verify automatically
  • Audit queue -- random sample of recent low and false-positive verdicts at /audit so the operator can spot-check the classifier; each review records an agreement-or-overturn verdict on the finding
  • Exploited-in-the-wild flag -- analyst-only yes/no field on findings with free-text evidence, surfaced on the finding page, in the OSV database_specific block, in CSAF audit notes, and in markdown report exports
  • Breaking-change classifier -- the breaking-change skill runs over a suggested-fix diff plus the top dependents, recording breaking / non_breaking / unknown with a rationale and the list of affected dependents
  • Mitigation guidance -- the mitigate skill drafts short-term workarounds and an optional semgrep rule per finding, separate from the code fix
  • CVSS v3.1 and v4.0 -- both vectors stored side by side with derived scores; analyst form, OSV/CSAF exports, and the disclose skill all carry both forward, with the v4 metric set kept distinct from v3
  • Release watch -- the release-watch skill closes the gap between fix-landed and fix-shipped: once a finding reaches fixed, the skill polls upstream releases and records the release tag, URL, and timestamp when it appears
  • CNA matching -- identify the CVE Numbering Authority whose scope covers a repo so disclosures go to the right contact
  • Upstream reporting -- file a finding on the upstream repository through GitHub's private vulnerability reporting with the proposed patch attached, and push the fix to the temporary private fork when GitHub grants access. A PVR report is hard to unsend; before pointing this at an external repository, run it once end-to-end against a repository you control with PVR enabled to confirm the body shape and patch attachment land the way you expect. When upstream has no PVR available, follow the runbook in docs/disclosure-fallback.md

Imports and exports

  • SBOM import -- upload a CycloneDX or SPDX document, resolve each component to a source repository, and queue scans automatically
  • Finding import -- POST SARIF, CSV, markdown, or minimal-JSON findings from external scanners and pentest reports into the same workflow as native scans, with fingerprint dedup against re-imports
  • Free-form ingest -- when the format sniffer in /api/v1/import cannot place a payload, the ingest skill normalises it against the source checkout (resolving locations) before it enters the findings table
  • CSAF export -- download any finding as a schema-validated CSAF 2.0 advisory document
  • OSV export -- download any finding as a schema-validated OSV record, aligned with the OSS-SIRT advisory template (credits, CWE IDs, withdrawn, SEMVER ranges, CVSS v3 + v4 severity entries)
  • JSONL export -- stream all findings or scans as line-delimited JSON for ingestion elsewhere
  • Markdown report export -- download a single consolidated report.md per repository or organisation
  • Disclosure bundle -- download bundle.tar.gz per finding: OSV, CSAF, markdown report, patch.diff, and a manifest naming the contents; ready to hand to a coordinator or attach to a private email when filing outside GitHub PVR

Operational

  • Containerised runner -- optional per-scan container isolation with read-only source mounts, dropped capabilities, and an authenticated egress allowlist proxy
  • Skill HTTP API -- running skills can call back into scrutineer to list prior scans and enqueue further skills; surface documented in openapi.yaml
  • Live updates -- SSE streaming of scan logs and status changes, no polling
  • Organisation rollup -- repos, findings, and maintainers grouped by owning org, with per-org markdown exports
  • Usage tracking -- per-scan token and cost figures plus a /usage page totalling spend per skill; on a Claude subscription token, a rate-limit wall auto-pauses the batch and resumes it after the reported reset, with per-window status shown on /usage. Optionally (downgrade_on_overage), once the account crosses into overage the model tier falls back from max/high to the mid tier for new scans until overage clears (typically when the window resets) -- announced in the log, on the jobs page, and on /usage
  • Themes -- six colour themes plus a light/dark/system toggle, set on the Settings page

The default pipeline

When a repo is added, the triage skill is enqueued. Its SKILL.md lists the skills to trigger. The bundled skills live in skills/:

Skill What it does
triage Orchestrates the default scan set via the scrutineer API
metadata Fetches repo metadata from repos.ecosyste.ms
packages Looks up published packages from packages.ecosyste.ms
advisories Fetches known security advisories
dependencies Runs git-pkgs list to index every manifest
sbom Runs git-pkgs sbom for a CycloneDX SBOM
maintainers Model-backed analysis identifying real maintainers and contact routes
repo-overview Runs brief --json for a structured project summary
subprojects Enumerates monorepo packages/workspaces so deep-dives can be scoped to a sub-path
threat-model Derives the project's security contract (components, entry-point trust table, claimed and disclaimed properties) for the deep-dive to load
semgrep Static analysis mapped into findings shape
vuln-scan High-recall model-backed static candidate scan adapted from Anthropic's defending-code reference harness
zizmor GitHub Actions workflow audit mapped into findings shape
ingest Normalizes external reports in arbitrary formats into findings when /v1/import cannot recognise the payload
security-deep-dive The model-backed audit producing structured findings
advisory-deep-dive Re-audits every past advisory against its fix commit for a fix bypass, an incomplete fix, or the same bug class in sibling code the patch never touched; the deep-dive scoped to the advisory space
finding-dedup Compares open findings and marks overlapping reports as duplicates
verify Re-checks one finding against current HEAD; records reproduces / fixed / can't-reproduce
revalidate Cheap read-only c

Extension points exported contracts — how you extend this code

SkillRunner (Interface)
SkillRunner executes one skill scan. Tests and the container-backed runner substitute the process launch without touchin [7 …
internal/worker/claude.go
Harness (Interface)
A Harness is the agent CLI the container runner execs to drive a skill. It owns everything that varies between claude-co [2 …
internal/worker/harness.go

Core symbols most depended-on inside this repo

Error
called by 396
internal/web/org_import.go
ServeHTTP
called by 315
internal/worker/egress.go
Handler
called by 296
internal/web/server.go
String
called by 264
cmd/scrutineer/main.go
Set
called by 181
cmd/scrutineer/main.go
Error
called by 98
internal/worker/skill.go
Open
called by 97
internal/db/db.go
writeAPIError
called by 83
internal/web/api.go

Shape

Function 1,746
Method 419
Struct 190
TypeAlias 9
FuncType 2
Interface 2

Languages

Go99%
TypeScript1%
Python1%

Modules by API surface

internal/web/server_test.go153 symbols
internal/web/server.go98 symbols
internal/worker/skill_parsers_test.go54 symbols
internal/db/db.go50 symbols
internal/worker/container.go49 symbols
internal/worker/worker.go43 symbols
internal/web/finding_csaf.go41 symbols
internal/worker/skill.go40 symbols
internal/web/repo_report.go40 symbols
internal/skills/parse_test.go40 symbols
internal/web/api_export_test.go36 symbols
internal/worker/container_test.go34 symbols

For agents

$ claude mcp add scrutineer \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page