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.

  1. 01 · Monitor

    Every rule ships here

    decision recorded, install still succeeds

  2. 02 · You review the data

    • what would have fired
    • exceptions, with expiry
  3. 03 · Enforce

    Refused or quarantined

    audit record on every decision

Core rule families

Compose the rules that matter

Rule familyMatches on
Vulnerability gatingCVE, CVSS, EPSS exploit probability, CISA KEV
License enforcementSPDX identifier, direct and transitive separately
Version and age rulesminimum version, deprecated or EOL, semver range, minimum age, per-version cooldown
Provenance verificationnpm provenance, Sigstore, sum.golang.org, PGP, InRelease / repomd
Client-context rulesprod vs dev, repository, CI job, region
VEX-aware exceptionsreviewer, 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.
Hidden characters in package hasHiddenUnicode · hiddenUnicodeKinds GlassWorm, Trojan Source Zero-width, bidi-override or Unicode-tag characters in published source. Bounded at 500 files and 50 MiB per artifact.
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

Chainsaw Findings screen with severity summary cards and a triage list of blocked findings ranked by priority.
Findings Severity cards and the refused findings queued for triage, by priority. Demo org, synthetic install traffic.

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
Typosquatting

BK-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 confusion

The 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 malware

Digest 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 Source

Refuses 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 exfiltration

PhantomRaven-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 takeover

Compares 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 bursts

Rolling 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 artifact

SLSA / 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 artifact

Format-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 package

Flags 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 artifact

Three 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 mismatch

Repolink 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 dependency

Deprecated / 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 registriesArtifactory, Nexus, Cloudsmith, GitHub Packages, CodeArtifact
Vendored copiesvendor/, third_party/ in the SBOM, attributed upstream
Git and HTTP URL dependenciessc.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.