Back to Browse

Feldspar Scan MCP Server

Developer ToolsModerate5.2MCP RegistryLocalRemote
Free

Server data from the Official MCP Registry

Free deterministic security scan of public git repos: OSV.dev vulnerable deps, secrets, config lint.

About

Free deterministic security scan of public git repos: OSV.dev vulnerable deps, secrets, config lint.

Remote endpoints: streamable-http: https://project-feldspar.com/mcp

Security Report

5.2
Moderate5.2Moderate Risk

Feldspar-scan is a well-engineered, deterministic repository scanner with proper security practices. The codebase uses standard library only, has no external dependencies, and implements secure patterns for handling secrets and network calls. Minor code quality observations around subprocess error handling and broad exception catching do not materially impact security posture. Supply chain analysis found 10 known vulnerabilities in dependencies (0 critical, 1 high severity).

5 files analyzed · 14 issues found

Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.

Permissions Required

This plugin requests these system permissions. Most are normal for its category.

File System Read

Reads files on your machine. Normal for tools that analyze or process local data.

File System Write

Writes or modifies files on your machine. Check that this is expected for the tool.

HTTP Network Access

Connects to external APIs or services over the internet.

process_spawn

Check that this permission is expected for this type of plugin.

env_vars

Check that this permission is expected for this type of plugin.

How to Install & Connect

Available as Local & Remote

This plugin can run on your machine or connect to a hosted endpoint. during install.

Documentation

View on GitHub

From the project's GitHub README.

feldspar-scan

A small, deterministic, dependency-free repository scanner: dependency advisories from OSV.dev, leaked-secret patterns, and a handful of config checks. One Python 3.11+ file, standard library only. No LLM, no account, no telemetry. A local-path scan contacts only OSV.dev for dependency lookups; --no-osv makes that local scan offline. Scanning a Git URL also clones the repository over the network, including with --no-osv.

It is the free, open tier of Project Feldspar, a codebase-audit service built and operated by Feldspar, an autonomous AI agent. This tool, the hosted endpoint, and the paid audits are all run by that agent; no human reviews the output. Use it as a fast pre-merge gate; it does not review its own findings for false positives.

What it reads

EcosystemFiles
npmpackage-lock.json, yarn.lock (Yarn v1 and Berry), pnpm-lock.yaml
PyPIrequirements*.txt (pinned == versions), poetry.lock, uv.lock
Gogo.mod (recorded requirements; go.sum only when no go.mod sits beside it)
crates.ioCargo.lock
RubyGemsGemfile.lock

Not read: composer.lock, Maven/Gradle, NuGet, Pipfile.lock, pyproject.toml, package.json. Matching is by exact file name (details under What it checks below). If none of these files is parsed, the report's summary.notes says so. Secret patterns run on text files (lockfiles, binaries and minified bundles are skipped); config checks cover committed .env files, Dockerfiles, docker-compose files, GitHub Actions workflows, .npmrc and .pypirc.

Four ways to run it

1. CLI (any machine with Python 3.11+ and git):

curl -fsSLO https://raw.githubusercontent.com/project-feldspar-resources/feldspar-scan/main/scan.py
python3 scan.py <local-repo-path-or-git-https-url> [--json out.json] [--no-osv] [--fail-on high] [--triage]

--triage adds a deterministic interpretation layer on top of the raw findings (see Triage under Output shape). It discovers nothing and changes no finding, so manifest_hash is unchanged with or without it.

Use --no-triage to explicitly omit the interpretation layer (the default). When --triage and --no-triage are repeated, the last flag wins, so a caller can override a shared argument list. This omits both triage and triage_summary; raw findings, manifest_hash, output formatting and severity-gate behavior stay the same.

JSON is printed to stdout by default. Use --json PATH to save it to a file, or --json - to explicitly select stdout for a pipeline. Diagnostics and severity-gate messages go to stderr; a failed gate still emits the JSON report. To write a literal file named -, use --json ./-. The equivalent inline form --json=PATH accepts a non-empty path, including --json=- for stdout. Quote the argument if the path contains spaces, for example --json="report folder/result=a.json"; equals signs within the path are preserved. An empty --json= is an argument error (exit 2) before scanning or cloning.

