Policy

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

Chainsaw policy is declarative. You compose vulnerability, license, version, provenance, and client-context rules, then layer in the supply-chain attack signals CVE-based scanners miss. Every rule supports monitor, block, and quarantine modes, and every decision leaves a structured audit record.

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 release age. Quarantines new versions 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

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.

hasInstallScript · installScriptFetchesRemote

Maintainer-account takeover

Axios v1.14.1 pattern

Compares the current maintainer set against the package's history. A surprise publisher on a popular dependency blocks pending review. Works on npm, PyPI, RubyGems, NuGet, and Maven.

publisherChanged

Version-number anomalies

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.

versionAnomaly · versionAnomalyKinds

Hidden characters in package

GlassWorm, Trojan Source

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.

hasHiddenUnicode · hiddenUnicodeKinds

Publish-velocity worm bursts

Shai-Hulud pattern

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.

publishVelocityAnomaly

Reserved-namespace dependency confusion

Birsan pattern

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.

reservedNamespaces

Typosquat detection

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. Fourteen ecosystems including Go, CocoaPods, and GitHub Actions.

isSuspectedTyposquat

Docker malware feed

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.

isKnownMalicious (docker)

Per-layer image enforcement

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.

Container image analysis

OS-package hash-chain provenance

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.

hasProvenance (apt/yum/dnf)

Linux distro CVE detection

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.

distroCVE (alpine/debian/rhel/oracle)

Hugging Face malware feed

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.

isKnownMalicious (huggingface)

Repo liveness and ownership match

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.

trustScoreMin

Checksum fail-closed enforcement

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.

checksum mode

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 Data source
Known vulnerability (CVE) Vulnerability gate — CVSS, EPSS, CISA KEV, CVE ID match per ecosystem. Trivy DB, OSV, GHSA, NVD, CISA KEV
Typosquatting BK-tree + homoglyph + word-reorder matcher against curated seed lists across 14 ecosystems. Curated popular-package seeds per ecosystem
Dependency confusion Reserved-namespace packs block public publishes of your internal names. Birsan pattern refused up front. Customer-declared internal namespaces + public-registry name-resolution check
Malware Digest + name+tag match against malicious-package and malware indexes; ClamAV scanning on container layers. OpenSSF malicious-packages, OpenSSF malware feed, ClamAV signatures
Hidden Unicode / Trojan Source Refuses packages with zero-width, bidi-override, or Unicode-tag characters. Bounded at 500 files / 50 MiB per artifact. Static source scan (no external feed required)
Install-script exfiltration Flags lifecycle hooks that run remote fetches or decode base64 payloads — npm pre/postinstall, pip setup.py, Cargo build.rs, Composer hooks. Static + symbolic analysis of declared lifecycle scripts
Maintainer-account takeover Compares the current maintainer set against the package's publish history. Surprise publishers block pending review. Per-registry publisher history (npm, PyPI, RubyGems, NuGet, Maven)
Publish-velocity worm bursts Rolling 24-hour counter per publisher trips when one account starts pushing dozens of tainted versions. Per-publisher publish-rate telemetry from registry events

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.

Ready to roll out?

Put Chainsaw on the install path

Start free, switch to blocking when you're ready, or chat with us about custom deployments.