Policy

Six rule families plus up to 12 supply-chain attack signals. One evaluation per install.

A failing package is refused before it reaches the build — on rules you write. Compose vulnerability, license, version, provenance, and client-context rules declaratively, then layer in the supply-chain attack signals CVE-based scanners miss. Every rule ships in monitor mode first, flips to block or quarantine when you've seen the data, and leaves a structured audit record on every decision.

Core rule families

Compose the rules that matter to your org

Vulnerability gating

Block by CVE, CVSS score, EPSS exploit probability, or CISA KEV membership. Combine freely; every operator is composable inside a single rule.

License enforcement

Allow, warn, or block by SPDX identifier. Separate policy for direct and transitive dependencies. SPDX-flavoured expressions for the edge cases.

Version and age rules

Pin minimum versions, block deprecated or EOL releases, enforce semver ranges, require a minimum package age, or set a per-version cooldown that holds every fresh release for a window while the ecosystem has time to react.

Provenance verification

Require npm provenance, Sigstore signatures, Go's sum.golang.org, PGP for Maven and RubyGems, or InRelease/repomd hash-chain for APT/Yum/DNF. Trust roots are configurable.

Client-context rules

Different policy for prod vs dev, per repository, per CI job, per geographic region. Tight where it matters, permissive where it doesn't.

VEX-aware exceptions with expiry

Every exception carries a reviewer, a reason, and an expiry date — no permanent waivers by default. Exceptions are VEX-aware: chainsaw exception create accepts --cve, --decision (not_affected / affected / fixed / under_investigation), and --vex-note, so the audit log answers vendor questionnaires directly.

Supply-chain attack signals

The 12 attack-pattern signals CVE-based scanners miss — 4 always-on, the rest where each registry supports them

Each signal evaluates at install time against the artifact, the registry metadata, and the publisher history. Combine them with vulnerability, license, version, and provenance rules in the same policy. Start in monitor mode, tune exceptions, flip to enforce one rule at a time.

Install-script exfiltration

PhantomRaven pattern
hasInstallScript · installScriptFetchesRemote

Flags packages whose lifecycle hooks — preinstall/postinstall in npm, setup.py extensions in pip, build.rs in Cargo, Composer hooks — run remote fetches or decode base64 payloads. Refuses the install before the hook fires.

Fresh-version cooldown

Account-takeover class
cooldownDays

Quarantines any version published inside your cooldown window — a few hours to a few days — on the install path, so a poisoned release sits unreachable while the ecosystem, the malware feeds, and the publisher-anomaly signals catch up. Package age asks how old the package is; cooldown asks how old this version is, which is the question the Axios, Chalk, and Mastra takeovers turned on: a brand-new release of a long-trusted package. Runs on every registry that exposes a per-version publish date — the same coverage as the minimum-age rule. Absent date metadata fails open, never quarantining on a guess.

Maintainer-account takeover

Axios v1.14.1 pattern
publisherChanged

Compares the current maintainer set against the package's history. A surprise publisher on a popular dependency refuses on the install path pending review. The account-takeover class — a hijacked maintainer pushing a tainted version of a package you already trust — is the live threat right now, ahead of the typosquats that catch the easier, older mistakes. Works on npm, PyPI, RubyGems, NuGet, and Maven.

Version-number anomalies

versionAnomaly · versionAnomalyKinds

Catches semver regressions, multi-major skips, and backdated publish timestamps used to sneak a compromised version under a higher constraint. Narrow by kind: semver_regression, major_skip, timestamp_regression.

Hidden characters in package

GlassWorm, Trojan Source
hasHiddenUnicode · hiddenUnicodeKinds

Refuses packages whose published source includes zero-width, bidi-override, or Unicode-tag characters. Closes the GlassWorm and Trojan Source attack class without running any code. Bounded at 500 files and 50 MiB per artifact.

Publish-velocity worm bursts

Shai-Hulud pattern
publishVelocityAnomaly

A rolling 24-hour counter per publisher. When one compromised account starts pushing dozens of tainted versions in a day, the burst trips the rule before your build runs. Threshold is tunable.

Reserved-namespace dependency confusion

Birsan pattern
reservedNamespaces

Dependency confusion works because attackers publish your internal package names on public registries first. Reserved-namespace packs block the attack class up front. One-click apply of curated starters per ecosystem.

Typosquat detection

isSuspectedTyposquat

Blocks the install when the requested name is a near-miss of a popular package. BK-tree, homoglyph, and word-reorder matchers run against curated seed lists. Fifteen ecosystems including Go, CocoaPods, and GitHub Actions. Dart/pub gets CVE and typosquat coverage only — no malicious-package prevention.

Docker malware feed

