Skip to content

inspect-scanner

CLI scanner that audits a public GitHub repository or organization and produces structured JSON and HTML reports with health, maintainability, quality, security, and dependency signals. Part of the inspect-software auditing/certification platform.

The scanner does not clone or execute target-repository code. It reads public GitHub API data and raw manifest files, and it queries supported public package registries. OpenSSF Scorecard may run locally against the public repository as a best-effort security input.

Install

uv sync          # or: pip install -e .

Usage

inspect-scan https://github.com/pallets/flask
inspect-scan pallets/flask -o report.json
inspect-scan git@github.com:pallets/flask.git --compact

# Single-file HTML report (score-focused, human-readable)
inspect-scan pallets/flask -o report.json --html report.html

# Re-render HTML from a previously saved JSON report — no network needed
inspect-scan report.json --html report.html

# Scan an organization (profile + repository portfolio)
inspect-scan psf
inspect-scan https://github.com/python

# Store JSON + HTML under ./storage with standardized names
inspect-scan pallets/flask --storage
inspect-scan psf --storage D:/audit-reports

# Enable/disable parts of the scoring methodology (see below)
inspect-scan pallets/flask --disable-category security --disable-metric popularity
inspect-scan pallets/flask --config scan-config.json --html report.html

Scan configuration (enabling / disabling metrics)

Any part of the methodology — a component, a metric, or a whole category — can be switched off for a scan. Disabled items are removed from scoring and the remaining weights renormalized (never counted as zero), so scores stay on the 1–100 scale. The configuration is embedded in the report (config) and summarized in a Scan configuration section of the HTML.

inspect-scan pallets/flask --disable-category security          # drop a category
inspect-scan pallets/flask --disable-metric popularity          # drop a metric
inspect-scan pallets/flask --disable-component documentation:Wiki  # drop a component
inspect-scan pallets/flask --config scan-config.json            # from a file (+ flags merge on top)
// scan-config.json
{
  "disabled_categories": ["security"],
  "disabled_metrics": ["popularity"],
  "disabled_components": { "documentation": ["Wiki"] }
}

Category/metric keys and component names are listed in docs/metrics.md.

OpenSSF Scorecard (security metric)

The Security metric is backed by OpenSSF Scorecard, a neutral, tool-agnostic security standard — so a project earns credit for the practice (any dependency-update tool, any SAST, signed releases, least- privilege workflow tokens, no known-vulnerable deps…), not for a specific vendor's config file. Checks Scorecard can't determine are excluded from the score, never counted as zero.

This needs the scorecard CLI on your PATH:

# e.g. via Go, Homebrew, or a release binary — see the Scorecard README
go install github.com/ossf/scorecard/v5@latest

The scan reuses the same GitHub token it already resolves. Scorecard is best-effort: if the binary is missing, times out, or fails, the security metric falls back to coarse file checks and a warning is emitted. Skip it with --no-scorecard (faster), or disable the whole metric with --disable-metric security_posture.

Report storage

--storage [DIR] writes both report formats into a standardized layout:

storage/
  repos/<owner>__<repo>.json|.html    e.g. repos/pallets__flask.html
  orgs/<login>.json|.html             e.g. orgs/psf.html

Names are sanitized to filesystem-safe characters and lowercased. The storage root comes from (highest precedence first): the --storage DIR argument, the SCANNER_STORAGE variable (environment or .env), then ./storage. Setting SCANNER_STORAGE enables storage even without the flag. One file pair per target — a rescan overwrites.

GitHub token

Unauthenticated GitHub API access is limited to 60 requests/hour (a scan uses ~12). Provide a token to raise the limit to 5000/hour — a fine-grained token with public-repo read access is enough. The scanner resolves it from, in order of precedence:

  1. --token CLI argument
  2. GITHUB_TOKEN or GH_TOKEN environment variable
  3. GITHUB_TOKEN or GH_TOKEN in a .env file in the working directory

For workers that scan many repositories, set GITHUB_TOKENS to a comma-separated list of secondary PATs. The primary GITHUB_TOKEN remains first; when GitHub reports a rate limit, the scanner retries the request with the next token. Token values are never logged. (see .env.example; .env is gitignored)

inspect-scan pallets/flask --token ghp_...   # explicit
export GITHUB_TOKEN=ghp_...                  # environment
cp .env.example .env                         # or a local .env file

