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.
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 multiple → Import 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.
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.
triage) that enqueues the others; edit its SKILL.md to change what runsthreat-model scan, falling back to the deep-dive's boundaries and sink inventory on older repositories+N expandable location list, and findings that stop appearing are marked "not seen" with a miss countrevalidate 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 so the operator can spot-check the classifier; each review records an agreement-or-overturn verdict on the findingyes/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 exportsbreaking-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 dependentsmitigate skill drafts short-term workarounds and an optional semgrep rule per finding, separate from the code fixdisclose skill all carry both forward, with the v4 metric set kept distinct from v3release-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/api/v1/import cannot place a payload, the ingest skill normalises it against the source checkout (resolving locations) before it enters the findings tablereport.md per repository or organisationbundle.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 PVRopenapi.yaml/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 /usageWhen 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 |
$ claude mcp add scrutineer \
-- python -m otcore.mcp_server <graph>