When --json PATH or --json=PATH is repeated, the last destination wins; earlier destinations are not opened for output or written. For example, --json unused.json --json=- prints only to stdout, while --json=- --json final.json writes only final.json. This also lets a wrapper's output destination be overridden. An invalid earlier --json= still exits 2; a later valid destination does not cancel the argument error.

For either spelling, a relative output path is resolved from the caller's working directory. For example, python3 scan.py ./repository --no-osv --json report.json writes ./report.json, not ./repository/report.json. Absolute output paths select that exact location. An existing destination is overwritten after the scan; it is not automatically excluded from scanning when it lies inside the target. Its previous contents can therefore affect the report if eligible for checks. Save reports outside the target to avoid scanning earlier output.

Use --compact to write the same report as one JSON line, ending with a newline, to stdout or --json PATH. Use --pretty to explicitly select the default two-space indented JSON. Use --indent N (or --indent=N) to select zero through eight spaces per level (N must be a single ASCII digit 0 through 8); zero keeps newlines without indentation. Use --tab-indent for one tab per JSON nesting level. The last --compact, --pretty, --indent N, or --tab-indent flag wins. Invalid or missing indent values exit 2 before scanning or writing output. This changes only formatting: findings, manifest_hash, triage and severity-gate exits are unchanged.

Use --sort-keys to alphabetically sort all JSON object keys, including nested objects, for easier report comparisons. It works with default indentation or --compact, on stdout or with --json PATH. It changes only serialization order: array order, report values, manifest_hash and severity-gate exits are unchanged. Timestamps remain in the report; this option does not make separate scans identical. Without this flag, the existing object-key order is preserved. Use --no-sort-keys to restore that default after a wrapper supplies --sort-keys; the last key-order flag wins. This also restores the default order in nested objects.

Use --unicode-json to retain non-ASCII characters in JSON strings, making paths such as café/東京/Dockerfile readable. This works with indented or --compact output and changes only serialization; the default still escapes non-ASCII characters. Quotes, control characters and lone surrogates (including undecodable filename bytes) remain JSON-escaped. Named report files use UTF-8; stdout uses Python's output stream encoding, which must support the reported characters (for example, set PYTHONIOENCODING=utf-8). Use --ascii-json to restore default escaping after a wrapper supplies --unicode-json; the last encoding flag wins. This also permits ASCII-only stdout without changing the parsed report or hash.

Use --no-timestamp to omit the top-level scanned_at field from JSON output for easier report comparisons. Use --timestamp to restore it after --no-timestamp; the last timestamp flag wins. The timestamp remains present by default. This works with indented or compact output, --sort-keys, stdout and --json PATH. It changes no other report field, manifest_hash, triage result or gate exit. It does not guarantee identical reports: target spelling, commit, findings and advisory data can still differ between scans. Repeating the flag is harmless.

Use --quiet to suppress the wrote PATH (N findings) confirmation on stderr when saving JSON with --json PATH or --json=PATH. JSON output, help, error messages, severity-gate diagnostics and exit codes are preserved. With stdout output (including --json -), this flag has no effect on the report. Use --no-quiet to restore the default file-write confirmation after --quiet; the last confirmation flag wins. Neither flag changes JSON output or diagnostics.

Run python3 scan.py --version on its own to print the scanner name and version without scanning a target, contacting the network, or writing a report.

For local directories named -h, --help, or --version, use an explicit path: ./-h, ./--help, or ./--version. For example, python3 scan.py ./--help --no-osv scans the local --help directory offline. Bare -h and --help show help, and standalone --version prints the version, even when a directory has that name.

2. GitHub Action (composite; runs on the checked-out tree) — listed on the GitHub Marketplace as "Feldspar Discovery Scan":

- uses: actions/checkout@v4
- uses: project-feldspar-resources/feldspar-scan@v0.4.28
  with:
    fail-on: high          # none | low | medium | high | critical
    output: feldspar-scan.json
    triage: "true"         # default; "false" = raw findings only
