All notable changes to ca9 will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
0.5.0 (2026-06-27)
- supply-chain: add package feed runtime protections (#31) (65dbdd4)
- vet: flag workflow backdoor payloads (#29) (661b200)
0.4.0 (2026-05-27)
- ingest: add SARIF evidence normalization (#21) (ea44a43)
- inventory: add npm package-lock reader (#19) (f63f12f)
- vet: add malware and workflow incident checks (#28) (0c6d6a1)
- scan: harden evidence defaults (#17) (fa17091)
- vet: unstack incident checks from hunt cleanup (#27) (864be3f)
- Coverage verdicts preserve valid execution despite malformed ancillary metadata and consider positive execution before static import absence. Production observations prevent unsupported absence-based suppressions.
- Dependency review retains structural lock errors, inspects known nested and lifecycle executable targets, and compares npm module format, directory bins, implicit native install hooks, and default entrypoints. Manifest declarations remain comparable independently of executable inspection gaps. Report URL redaction preserves the identity of hidden changes.
- The npm metadata gateway revalidates required feed evidence for each metadata response and reports a blocking evaluation failure instead of silently forwarding metadata when configured policy requires blocking.
-
ca9 review --base FILE --head FILEcompares npm v2/v3 dependency updates using lockfile occurrence identity, verified release artifacts, lifecycle and entry-point declarations, and stable static observations. Markdown and JSON reports distinguish new changes from existing findings and keep incomplete artifact comparisons explicit. -
ca9 protectprovides one read-onlyca9.protect.v1posture report for package-manager enforcement coverage, feed state, expiring exceptions, package-policy decisions, and local GitHub Actions trust boundaries, with table, JSON, Markdown, and SARIF output. The composite GitHub Action now supportscommand: protect. -
Runtime preflight now supports local pip
-rrequirements and-cconstraints, including nested repository-bound includes, markers, exact constraints, and strong hash evidence. Unsafe sources, cycles, escaping paths, and gateway-bypass argument forms fail closed. -
Package policy now supports scoped
[[exceptions]]with required owner, reason, and expiry. Applied exceptions are recorded byvet, preflight, and npm/PyPI gateways; known-malware decisions remain non-overridable. -
ca9 scripts audit(for npm v12,latest/GA July 8, 2026) strictly audits every script-bearing npm v2/v3 lockfile occurrence, including non-registrypreparehooks, while excluding workspaces and keeping external file sources in review. Malformed or unmaterializable entries hard-error; registry and command identity comes from trusted lock-source evidence. v1 is deny/review-oriented: even hash-verified native-build shapes remain review until executable/GYP provenance is established. Its command emitter requires an exact installed-tree match and trusted identity/version, uses--allow-scripts-pinfor approvals, and otherwise emits POSIX/PowerShell-compatible comments instead of commands. -
Lockfile-backed runtime preflight for
npm ciand its clean-install aliases, including direct and transitive package policy checks before npm starts. -
Native Grype and OSV-Scanner JSON report parsers with auto-detection, advisory metadata, mixed-ecosystem handling, fixtures, and integration documentation.
-
ca9 vet --malware-querynow treats explicit malicious-package advisories and malware-labeled GHSA/OSV records as blocking malware findings, including npm packages. -
ca9 vet --scan-workflowsscans GitHub Actions workflow files for riskypull_request_target, OIDC token, broad write-permission, mutable action-ref, cache trust-boundary, and source-clone patterns.
- Unknown and non-Python scanner findings remain explicitly inconclusive instead of receiving Python reachability reasoning, and OSV multi-interval affected ranges are preserved.
- Coverage evidence now distinguishes reported statements from missing, partial, empty, or excluded-only affected scope. Strict mode keeps test non-execution inconclusive regardless of overall coverage percentage; balanced dynamic verdicts require explicit statement evidence. OpenVEX exports dynamic verdicts as
under_investigationrather thannot_affected.
- Advisory metadata normalization now preserves aliases, CWE/CPE IDs, advisory source URLs, published/modified timestamps, OSV cache freshness, and package URLs across JSON, SARIF, and OpenVEX outputs where input data provides them.
- GitHub Pages documentation deployment workflow with generated sitemap and crawler metadata.
- Policy-ignored findings from accepted risks and baselines now remain visible in JSON
ignored_results, Markdown/HTML ignored-finding sections, SARIF suppressed results, and OpenVEXca9.policy_ignoredmetadata while staying excluded from exit-code decisions. - README and docs now describe the current repo-first dependency inventory behavior, current model dataclasses, and API reachability verdict semantics.
- GitHub Action - reusable
duriantaco/ca9composite action forscanandcheckworkflows, SARIF upload, OpenVEX/action-plan artifacts, accepted risks, baselines, and optional fail-on-findings enforcement. - Accepted-risk policy files - TOML/JSON exceptions with optional version, owner, reason, and expiry fields.
- Baseline and new-only gating -
--baselineand--new-onlyoptions for gating only newly reachable or inconclusive findings. - Markdown and HTML reports -
--format markdownand--format htmlfor PR comments, build artifacts, and human review. - Dependency inventory improvements - Pipfile/Pipfile.lock support, requirements constraint pinning, and
pyproject.tomloptional dependency parsing. - Reachability hints - statically recoverable dynamic import detection plus Typer command and Celery task entry point detection.
- Release automation - manual GitHub release workflow with SemVer validation, version bumping, changelog guard, package build, GitHub release creation, and PyPI trusted publishing.
- SEO and integration docs - dedicated pages for OSV, Snyk, Dependabot, Trivy, pip-audit, SARIF, OpenVEX, SBOM, MCP, CI/CD, proof standards, and positioning.
- Structured docs metadata - SoftwareApplication and SoftwareSourceCode JSON-LD for the documentation site.
- Benchmarks and release checklist scaffolding - reproducible benchmark methodology and release/growth checklist pages.
- Expanded PyPI metadata with project URLs and security/search keywords.
- Updated public docs and README to reflect current parser support, CLI options, output formats, exit codes, and optional integrations.
- Wired CLI, SARIF, OpenVEX, and docs structured data to the package version.
- Documented that policy overlays produce filtered reports and summarize ignored findings in warnings.
- Corrected repository and documentation URLs to
duriantaco/ca9. - Added warnings for
--new-onlywithout a usable baseline and for empty accepted-risk files. - Removed stale documentation claims about supported formats and dependency footprint.
- MCP server —
ca9-mcpexposes ca9 as an MCP tool server withcheck_reachability,scan_dependencies,check_coverage_quality, andexplain_verdicttools. Install withpip install ca9[mcp]. - API call site coverage — when coverage data is available, ca9 now checks whether specific vulnerable API call sites were executed in tests, not just whether the package was executed.
- Coverage completeness weighting — confidence scoring now factors in overall test coverage percentage. High coverage (80%+) makes dynamic absence signals more trustworthy; low coverage reduces their weight.
- Coverage completeness in evidence —
coverage_completeness_pctfield added to the Evidence model, surfaced in JSON/SARIF output. - 34 new tests (349 total).
- Verdict precision for API calls — when vulnerable API calls are found but call sites are not executed in tests, verdict is now INCONCLUSIVE instead of REACHABLE.
- Code cleanup — removed inline ternary expressions across parsers, scanner, CLI, and report modules for readability.
- Removed AI-generated comments — stripped redundant phase comments and docstrings from engine.
- Dead code in
_api_usage_boost— ternary assignment was immediately overwritten by an identical if/else block. - Report column width — deduplicated repeated if/else blocks for table column width calculation.
- Vulnerability intelligence layer — 21 curated rules across 6 packages (Django, Jinja2, PyYAML, requests, urllib3, Werkzeug) mapping advisories to 46 vulnerable API targets.
- API-level reachability — AST-based scanner detects actual calls to vulnerable functions/classes/methods, not just package imports. Resolves aliased imports, attribute chains, and
from X import Ypatterns. - API evidence in verdicts — JSON/SARIF output now includes
api_targets,api_usage_seen,api_usage_hits(with file, line, snippet), andintel_rule_ids. - API-driven verdict upgrades — finding vulnerable API calls can upgrade a verdict to REACHABLE even without coverage data.
- API-aware confidence scoring — API usage boosts reachable confidence (+10–15), strengthens unreachable when no usage found (+8), penalizes contradictions.
- 46 new tests (315 total).
- Evidence model — every verdict now carries structured evidence (version range, import status, dependency kind, coverage, affected component source/confidence).
- Confidence scoring — verdict-aware 0-100 confidence score. Signals boost or penalize depending on whether they support the verdict direction.
- Affected component inference — commit analysis, curated mappings, regex extraction, and class name resolution each produce confidence-scored component matches.
- OSV caching — vulnerability details cached to
~/.cache/ca9/osv/with 24h TTL. Commit file lists cached to~/.cache/ca9/commits/with 7-day TTL. - Concurrent OSV fetches —
ThreadPoolExecutorfor parallel vulnerability detail lookups (--max-osv-workers, default 8). - Offline mode —
--offlineflag returns results from cache only, no network requests. --refresh-cache— clears OSV cache before fetching.--show-confidence— display confidence score in table output.--show-evidence-source— display evidence extraction source in table output.- SARIF fingerprints — stable
ca9/v1fingerprints based on(vuln_id, package, version, verdict). - SARIF/JSON evidence — confidence score and full evidence object included in SARIF properties and JSON output.
- GitHub token support —
GITHUB_TOKENenv var for commit fetch rate limit mitigation. - 55 new tests (260 total).
- PEP 440 version parsing — replaced naive tuple-based comparison with
packaging.version.Version. Handles pre-releases, post-releases, dev releases, epochs, and local versions correctly. - Parser deduplication — widened dedupe key from
vuln_idto(vuln_id, package_name, package_version). Same CVE across different packages is now preserved. - Engine refactored to evidence-first —
collect_evidence()gathers all signals into an Evidence object,derive_verdict()applies deterministic policy on evidence. - Bare import no longer over-claims submodule reachability —
import requestssetssubmodule_imported=None(unknown) instead ofTrue. - Commit fetch warnings propagated — GitHub fetch failures now flow into
Evidence.external_fetch_warningsand degrade confidence scores. - Confidence scoring is verdict-directional —
package_imported=Trueboosts REACHABLE confidence but penalizes UNREACHABLE, and vice versa. Same forversion_in_range,coverage_seen,submodule_imported.
- Python 3.11 f-string syntax error — ditto marks in table grouping used backslashes inside f-strings, which is only valid in 3.12+.
- Linting errors — unused imports, Yoda conditions, non-idiomatic conditionals.
- Duplicate
extract_affected_component()call — was computed twice per vulnerability (once incollect_evidence, once inanalyze). Now computed once and passed through. --offlinewas a no-op —_query_from_cache_only()was a stub. Now scans cache directory and matches cached vulns to requested packages.- Version ranges without
introducedskipped silently — ranges missing the introduced field were dropped entirely.
- CI/CD exit codes —
0clean,1reachable CVEs found,2inconclusive only. - SARIF 2.1.0 output —
--format sariffor GitHub Security tab integration. .ca9.tomlconfig file — auto-discovered from CWD upward, sets default CLI options.- Trivy parser —
ca9 check trivy.jsonnow works out of the box. - pip-audit parser —
ca9 check pip-audit.jsonnow works out of the box. - 42 new tests (205 total).
- Core verdict engine with four-state decision tree:
REACHABLE,UNREACHABLE_STATIC,UNREACHABLE_DYNAMIC,INCONCLUSIVE. - Static analysis via AST import tracing — scans all
.pyfiles in a repo and checks whether vulnerable packages are imported. - Dynamic analysis via coverage.py JSON data — checks whether vulnerable package code was actually executed during tests.
- Snyk parser — parses
snyk test --jsonoutput (single-project and multi-project formats). - Dependabot parser — parses GitHub Dependabot alerts JSON (API export format).
- Auto-detection of SCA report format — no need to specify which tool generated the report.
- PyPI-to-import name mapping for ~30 common packages with mismatched names (Pillow/PIL, PyYAML/yaml, scikit-learn/sklearn, etc.).
- CLI (
ca9command) with table and JSON output formats, file output, and coverage data support. - Protocol-based parser architecture — new SCA formats can be added without modifying existing code.
- Zero runtime dependencies for library core (stdlib only). CLI requires
click. - 59 tests covering parsers, AST scanner, coverage reader, engine verdicts, CLI, and edge cases.