# Policy | Chainsaw

> Six rule families plus 25 supply-chain detectors, deepest on npm and PyPI. One evaluation per install — install-script exfiltration, maintainer takeover, worm bursts, and more.

Source: https://chain305.com/product/policy/

---

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 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.

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.

[Policy how-to guides →](https://docs.chain305.com/categories/security--policy/)

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.](/demo/digest.png?v=3)

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 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.

[Get started](https://chain305.com/chainsaw/signup) [Talk to sales](https://cal.com/chain305/30min)

---

## Long form

The full text behind this page, including detail the page itself leaves out.

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 — a weighted roll-up of vulnerability, supply-chain, maintenance, license, and quality risk. 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.

[Policy how-to guides →](https://docs.chain305.com/categories/security--policy/)

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 blocked findings ranked by priority.](/demo/digest.png?v=3)

Findings A capture of the Findings screen — severity summary cards and the blocked findings queued for triage, ranked by priority. Demo org seeded with synthetic install traffic.

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

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 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.

[Get started](https://chain305.com/chainsaw/signup) [Talk to sales](https://cal.com/chain305/30min)