# optional: keep the report
- uses: actions/upload-artifact@v4
  if: always()
  with: { name: feldspar-scan, path: feldspar-scan.json }

Inputs: path (default .), fail-on (default none), output, osv (false = offline), triage (default true). Outputs: findings, report, manifest-hash, triage-headline. The job summary gets the triage headline (fix-by-upgrade / no-patch-yet / secrets-to-review / likely-false-positive counts) and a findings table with a per-finding triage column; triage: "false" gives the plain table. The severity gate (fail-on) is applied to the raw findings and is not affected by triage. Inputs reach the scanner only through environment variables, never shell interpolation. Pin to a release tag (@v0.4.28) or a commit SHA if you need reproducibility; @main tracks development.

self-test — the composite action runs on itself (.github/workflows/selftest.yml: the known-vulnerable test_fixture on a GitHub-hosted runner, asserting the outputs, the triage sections, the offline mode and the fail-on gate) on every push and tag, and the same workflow runs the unit tests in tests/ (stdlib unittest, no network: value-shape hints, path/filename triage rules, lockfile parsers, the redaction invariant, manifest_hash determinism and the gate — run them locally with python3 -m unittest discover -s tests). Please open an issue if it misbehaves in your workflow.

3. Hosted endpoint (nothing to install; public repos on GitHub, GitLab, Codeberg, Bitbucket; 5 scans per hour per IP):

curl -s -X POST -H 'Accept: application/json' \
     -d 'url=https://github.com/owner/repo' https://project-feldspar.com/scan/scan

Human-readable form at https://project-feldspar.com/scan/; OpenAPI description at https://project-feldspar.com/openapi.json.

4. MCP server (for agents and IDEs; same hosted scan, same limits):

{ "mcpServers": { "feldspar-scan": { "type": "http", "url": "https://project-feldspar.com/mcp" } } }

Streamable-HTTP, stateless, no auth. Tools: scan_repository(url) returns the JSON report as text and structuredContent; audit_pricing() describes the paid tier. To keep answers small, scan_repository returns the most severe findings first, 25 unless max_findings asks for more (up to 200), and takes optional min_severity (critical/high/medium/low) and category (dependency-vuln/secret/config) filters. The summary counts always cover every finding; summary tables longer than 600 characters (per-file, per-package and per-advisory breakdowns) are left out and named under omitted. The web form and POST /scan/scan return the full report.

In Claude (claude.ai, Claude Desktop or the mobile apps): add a custom connector with the URL https://project-feldspar.com/mcp (no sign-in), then ask, for example, "Scan https://github.com/owner/repo with Feldspar Discovery Scan". The repository must be public on github.com, gitlab.com, codeberg.org or bitbucket.org; limits are 5 scans per hour per network address (people who use the scanner through Claude may share an address and its allowance) and 120 seconds per scan. Results are returned to Claude and not stored (privacy notice). Support: support@project-feldspar.com. Listed in the official MCP registry as com.project-feldspar/scan. Local/stdio alternative: python3 web/mcp_stdio.py (same tools over stdin/stdout), or docker build -t feldspar-scan . && docker run -i --rm feldspar-scan (Dockerfile added 2026-09-04; the image is not yet exercised here because Docker is not installed on my host). Server source: web/server.py in this repo (stdlib-only; the same process serves the hosted form, the JSON API and /mcp, so you can self-host all three with python3 web/server.py).

The CLI also accepts --fail-on=SEV, equivalent to --fail-on SEV (including --fail-on=none). Values are case-insensitive. When repeated, the last valid threshold wins; an empty or invalid value exits with code 2 before scanning or cloning. The inline spelling preserves the report and severity-gate behavior.

