Skip to content

Supported Formats

ca9 supports two kinds of input:

  • Existing SCA reports from tools such as Snyk, Dependabot, Trivy, and pip-audit.
  • Repository or environment dependency inventory scanned directly against OSV.dev with ca9 scan.
  • Package inventory from manifests and fyn.lock with ca9 inventory and ca9 vet.

It can also emit multiple output formats for humans, CI systems, code scanning, VEX workflows, and SBOM enrichment.

Input: SCA reports

Snyk

Generate a Snyk report:

snyk test --json > snyk-report.json
ca9 check snyk-report.json --repo .

Supported layouts:

  • Single-project JSON with a vulnerabilities array.
  • Multi-project JSON with an array of project objects, each containing vulnerabilities.

Dependabot

Export Dependabot alerts with the GitHub CLI:

gh api repos/{owner}/{repo}/dependabot/alerts > dependabot.json
ca9 check dependabot.json --repo .

ca9 extracts advisory IDs, aliases, CWE IDs, package names, vulnerable ranges, severities, titles, source URLs, timestamps, and dependency relationship data where present.

Trivy

Generate a Trivy JSON report:

trivy fs --format json --output trivy.json .
ca9 check trivy.json --repo .

ca9 reads package vulnerability findings from Trivy result entries and preserves dependency metadata when the report contains it.

pip-audit

Generate a pip-audit JSON report:

pip-audit --format json --output pip-audit.json
ca9 check pip-audit.json --repo .

ca9 maps pip-audit vulnerability entries into its common Vulnerability model for reachability analysis. pip-audit aliases are preserved so PYSEC findings can still match CVE-oriented reachability rules.

Input: Direct OSV scanning

ca9 scan queries OSV.dev without requiring a separate SCA tool:

ca9 scan --repo .

The scanner prefers exact dependency versions from the repository. Dependencies without resolved versions are skipped by default; pass --allow-env-fallback only when you intentionally want ca9 to use versions from the current Python environment. OSV vulnerability details populate advisory aliases, CWE/CPE IDs, source URLs, published/modified timestamps, and cache freshness metadata in JSON, SARIF, and OpenVEX outputs.

Useful scan options:

ca9 scan --repo . --offline
ca9 scan --repo . --refresh-cache
ca9 scan --repo . --max-osv-workers 16

Input: Package inventory and fyn.lock

ca9 inventory and ca9 vet use the normalized inventory model. When fyn.lock is present, ca9 reads it natively and extracts resolved packages, dependency edges, artifact URLs, artifact hashes, source registries, groups, and markers.

ca9 inventory --repo . -f json
ca9 vet --repo . -f json

Without fyn.lock, ca9 falls back to native manifest readers.

Output formats

Table

Default human-readable terminal output:

ca9 check snyk-report.json --repo .

Add more evidence columns with:

ca9 check snyk-report.json --repo . --show-confidence --show-evidence-source

JSON

Machine-readable report with summary, verdicts, evidence, warnings, confidence scores, optional enrichment data, and ignored_results for accepted-risk or baseline findings that did not affect the gate:

ca9 check snyk-report.json --repo . -f json -o ca9-report.json

ca9 inventory -f json emits ca9.inventory.v1. ca9 vet -f json emits ca9.vet.v1, including inventory summary, supply-chain findings, policy decisions, warnings, and artifact scan counts.

Each result also includes an advisory object with normalized identity metadata:

{
  "ecosystem": "pypi",
  "aliases": ["CVE-2024-12345"],
  "cwes": ["CWE-79"],
  "cpes": [],
  "source": "osv.dev",
  "url": "https://osv.dev/vulnerability/GHSA-...",
  "published_at": "2024-01-01T00:00:00Z",
  "modified_at": "2024-01-02T00:00:00Z",
  "fetched_at": "2024-01-03T00:00:00Z",
  "cache_stale": false
}

SARIF

Use SARIF for GitHub code scanning or SARIF-compatible security tools. Accepted-risk and baseline findings are emitted as suppressed SARIF results so audit tools can still see them:

ca9 check snyk-report.json --repo . -f sarif -o ca9.sarif

OpenVEX

Generate OpenVEX exploitability statements. Policy-ignored findings remain in the VEX document with ca9.policy_ignored metadata:

ca9 check snyk-report.json --repo . -f vex -o openvex.json

Compare VEX documents over time:

ca9 vex-diff --base previous.openvex.json --head current.openvex.json

Markdown and HTML

Generate human-readable reports for pull request comments, build artifacts, or internal review:

ca9 check snyk-report.json --repo . -f markdown -o ca9-report.md
ca9 check snyk-report.json --repo . -f html -o ca9-report.html

Remediation plan

Generate prioritized remediation actions:

ca9 check snyk-report.json --repo . -f remediation -o remediation.json

Action plan

Generate a CI/CD decision object:

ca9 check snyk-report.json --repo . -f action-plan -o action-plan.json
ca9 action-plan snyk-report.json --repo . -o action-plan.json

SBOM enrichment

Enrich CycloneDX or SPDX JSON with ca9 reachability verdicts:

ca9 enrich-sbom sbom.json --repo . --coverage coverage.json -o sbom.ca9.json

AI-BOM capability output

Scan for AI assets and capabilities:

ca9 capabilities --repo . -f json -o aibom.json

Diff and gate capability changes:

ca9 cap-diff --base base-aibom.json --head head-aibom.json --md cap-diff.md
ca9 cap-gate --diff cap-diff.json --policy ca9-policy.yaml

Adding a new parser

ca9 uses a protocol-based parser architecture. To add support for another SCA tool:

  1. Create a new file in src/ca9/parsers/.
  2. Implement the SCAParser protocol.
  3. Register the parser class in src/ca9/parsers/__init__.py.
from typing import Any

from ca9.models import Vulnerability


class MyToolParser:
    def can_parse(self, data: Any) -> bool:
        return isinstance(data, dict) and "my_tool_version" in data

    def parse(self, data: Any) -> list[Vulnerability]:
        return [
            Vulnerability(
                id=item["id"],
                package_name=item["package"],
                package_version=item["version"],
                severity=item.get("severity", "unknown"),
                title=item.get("title", ""),
                description=item.get("description", ""),
            )
            for item in data["findings"]
        ]