Policy
Six rule families and the supply-chain signals each registry supports. One evaluation per install.
A failing package is refused before it reaches the build, on rules you write.
-
01 · Monitor
Every rule ships here
decision recorded, install still succeeds
-
02 · You review the data
- what would have fired
- exceptions, with expiry
-
03 · Enforce
Refused or quarantined
audit record on every decision
Core rule families
Compose the rules that matter
| Rule family | Matches on |
|---|---|
| Vulnerability gating | CVE, CVSS, EPSS exploit probability, CISA KEV |
| License enforcement | SPDX identifier, direct and transitive separately |
| Version and age rules | minimum version, deprecated or EOL, semver range, minimum age, per-version cooldown |
| Provenance verification | npm provenance, Sigstore, sum.golang.org, PGP, InRelease / repomd |
| Client-context rules | prod vs dev, repository, CI job, region |
| VEX-aware exceptions | reviewer, reason, expiry; chainsaw exception create --cve --decision --vex-note |
Supply-chain attack signals
What CVE-based scanners miss
Up to 12 per ecosystem: 4 always on, the rest where each registry supports them. The 15 below span all ecosystems.
| Signal | Policy field | Named attack | Behaviour |
|---|---|---|---|
| Install-script exfiltration | hasInstallScript · installScriptFetchesRemote | PhantomRaven pattern | Lifecycle hooks that run remote fetches or decode base64 payloads. Refused before the hook fires. |
| Fresh-version cooldown | cooldownDays | Account-takeover class | Quarantines any version published inside your cooldown window. Absent date metadata fails open, never quarantining on a guess. |
| Maintainer-account takeover | publisherChanged | Axios v1.14.1 pattern | A surprise publisher on a popular dependency refuses pending review. npm, PyPI, RubyGems, NuGet and Maven. |
| Version-number anomalies | versionAnomaly · versionAnomalyKinds | Kinds: semver_regression, major_skip, timestamp_regression. | |
| Publish-velocity worm bursts | publishVelocityAnomaly | Shai-Hulud pattern | A rolling 24-hour counter per publisher. Threshold is tunable. |
| Reserved-namespace dependency confusion | reservedNamespaces | Birsan pattern | Your internal names, reserved against public registries. One-click curated starters per ecosystem. |
| Typosquat detection | isSuspectedTyposquat | BK-tree, homoglyph and word-reorder matchers across fifteen ecosystems. Dart/pub gets CVE and typosquat coverage only, no malicious-package prevention. | |
| Docker malware feed | isKnownMalicious (docker) | Matched by digest and by name-plus-tag. A hit drops the trust score to -100. | |
| Per-layer image enforcement | Container image analysis | Walks every image layer with Trivy; a clean tag no longer guarantees a clean image. | |
| OS-package hash-chain provenance | hasProvenance (apt/yum/dnf) | InRelease for APT, repomd.xml.asc for Yum/DNF. Debian and Fedora keyrings ship embedded. | |
| Linux distro CVE detection | distroCVE (alpine/debian/rhel/oracle) | Native detectors for Alpine, Debian, Red Hat and Oracle Linux, each distro stream on its own cadence. | |
| Hugging Face malware feed | isKnownMalicious (huggingface) | Bundled HF-native coordinate-match feed, shipped in-process. | |
| Repo liveness and ownership match | trustScoreMin | A composite trust score per package. Set the floor you're comfortable with. | |
| Checksum fail-closed enforcement | checksum mode | Log, quarantine or block per ecosystem. Tells a real mismatch from an upstream that never published a hash. |
Where refusals surface
Every fired rule lands in Findings
Coverage matrix
Attack class, mechanism and data source
One row per attack class. If a cell is empty for an ecosystem, it's empty in the product.
| Attack class · mechanism | Ecosystems | Data source |
|---|---|---|
Known vulnerability (CVE)Vulnerability gate matches CVE IDs and scores by CVSS, EPSS exploit probability, and CISA KEV membership. KEV cross-reference runs after the CVE merge so known-exploited issues sort to the top. | All 16 ecosystems | OSV, GHSA, NVD, FIRST EPSS, CISA KEV, Aqua Trivy DB |
TyposquattingBK-tree, homoglyph, and word-reorder matchers against curated popular-package seeds. Low-risk gate on APT/Yum/DNF. | 15 ecosystems (BK-tree); APT/Yum/DNF low-risk gate | Curated popular-package seeds per ecosystem |
Dependency confusionThe proxy refuses a public-registry package whose name matches a namespace you have reserved, so the Birsan substitution is stopped at the fetch with no per-package allow-listing. Evaluated on every ecosystem, but only for the patterns you declare: a recommended starter pack ships and the proxy warns at startup when none is applied, because your namespaces are not knowable in advance. | Universal (operator-declared namespaces) | Operator-declared reserved namespaces; recommended starter pack in configs/reserved_namespaces_defaults.yaml |
Known malwareDigest and name+tag match against the OpenSSF malicious-package and malware indexes, plus a bundled Docker and Hugging Face malware feed. | npm, PyPI, RubyGems, Cargo, Packagist, NuGet, Hugging Face, Docker, Swift | OpenSSF malicious-packages, OpenSSF malware feed, Docker malware feed, GHSA (Swift) |
Hidden Unicode / Trojan SourceRefuses packages carrying zero-width, bidi-override, or Unicode-tag characters (GlassWorm, Trojan Source). Bounded scan: 500 files / 50 MiB per artifact. | All artifact-bearing ecosystems | Static source scan (no external feed required) |
Install-script exfiltrationPhantomRaven-style detection flags lifecycle hooks that fetch remote payloads or shell out — curl/wget, urllib, requests.get, subprocess, child_process.exec, eval. | npm, PyPI (setup.py / pyproject), RubyGems, Cargo, Composer | Static + AST analysis of declared lifecycle scripts |
Maintainer-account takeoverCompares the current maintainer set against publish history (Axios-style ATO); surprise publishers block pending review. Maintainer tenure / reputation / account history feed the verdict. | npm, PyPI, RubyGems, NuGet, Maven/Gradle | Per-registry publisher history + maintainer reputation feeds |
Publish-velocity worm burstsRolling 24-hour publish-velocity counter per publisher trips on Shai-Hulud-style worm bursts when one account pushes dozens of tainted versions. | npm, PyPI, RubyGems, NuGet, Maven/Gradle | Per-publisher publish-rate telemetry from registry events |
Broken provenance / unsigned artifactSLSA / Sigstore chain verification per ecosystem, with a post-merge signature-verify pass that projects the provenance verdict for closure. | npm, PyPI, Maven/Gradle, RubyGems, Go, NuGet, Cargo, Docker, Swift, CocoaPods, Hugging Face, APT, Yum, DNF | SLSA attestations, Sigstore transparency log |
Tampered artifact (checksum mismatch)Declared-versus-actual hash verification with log / quarantine / block outcomes per policy. | npm, PyPI, RubyGems, Composer, Maven, Gradle, NuGet, Cargo | Registry-declared hashes + computed artifact digest |
Manifest confusion / lockfile-only artifactFormat-specific mismatch detection (PyPI wheel-vs-sdist sub-provider) plus shrinkwrap / lock-file-only artifacts that ship without source. | npm (shrinkwrap), PyPI (wheel/sdist), plus cross-format manifest checks | Static manifest + lockfile inspection |
Malicious package behaviour (code smell)Nine static sub-detectors on the artifact bytes — eval, network, shell, filesystem, env-var access, native binary, high-entropy strings, URL strings, minified code. npm-only capability scanner emits first-class cap.* signals behind a flag. | All artifact-bearing ecosystems (capability scan: npm) | Static analysis of package artifact contents (not user source) |
Trivial / decoy packageFlags trivial_package and too_many_files heuristics that mark empty decoys or grossly over-stuffed artifacts. | All artifact-bearing ecosystems | Artifact structure heuristics |
Malicious AI model / agent artifactThree sub-providers detect unsafe pickle opcodes — unwrapping zip-container .pt/.bin checkpoints and modeling the pickle stack + memo so nested and obfuscated payloads are caught — plus mismatched model cards and agent-tool artifacts; a post-merge pass verifies the agent-tool artifact. | Hugging Face / model registries | Static model-artifact inspection |
Repo liveness / ownership mismatchRepolink probes the upstream source-repository URL; Repository Trust Traits check liveness, ownership match, non-existent author, suspicious-star bursts, and first-time collaborators. | All ecosystems with a linkable upstream repo | Upstream repository metadata + community signals |
Abandoned / unpopular dependencyDeprecated / archived / stale detection (OpenSSF Scorecard-adjacent) reads the repolink output; download-count fetch powers maint.unpopular_package with a fail-open SevUnknown when the count can't be fetched. Registry metadata supplies publish date, licences, maintainers. | npm, PyPI (download counts); all ecosystems (metadata + maintenance) | OpenSSF Scorecard signals, npm/PyPI weekly downloads, registry manifests |
Internal, private & vendored packages
Same policy on what you host
| Reserved namespaces | @acme/*, com.acme.*, acme.: public squatters refused |
|---|---|
| Private registries | Artifactory, Nexus, Cloudsmith, GitHub Packages, CodeArtifact |
| Vendored copies | vendor/, third_party/ in the SBOM, attributed upstream |
| Git and HTTP URL dependencies | sc.git_url_dependency, sc.http_url_dependency |
Ready to roll out?
Ship your first rule in monitor mode
Every rule starts by recording what it would refuse. Flip it to block once you have seen the data.