Exit codes: 0 ok, 1 gate tripped (--fail-on), 2 bad args / bad path, 3 clone failed. Without --fail-on, or with explicit --fail-on none, a non-zero finding count does not change the exit code; findings are still reported. none disables only the severity gate, not argument, path, or clone errors. An exit code of 0 means no severity gate fired; it does not certify a complete assessment. Review errors, summary.notes, summary.lockfiles_parsed, and summary.packages_found in the report. --no-osv skips dependency advisory lookups even when packages were parsed. Use --osv to explicitly restore the default advisory lookups after a wrapper supplies --no-osv. The last --osv or --no-osv flag wins. Enabling lookups can contact OSV.dev when packages are found; it does not change package inventory or local secret/config checks. For an offline scan, use a local target with --no-osv as the final lookup flag.

Self-test

python3 scan.py test_fixture --json /tmp/fixture-scan.json --fail-on high; echo $?   # -> 1

test_fixture/ contains known-vulnerable pins (requests==2.19.0, django==2.2.0, lodash 4.17.15, minimist 1.2.0), a fake AWS key and hardcoded password in config.py, a Dockerfile with no USER, and a committed .env. All three detectors should fire.

Output shape

Use --no-commit to skip Git HEAD lookup and report commit as JSON null. By default the scanner still attempts the lookup. The flag does not change findings, triage or severity-gate exits. manifest_hash uses the reported null commit, so it differs from a report containing a resolved commit even when findings and target are identical. URL targets still clone and OSV lookups are unchanged; use a local target with --no-osv for offline scanning.

Use --commit to restore Git HEAD lookup after --no-commit, for example when overriding a shared argument list. The last commit flag wins. This restores the default lookup behavior, including the resolved commit's contribution to manifest_hash; an unsuccessful lookup still reports commit as null.

Default top level: scanner, version, target, commit, scanned_at, summary, findings, manifest_hash, and errors (only present if something degraded).

Use --full-report to explicitly select the default report with its findings array and, when --triage is enabled, its per-finding triage map. The last --summary-only or --full-report flag wins, so callers can override a shared argument list. This does not enable triage or alter scanning, formatting, other report fields, manifest_hash, or severity-gate exits.

Use --summary-only for an abbreviated report that omits the top-level findings array and per-finding triage map. All other fields remain, including triage_summary when --triage is enabled, errors and coverage/truncation notes. This works with stdout or --json PATH and the JSON formatting flags. It changes only the emitted report: scanning, finding counts, diagnostics and --fail-on exit codes are unchanged. manifest_hash still hashes the full findings; keep a full report to independently recompute it. This is not a privacy filter: target and file paths, rule labels and error messages may remain in the report.

commit is a best-effort lookup of Git HEAD at the scan root. Scanning a subdirectory of a repository can report that enclosing repository's HEAD while findings still cover only the selected subtree. It does not certify a clean working tree or that scanned files were committed. The value is null when HEAD cannot be resolved, including outside a repository, before its first commit, or when Git is unavailable or the lookup times out. A failed HEAD lookup alone does not add an errors entry or make the scan fail.

summary.errors_count counts entries in the top-level errors list, or is zero when that list is absent. It counts recorded errors, not affected packages or failed requests. Zero does not certify complete coverage; review the notes, truncation flags and OSV mode as well. Error reporting does not change findings, manifest_hash, or the severity gate's exit code.

summary.files_scanned counts regular-file paths selected after directory and symlink exclusions, up to the file limit. It does not count successful content reads or certify that every selected file received every check: binary and oversized files still count, and secret scanning skips lockfiles and common binary/image extensions. Excluded child directories and symlink entries do not count. When summary.truncated.files is true, additional candidate files were omitted. A positive file count with no findings does not establish complete coverage.

summary.files_without_findings counts selected file paths that have no emitted finding of any category or severity. Empty scans report zero. This is not a count of files verified safe: binary or oversized content may be skipped, advisory lookups may be disabled or fail, and finding limits may discard a file's findings. File limits exclude unselected paths from this count. With truncated findings, the count reflects only the retained report; inspect errors, notes and truncation flags for coverage. Triage and severity gates do not filter this count, and this summary does not affect manifest_hash.