What's in the report

The report has two strictly separated layers (schema defined as Pydantic models in src/scanner/models.py):

  • data — raw facts observed from the GitHub API: repo metadata, the owning account's profile (organization or user), popularity, commit/release activity, contributors and bus factor, issue/PR counts, community profile, file-tree signals (CI, tests, linting, security policy, lockfiles, dependency manifests, and the declared dependency list parsed straight from those manifests — name + version constraint, no registry lookup yet), AI-readiness signals (agent-instruction files, bootstrap/typecheck/ container configs, machine-readable interfaces), and — for repos that publish a package — registry facts from PyPI / npm / Packagist / crates.io / RubyGems / Hex (downloads, versions, deprecation). No judgement, no scoring.

  • metrics — standardized scores, each an integer 1..100 mapped to a band (critical / at_risk / weak / moderate / good / excellent / exceptional). Fifteen metrics grouped into weighted categories, each with its own rolled-up score, plus a weighted overall that is additionally calibrated against the distribution of the public record (see src/scanner/calibration.py):

    Category Weight Metrics
    Vitality 0.21 development_activity, release_discipline
    Community & Adoption 0.17 popularity, community_health, ecosystem_adoption
    Sustainability & Governance 0.23 maintainer_resilience, responsiveness, stewardship, package_maintenance
    Engineering Quality 0.19 engineering_practices, documentation
    Security 0.16 security_posture × high_risk_jurisdiction_exposure
    AI Readiness 0.04 ai_agent_context, ai_verify_loop, ai_code_legibility, ai_interfaces

    stewardship scores who backs the repo: organization-owned projects (especially with a GitHub-verified domain and reach) score higher than single-personal-account projects. ecosystem_adoption and package_maintenance read real package-registry data (downloads, publish recency, deprecation) for repos that publish to PyPI / npm / Packagist / crates.io / RubyGems / Hex — see docs/ecosystems.md. security_posture is backed by OpenSSF Scorecard (tool-agnostic; see below). high_risk_jurisdiction_exposure is the internal key for an offline, privacy-preserving high-risk jurisdiction exposure signal. It uses high-confidence self-published public profile evidence within the Russia, Iran, and North Korea policy scope; it adjusts Security and overall rating and never improves either. AI Readiness carries a deliberately small 4% weight — sized, together with the calibration curve's top saturation, so a repository with no agent tooling can still score 100/100. Every metric echoes the raw inputs it was computed from.

The --html flag renders the report into a single-file HTML page focused on the score: an overall gauge with the standardized band scale, a category radar chart, a dedicated Ownership section, and metric cards grouped by category with plain-language explanations, per-criterion pass/fail breakdowns, and the exact inputs used. Styling/charts/icons load from CDNs (Chart.js, Lucide, Google Fonts); the page degrades gracefully offline.

Both layers are independently versioned (schema_version for the structure, metrics_version for the scoring methodology). Full documentation:

Notes on semantics:

  • File-based signals are heuristics from the git tree of the default branch; they indicate presence, not quality.
  • Metrics are signals, not warranties — publicly visible practices, not a code audit. Missing data is excluded and renormalized, never scored as zero.

Development

uv sync --extra dev
uv run pytest

Exit codes

  • 0 — report produced (warnings, if any, go to stderr)
  • 1 — GitHub API failure (rate limit, network, repo not found)
  • 2 — invalid input (unparseable repo URL)

Contributing

Pull requests are welcome — read CONTRIBUTING.md first. It explains one thing that is not obvious from the outside: this repository is published from a private workspace, so a merged change comes back with a different commit hash. A first pull request also needs CLA.md signed, which is a licence grant, not a copyright assignment.

Participation is covered by the Code of Conduct.

Security

The scanner never clones or executes the code it audits, but it does parse untrusted input from repositories and registries. Report vulnerabilities privately — see SECURITY.md, not a public issue.

Licence

GNU Affero General Public License v3.0 or later — see LICENSE.

The network clause is the point: run a modified scanner as a service and you owe its users your changes. Running it on your own machine, in your CI, or against your own dependencies carries no such obligation.

A commercial licence is available for use AGPL does not suit — mail@inspect.software.

src/scanner/data/jurisdiction_places.json is derived from GeoNames data under CC BY 4.0; attribution and the details are in NOTICE.