Browse by type
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 agent skills against it inside a container, and presents the results in a web UI where you can triage findings, identify maintainers, and track disclosures. The agent CLI is pluggable: claude-code by default, or codex, opencode, or GitHub Copilot CLI with -backend.
You need one supported container runtime: Docker (the default), rootless Podman, or Apple's container CLI.
Install Scrutineer from Homebrew on macOS or Linux:
brew install scrutineer
Alternatively, download the Linux or macOS archive for your architecture from GitHub Releases, verify it against SHA256SUMS, and put scrutineer on your PATH. The macOS archives are currently unsigned and not notarized, so macOS may present a Gatekeeper warning even after you verify the checksum and GitHub build-provenance attestation.
To build or run from source instead, install Go 1.26+:
git clone https://github.com/alpha-omega-security/scrutineer
cd scrutineer
Authenticate the default claude-code backend with either a Claude Code subscription token (Max, Pro, Team, or Enterprise) generated by the Claude CLI:
claude setup-token
export CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-...
or an Anthropic API key from console.anthropic.com:
export ANTHROPIC_API_KEY=sk-ant-api03-...
Start a downloaded or installed binary with Docker:
scrutineer
Select another installed runtime explicitly:
scrutineer --runtime podman
scrutineer --runtime apple
The existing source-checkout command remains supported:
go run ./cmd/scrutineer -skills ./skills
Then open http://127.0.0.1:8080. The explicit -skills ./skills directory makes the checkout command useful while developing skills because it overrides the copies embedded in the binary. It is optional for ordinary use because Scrutineer ships its built-in skills and per-ecosystem runner profiles inside the executable.
You can also build a checkout-independent executable and run it from another directory:
go build -o scrutineer ./cmd/scrutineer
mkdir -p ~/.local/bin
install -m 0755 scrutineer ~/.local/bin/scrutineer
scrutineer
Precompiled binaries cover Linux and macOS on amd64 and arm64. Run scrutineer --version (or scrutineer version) to see the application version, source commit, build timestamp, and exact runner-image digest paired with that release.
The executable does not bundle Docker, Podman, or Apple's container CLI. Scrutineer remains a host application that asks the selected external runtime to launch an ephemeral runner container for each scan; it materialises the embedded profile Dockerfiles under the data directory when docker/profiles is not available from a source checkout. Content-addressed skill and profile bundles from older versions are retained because existing skill records may still reference their auxiliary files; incomplete extraction directories older than 24 hours are removed automatically. To run under codex, opencode, or copilot instead, see the Codex backend, Opencode backend, and Copilot backend sections; only the credential and -backend flag change.
Large batches pause automatically at an account-level rate limit or quota wall. When claude-code is running on a subscription token it emits a rate_limit_event carrying the reset time, and scrutineer re-queues the paused batch after that reset; API-key accounts and the codex/opencode/copilot backends report the error without a reset time, so those batches stay paused for manual resume from the /usage page, which also shows the most recent per-window status.
Scrutineer uses Docker by default: each scan runs in an ephemeral container with a read-only source mount and an egress allowlist proxy. The paired runner image (ghcr.io/alpha-omega-security/scrutineer-runner) is pulled on first use, so the first scan is slower while it downloads. A host without the selected runtime fails startup rather than silently weakening isolation; --no-container is the explicit, Claude-only escape hatch for running directly on a trusted host.
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 scan one package inside a monorepo, append #<sub/dir> to the URL -- e.g. https://github.com/rails/rails#activesupport (or paste a .../tree/<branch>/<sub/dir> URL). Scrutineer scopes the whole pipeline to that sub-folder and gives the sub-package its own page, findings, packages, advisories, disclosure channel, and export, instead of rolling every package up under the repository. By default a sub-package scan is confined to its own sub-folder (siblings pruned, .git kept), widening to the whole repository automatically if the sub-package cannot resolve its dependencies in isolation; set subproject_scope: soft to always scan against the whole tree, or monorepo_attribution: false to keep registry data repo-wide (see scrutineer.sample.yaml). The subprojects skill also auto-discovers a monorepo's sub-packages, so you can scan them from the repo page without composing URLs by hand. Findings, packages, advisories, the disclosure channel, and export are attributed per sub-package; dependents, maintainers, posture, and health stay repo-wide. There is no per-sub-package schedule -- to re-scan sub-packages on a cadence, drive the local API from cron or a systemd timer (see scripts/rescan-subprojects.sh).
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, archived repos (unless you opt in), and any URL already in the database. 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, check that git clone https://github.com/org/repo works in the same environment you launch scrutineer from before adding the URL.
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 and never enter the container.
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 findingbrowse all types & interfaces →
$ claude mcp add scrutineer \
-- python -m otcore.mcp_server <graph>