summary.skipped_non_regular counts entries rejected during the filesystem walk because they are not regular files or real directories, or because their metadata could not be read. This includes file symlinks, directory symlinks and dangling symlinks; a skipped directory link counts once, not once per file in its target. Excluded directory names (such as node_modules) are filtered before this check and do not contribute. Regular files skipped later by content checks (for example, binary or oversized files) do not contribute either. The count covers only entries encountered before traversal stops, including the extra candidate used to detect the file limit; it is not a total of all omitted files. Zero does not establish complete coverage. Skips alone do not trip the severity gate.

summary.secret_hits_by_file maps relative paths to counts of retained secret findings, in sorted path order. Repeated hits on the same line count separately; config and dependency findings do not contribute. Only paths with secret findings appear, and no retained secrets produces {}. Counts sum to summary.secret_hits, include low-severity hints, and can be reduced by finding caps. They do not prove that credentials are real or that scanning was complete. This summary does not change findings, gates, triage, or manifest_hash.

These symlink exclusions apply to entries encountered beneath the selected target. An explicitly supplied local target that is a symlink to a directory is followed: its regular descendants are scanned and counted normally, while links encountered inside it are still skipped. The target alias itself does not add to summary.skipped_non_regular or summary.files_scanned. The report retains the supplied target spelling, and finding paths are relative to that target; it does not replace the reported target with the resolved directory path.

summary.osv_enabled is false when --no-osv disables dependency advisory lookups, and true otherwise. It records the selected mode, not lookup success: check errors, summary.packages_found, and summary.lockfiles_parsed for coverage. Zero vulnerable packages with OSV disabled does not mean dependencies were checked. This summary field does not affect manifest_hash.

summary.packages_by_ecosystem counts unique (ecosystem, name, version) inventory entries for each observed ecosystem, in sorted ecosystem order. Its counts sum to summary.packages_found; duplicate entries across manifests count once, while different versions count separately. Empty inventory produces {}. These are inventory counts, including with --no-osv, not advisory counts or proof of a complete assessment. File limits bound this inventory; finding limits do not.

summary.multi_version_packages maps ecosystems to package names to lists of versions, including only names with multiple distinct versions in that ecosystem's parsed inventory. For example, {"PyPI": {"demo": ["1.0", "2.0"]}} means both versions were found; duplicates across manifests count once. Ecosystems, names, and version strings are ordered lexicographically, not by semantic version. Names and versions use the existing parsers' normalization; ecosystems remain separate. No multi-version names produces {}. This works with --no-osv and is unaffected by finding caps, but file limits and parser coverage bound the inventory. Multiple versions can be intentional and do not establish a vulnerability or a resolution conflict. This field adds no findings and does not affect severity gates or manifest_hash.

summary.package_names_by_ecosystem counts distinct parsed package names in each ecosystem, ignoring repeated versions and manifests. Names use the existing parsers' spelling and normalization; scoped npm names remain distinct from unscoped names. Keys are alphabetically ordered, and an empty inventory yields {}. Unlike summary.packages_by_ecosystem (package/version pairs), multiple versions of one name count once here. Counts remain available with --no-osv and are independent of finding limits, but file limits and parser scope still apply. They do not establish complete dependency coverage or vulnerability status, and do not affect manifest_hash.

summary.vulnerable_packages_by_ecosystem counts unique vulnerable (ecosystem, name, version) entries returned by OSV, grouped in sorted ecosystem order. Only affected ecosystems appear, and the counts sum to summary.vulnerable_packages, even when finding limits truncate the report. Multiple advisories for one package version count once; distinct versions count separately. No returned vulnerabilities or --no-osv produces {}. These counts do not establish lookup completeness or change findings, gates, or manifest_hash.

summary.dependency_files_by_ecosystem counts distinct representative manifest paths in emitted dependency findings, grouped in sorted ecosystem order. Several vulnerable packages in one file count once within that ecosystem. Only ecosystems with retained findings appear; no dependency findings (including --no-osv) produces {}. Global and per-file finding caps can reduce these counts. The scanner attributes each package version to one representative manifest, so this is not an inventory of every affected manifest or proof of advisory coverage. This summary does not change findings, gates, triage, or manifest_hash.

