Supply-Chain Vetting¶
ca9 vet is the broader package-security command. It builds on normalized inventory and
checks dependency risk beyond CVE reachability.
The current implementation is intentionally local-first:
- fyn is optional; ca9 reads
fyn.locknatively when present. - package code is never installed, imported, or executed.
- artifact downloads are explicit and hash-verified by default.
- OSV malware advisory queries are opt-in.
- Real incident replay fixtures track current coverage and gaps.
Incident Replay¶
Replay recent real incidents against ca9's current supported surfaces:
The fixtures live in tests/fixtures/incidents/ and currently cover May 2026 npm package
compromise, PyPI import-time malware, and GitHub token/codebase exfiltration patterns. See
docs/proof/incident-coverage.md for the current matrix.
Demo Fixture¶
For a screenshot-ready report, run the local supply-chain fixture:
The fixture generates a fyn.lock with local hash-pinned wheels and shows three blocking
findings: dependency confusion, suspicious .pth startup execution, and a denied license.
It also writes demo/supply_chain/ca9-vet.json for docs or CI artifact screenshots.
ca9 supply-chain report for .../demo/supply_chain/repo
Packages: 4 | Edges: 3 | Findings: 3 | Block: 3 | Warn: 0
Artifact scans: 3 | Skipped artifacts: 0
Inventory¶
Inspect the normalized package inventory:
When a repository has fyn.lock, inventory includes:
- resolved package names and versions
- direct/transitive/project dependency kind
- dependency edges
- groups, markers, and extras where available
- artifact URLs, hashes, upload times, sizes, and registries
- source evidence for each package and edge
Without fyn.lock, ca9 falls back to native manifest readers for pyproject.toml,
requirements*.txt, Pipfile, uv.lock, and poetry.lock.
Basic Vetting¶
Run local metadata checks:
This checks:
- untrusted package indexes
- missing artifact hashes
- missing artifact metadata
- source-only install risk
- mutable package sources
Direct dependencies from untrusted indexes are blocking findings by default. Weaker local metadata signals are warnings unless policy support is expanded.
Artifact Static Analysis¶
Run artifact-based malicious package heuristics:
By default, ca9 only downloads artifacts that have hashes in the inventory. It verifies the hash, safely unpacks wheels/sdists, rejects path traversal or unsafe archive links, and scans files statically.
Current blocking rules include:
.pthstartup executionsitecustomize.py/usercustomize.pysuspicious startup behavior- install-time
setup.pyprocess/network/eval/exec behavior - encoded payload decode plus execution
- credential access near outbound network code
- top-level import-time risky behavior
Suspicious process execution outside setup/import startup paths is marked for investigation.
Use this only if you want ca9 to download package artifacts:
Artifacts without hashes are skipped unless you explicitly opt in:
Malicious Advisory Query¶
Query OSV for known malicious-package advisories:
ca9 treats OSV MAL-* and PYSEC-MAL-* advisories as blocking malware findings. Use
--offline to restrict the query path to cached OSV data.
Dependency Confusion¶
Protect internal package names from resolving from public or unexpected indexes:
ca9 vet --repo . \
--internal-package 'acme-*' \
--private-index https://packages.acme.internal/simple
An internal direct dependency that resolves outside the configured private indexes is a blocking dependency-confusion finding. Transitive matches are marked for investigation.
Use --trusted-index to define package indexes that are generally trusted:
ca9 vet --repo . \
--trusted-index https://pypi.org/simple \
--trusted-index https://packages.acme.internal/simple
License Policy¶
Gate denied licenses from wheel/sdist metadata:
ca9 reads:
- wheel
.dist-info/METADATA - sdist
PKG-INFO License-ExpressionLicenseClassifier: License :: ...
Denied licenses on direct dependencies are blocking findings. Denied licenses on transitive dependencies are investigation findings until richer policy configuration lands.
Warn when scanned artifacts do not declare a known license:
License checks require artifact metadata, so --deny-license and --require-known-license
implicitly enable artifact collection. The same safe artifact rules apply: hashes are
required by default, archives are safely unpacked, and package code is not executed.
JSON Output¶
The JSON schema includes:
- inventory summary
- findings with signal type, severity, package key, evidence, and metadata
- decisions with action, policy ID, and reason
- artifact scan counts and skipped artifact counts
Example signal types:
untrusted_registrydependency_confusionmalwarepython-startup-pth-execpython-startup-customize-execsetup-install-execencoded-executioncredential-network-exfiltrationimport-time-risky-behaviorsilent-process-executiondenied_licenseunknown_license
Exit Codes¶
| Code | Meaning |
|---|---|
0 |
No blocking supply-chain findings. |
1 |
Blocking supply-chain findings were found. |
Warnings and investigation findings remain visible in output but do not currently fail the default gate unless they are represented as blocking decisions.
Current Limits¶
ca9 does not yet implement every dependency attack class. The current vet path covers
the core local gates first: malicious package behavior, dependency confusion/internal
source policy, artifact integrity basics, OSV malware advisory matching, and license
policy.
Still planned:
- typosquatting and namespace confusion
- lockfile poisoning diffs
- PyPI release-age/yanked/project metadata
- maintainer or repository hijack signals
- provenance/Sigstore/SLSA checks
- richer policy files