isKnownMalicious (docker)

A Docker-native malware index matched by digest and by name-plus-tag. Closes the container-image gap that public SCA feeds miss. Lookup is constant-time; a hit drops the trust score to -100.

Per-layer image enforcement

Container image analysis

Walks every image layer with Trivy under the hood. Vulnerabilities hidden beneath a clean top-level manifest are blocked at install; a clean tag no longer guarantees a clean image.

OS-package hash-chain provenance

hasProvenance (apt/yum/dnf)

InRelease verification for APT, repomd.xml.asc for Yum/DNF. A mirror that tampers with a package between publish and fetch fails the chain. Debian and Fedora keyrings ship embedded.

Linux distro CVE detection

distroCVE (alpine/debian/rhel/oracle)

Native CVE detectors for Alpine, Debian, Red Hat, and Oracle Linux — distinct from upstream OSV, with each distro stream on its own update cadence. A vendor advisory becomes a block-list entry the same hour the distro publishes it.

Hugging Face malware feed

isKnownMalicious (huggingface)

Bundled HF-native coordinate-match feed shipped in-process. Closes the gap where public SCA indexes lag on model-repo malware. Lookup is constant-time at resolve.

Repo liveness and ownership match

trustScoreMin

Unmaintained repos with an active npm publisher are a compromise waiting to happen. Each package gets a composite trust score folding in repo activity, ownership match, license, age, version count, and checksum presence. Set the floor you're comfortable with.

Checksum fail-closed enforcement

checksum mode

Every upstream fetch is audited against the declared hash. Pick log, quarantine, or block mode per ecosystem. Distinguishes a real mismatch from an upstream that never published a hash.

Where refusals surface

Every fired rule lands in Findings, ranked for triage

When a rule fires, the decision and its reason land in Findings. Severity cards summarize the queue; the policy-blocked findings below are the ones to triage first.

Chainsaw Findings screen with severity summary cards and a triage list of policy-blocked findings.
Findings A real capture of the Findings screen — severity summary cards and the policy-blocked findings queued for triage, each carrying the rule that fired. Demo org (Northwind Labs).

Coverage matrix

Attack class → detection mechanism → data source

One row per attack class. The detection mechanism column names the module that runs the check; the data source column names the feed or signal it pulls from. Honest gaps — if a column is empty for an ecosystem, it's empty in the product.

Attack class Detection 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 Reserved-namespace packs block public-registry publishes of your internal names. The Birsan pattern is refused up front, no per-package allow-listing. Universal (customer-declared namespaces) Customer-declared internal namespaces + public-registry name resolution
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 packages

Internal package hosting and private-registry scanning

Chainsaw treats internal packages as first-class. You don't have to choose between running a private registry and running policy on what flows through it.

Reserved-namespace approach

Declare the namespaces your org owns — @acme/* on npm, com.acme.* on Maven, acme. on PyPI — and Chainsaw refuses any public-registry publish that tries to squat them. Closes the Birsan dependency-confusion class up front, no per-package allow-listing.

Private-registry proxying

Point Chainsaw at your internal registry (JFrog Artifactory, Sonatype Nexus, Cloudsmith, GitHub Packages, AWS CodeArtifact) the same way you point it at npm or PyPI. The proxy authenticates outbound, applies the same policy, and caches results. Configurable per ecosystem; turn it off for any registry you don't want scanned.

Vulnerabilities in internal packages

Internal-package vulns surface on the same install-path audit row as public-package vulns — by repo, by CI job, by rule. If your internal package ships a vulnerable transitive (which is where most internal CVEs hide), the transitive-risk module walks the closure and the result lands in the inventory under the internal name.

Vendored & bundled dependencies

Pre-vendored and bundled packages

Some teams check vendored copies into the repo — vendor/ in Go, third_party/ in monorepos, bundled wheels in Python projects, fat JARs on the JVM. Chainsaw's SBOM walks those directories during inventory generation, attributes findings back to the original upstream package, and records the vendored copy alongside the install-path record so a single CVE query catches both. Scan depth is configurable per repo — set the max walk depth, exclude a directory, or skip vendored scanning entirely if your compliance posture doesn't need it.

Dependencies pulled straight from a Git URL or HTTP archive instead of a registry never pass through publisher history, checksums, or provenance — so Chainsaw flags them with the first-class supply-chain signals sc.git_url_dependency and sc.http_url_dependency. You decide the verdict: log them, require an exception with an owner and expiry, or block the install outright. Either way the non-registry source shows up on the same audit row as everything else, so a git+https://… backdoor can't slip in unseen.

Ready to roll out?

Put Chainsaw on the install path

Start free in monitor mode. See what would be refused, then flip to enforce when you've seen the data.