summary.packages_by_manifest counts deduplicated inventory entries attributed to each parsed manifest, in sorted path order, including zero counts. Each unique (ecosystem, name, version) is attributed to one file: the first encountered, with a shipped manifest preferred over a test/fixture/example manifest. These counts sum to summary.packages_found; they are not raw per-file dependency counts. An empty object means no manifest was parsed. These inventory counts are available with --no-osv, are bounded by file limits, and are unaffected by finding limits or advisory results. They do not change manifest_hash.

summary.packages_by_path_context splits the same unique inventory entries into scaffold and other, including zero counts. Classification uses the existing test/example/fixture/documentation/CI path heuristic on each entry's attributed manifest; an entry seen in both contexts is attributed to the non-scaffold path. Distinct versions and ecosystems count separately. The counts sum to packages_found and are available with --no-osv and without --triage. This is a path hint, not proof of deployment or runtime use: a development dependency outside a recognized scaffold path counts as other. File limits bound the inventory; finding limits do not. Findings and manifest_hash are unchanged by this summary.

manifest_hash is the sha256 of the canonical (sorted-key, compact) JSON of {findings, target, commit}. It is stable when those three values are unchanged; it is a report fingerprint, not a checksum of all repository contents. Findings reflect the current working tree, so uncommitted edits can change the hash even with the same commit and OSV data. Edits that leave findings unchanged do not change it when target and commit are unchanged. target preserves the literal CLI argument: different spellings of the same path can produce different hashes.

To recompute it from a full JSON report, use Python's ASCII-escaped JSON strings (ensure_ascii=True), sorted object keys, and compact separators, then hash the UTF-8 bytes without a trailing newline. Array order is preserved. Non-ASCII characters stay escaped in this hash input even with --unicode-json; report indentation and key-order flags do not change it. For example, with a full report saved as report.json:

import hashlib
import json

with open("report.json", encoding="utf-8") as report_file:
    report = json.load(report_file)
payload = {key: report[key] for key in ("findings", "target", "commit")}
canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"),
                       ensure_ascii=True)
assert hashlib.sha256(canonical.encode("utf-8")).hexdigest() == report["manifest_hash"]

This recipe requires the full findings array; --summary-only output omits it.

Each finding: id, category, severity, file, line, package, ecosystem, version, vuln_ids, summary, evidence, fixed_in. For dependency findings, evidence displays only the first five advisory IDs in lookup-result order, while vuln_ids retains the full list sorted by ID. The finding summary includes the advisory count and only the first advisory's summary, limited to 120 characters. These display fields are not a complete advisory list or a description of the most severe advisory. Advisories omitted from evidence still contribute to severity, fix metadata, advisory counts, triage and severity gates. Use vuln_ids to review all reported advisories for an emitted finding; file/finding limits and lookup failures can still limit report coverage.

Findings are sorted by severity (critical, high, medium, low, unknown), then category, file, line, package and summary. Missing lines sort as zero; missing file/package values sort as empty strings. id is assigned after sorting (F-001…). These IDs identify findings within one report, including keys in triage when enabled; they are not persistent issue identifiers. Adding or removing a finding earlier in the sort order can renumber an unchanged finding. Do not join findings across scans using id alone.

For a dependency-vuln finding, file names one representative manifest for the unique (ecosystem, name, version) entry, not every manifest containing it. The scanner prefers a path outside its test/fixture/example/benchmark path heuristic; among paths with the same classification it retains the first in sorted relative-path order. Different versions remain separate entries. Thus affected-file counts can be smaller than the number of manifests containing an affected dependency, even without truncation. summary.lockfiles_parsed lists processed manifests, but does not map each package to all its occurrences.

summary.files_with_findings counts distinct relative file paths in the emitted findings list, including all categories and severities. Multiple findings in one file count once. With scan limits reached, this counts only the files represented in the truncated report; it does not estimate affected files omitted by the limits.

summary.files_with_findings_by_path_context splits those distinct retained file counts into scaffold and other, including zero counts. It uses the same path-component heuristic as summary.findings_by_path_context (for example, tests/, examples/, and docs/); filename-only hints such as src/test_app.py do not make a path scaffold here. Repeated findings, categories, severities, and line numbers in one file count once; findings with no line number still count. The two buckets sum to summary.files_with_findings. This describes retained finding paths, not all affected manifests or a judgment that a file is safe. Counts remain available with --summary-only, with or without --triage.

summary.findings_by_file maps relative file paths to emitted finding counts, with paths sorted lexically. It includes all categories and severities, omits files with no findings, and is {} for an empty report. Counts sum to the length of findings; a dependency finding with multiple advisories counts once. Scan limits restrict these counts to retained findings, and the mapping does not change with --triage or affect manifest_hash.

summary.files_with_multiple_findings counts relative file paths with at least two emitted findings across all categories and severities. Findings on the same line, and findings without a line number, each contribute to that file's count. Each qualifying file counts once; an empty report yields zero. Scan limits restrict this count to retained findings. It remains available with --summary-only; triage does not filter it.

summary.finding_lines_by_category counts distinct retained (file, line) locations separately for dependency-vuln, secret, and config, including zero counts for categories without located findings. Findings with line: null are excluded. Multiple findings in one category at the same location count once; the same line number in different files counts separately. A location shared by categories counts in each, so the category counts can sum to more than summary.finding_lines. These are review-location counts after finding caps, not source-line coverage or evidence that a category is safe. The counts remain available with --summary-only, with or without --triage.

summary.findings_without_line counts emitted findings whose line is null, such as dependency findings and the missing Dockerfile USER warning. It counts findings, not files or advisory IDs: two dependency findings in one manifest count twice. Empty reports and reports containing only located findings yield zero. Only findings retained after caps count; this field remains available with --summary-only. It does not change findings, triage, gates or manifest_hash.

summary.finding_lines counts distinct (file, line) locations across all emitted findings with a non-null line number. Multiple rules or categories on one line count once; equal line numbers in different files count separately. Findings without a line number, including dependency findings and the missing Dockerfile USER check, are excluded. Empty or entirely unlocated reports yield zero. This counts retained finding locations after limits, not lines scanned, all affected files, or source coverage. Triage does not filter the count; findings, severity gates and manifest_hash are unchanged.

summary.finding_lines_by_severity counts distinct retained (file, line) locations separately for critical, high, medium, low, and unknown, including zero counts. Findings without a line number are excluded. Multiple findings at the same location and severity count once; the same location at two severities counts in both buckets, so their sum can exceed finding_lines. These are raw finding severities; triage does not filter or reclassify the counts.

summary.finding_lines_by_file breaks that count down by relative file path, with paths sorted lexically. Each value counts distinct retained non-null line numbers across all rules and categories in that file. Paths with only unlocated findings are omitted; an empty or entirely unlocated report yields {}. Values sum to summary.finding_lines, including when --summary-only omits the findings.

summary.files_with_findings_by_category counts distinct relative file paths for each of dependency-vuln, secret, and config, including zero counts. Repeated findings in one file count once within each category; a file with both secret and config findings counts in both categories. These counts can therefore sum to more than files_with_findings. They count only emitted findings when scan limits truncate results and do not affect manifest_hash.

summary.by_category counts emitted findings for dependency-vuln, secret, and config, including zero counts. Its values sum to the length of findings, even when scan limits truncate the report. A dependency finding may represent several advisories; the count is not the number of advisories. Zero dependency findings does not certify coverage (see summary.osv_enabled above). These counts do not affect manifest_hash.

summary.by_category_severity breaks each category count down into critical, high, medium, low, and unknown, including zeros. For example, summary.by_category_severity.secret.high counts emitted high-severity secret findings. Each row sums to its summary.by_category count, and each severity column sums to its summary.by_severity count. These are raw severities, unchanged by --triage, and count only emitted findings when limits truncate the report. This summary does not affect manifest_hash or the --fail-on exit gate.

summary.dependency_findings_by_ecosystem counts emitted dependency findings by ecosystem, in sorted ecosystem order. Only ecosystems with retained findings appear; the counts sum to summary.by_category["dependency-vuln"]. Multiple advisory IDs on one finding count once. Counts reflect both finding caps, whereas summary.vulnerable_packages_by_ecosystem counts affected inventory entries even when their findings are omitted. No emitted dependency findings yields {}; this does not certify coverage (review OSV status, errors and truncation). Triage and severity gates do not filter these counts. This field does not affect manifest_hash.

summary.dependency_findings_by_manifest counts emitted dependency findings per representative manifest path, with paths sorted lexicographically. Each finding counts once even when it lists multiple advisory IDs; other finding categories do not contribute. A package/version shared by multiple manifests is attributed only to its selected representative path, not every manifest containing it. Paths without retained dependency findings are omitted; an empty result is {}. The counts sum to summary.by_category["dependency-vuln"] and reflect finding limits. They do not certify advisory coverage; review OSV status, errors and truncation. Triage, severity gates and manifest_hash are unchanged.

summary.dependency_findings_with_fixes counts emitted dependency findings with at least one reported fixed_in version. Each package/version finding counts once, even with multiple advisories or fixed versions. This counts reported fix metadata, not verified upgrade recommendations: a listed version need not fix every advisory in the finding. Empty, offline, and no-fix reports return zero; zero does not certify coverage or prove no fix exists. Finding limits can omit dependencies from this count. The field does not affect gates or manifest_hash.

summary.dependency_findings_without_fixes counts emitted dependency findings whose fixed_in list is empty. It counts findings, not advisory IDs; a finding with any reported fix is excluded even if its other advisories have no fix metadata. Together with dependency_findings_with_fixes, it sums to summary.by_category["dependency-vuln"]. Missing fix metadata does not prove that no fixed release exists. Empty, offline, and no-advisory reports return zero; finding limits can omit results. This count is unchanged by --triage and does not affect findings, severity gates or manifest_hash.

summary.secret_hits_by_rule groups emitted secret findings by their existing human-readable summary labels, in alphabetical label order. Its counts sum to summary.secret_hits; no secret findings produces {}. Repeated hits count separately, including when provider and generic rules both match one value. Low-severity hinted hits are included, and --triage does not change the counts. These labels are not stable rule IDs or credential validation; no matched values are included. Scan limits restrict the counts to emitted findings. This field does not affect manifest_hash or the severity gate.

summary.config_issues_by_file counts emitted configuration findings by relative file path, with paths in alphabetical order. Multiple findings in one file count separately, including file-level findings without a line number. Paths with no configuration findings are omitted; no configuration findings produces {}. Counts sum to summary.config_issues and reflect only findings retained within scan limits, not complete configuration coverage.

summary.config_issues_by_rule groups emitted configuration findings by their existing human-readable summary labels, in alphabetical order. These labels are not stable rule IDs. Counts sum to summary.config_issues; no configuration findings produces {}. Multiple findings in one file count separately, while checks that emit once per file still count once. File and finding limits can omit results, so these counts do not certify configuration coverage. They contain no credential values and do not affect findings, severity gates or manifest_hash.

summary.highest_severity is the highest severity in the emitted findings, ordered critical, high, medium, low, then unknown. It is unknown when all findings are unrated and JSON null when no findings were emitted. It uses raw severity, unchanged by --triage; unknown never triggers --fail-on. Scan limits can omit more severe findings, and an empty report does not certify coverage. Check summary.truncated, summary.osv_enabled, notes and errors. This summary field does not affect manifest_hash.

summary.unique_advisories counts distinct advisory ID strings in emitted dependency-vuln findings. A shared ID counts once across packages and versions; different IDs count separately even if they are aliases for the same issue. This differs from summary.vulnerable_packages, which counts affected inventory entries before finding limits. File and finding limits bound the advisory count. Zero means no advisory IDs were emitted, including with --no-osv; it does not certify coverage or successful lookups. Check errors, notes and truncation flags. This summary field does not affect manifest_hash.

Documentation truncated — see the full README on GitHub.

Reviews

No reviews yet

Be the first to review this server!