How to Enable Checksum Fail-Closed Enforcement
Block silently swapped upstream packages by switching CHAINSAW_CHECKSUM_MODE from log to quarantine or block.
Overview
A registry or mirror compromise that tampers with a single artifact (the Twilio TaskRouter JS incident in August 2025 is the canonical recent example) bypasses every classical supply-chain control: the package name is unchanged, the version is unchanged, the publisher is unchanged, and the vulnerability feeds don’t know about the tampered content yet. The only reliable signal is the checksum.
Chainsaw instruments every upstream fetch site with a checksum audit.
Mode is controlled by the CHAINSAW_CHECKSUM_MODE environment variable:
| Mode | Behaviour |
|---|---|
log | Compute the hash, log mismatches, serve the artifact. Default on upgrade — does not change behaviour. |
quarantine | Serve the mismatched artifact but mark the repo row as quarantined and require manual release |
block | Refuse to serve the artifact; return a 502 with a policy-violation reason |
Two metrics surface the signal:
chainsaw_checksum_mismatch_total— counter of mismatches, labelled by ecosystem- Skip reason
checksum_unavailable— distinguishes “upstream didn’t publish a hash” from “upstream hash didn’t match what we fetched”
Prerequisites
- Chainsaw admin access to environment configuration
- Baseline from at least one week in
logmode
Step 1: Start in Log Mode and Build a Baseline
Log mode is the default after upgrade. Let the proxy run for 7–14 days
and watch the chainsaw_checksum_mismatch_total counter. For most
deployments it should stay at zero:
sum by (ecosystem) (rate(chainsaw_checksum_mismatch_total[24h]))
Any non-zero result during the baseline deserves investigation before you enable enforcement — a corrupted mirror or an upstream bug will manifest as a mismatch and you want to understand the signal before it becomes a block.
Step 2: Differentiate Mismatch from Missing-Hash
Some ecosystems don’t universally publish a hash with every artifact. Chainsaw distinguishes the two failure modes so you can enforce only where enforcement is safe:
sum by (reason) (rate(chainsaw_proxy_skipped_total{reason="checksum_unavailable"}[24h]))
A high checksum_unavailable rate for a given ecosystem means turning
on block mode globally will cause install failures. In that case,
scope enforcement to ecosystems that reliably publish hashes — npm,
PyPI, Maven / Gradle, RubyGems, Cargo, NuGet, Go (via sum.golang.org),
Docker / OCI (via digests).
Step 3: Promote to Quarantine Mode
Once the baseline is clean, switch to quarantine:
export CHAINSAW_CHECKSUM_MODE=quarantine
In this mode, a mismatched artifact is still served to the requesting client (so CI doesn’t fail immediately), but the repository row is quarantined. Dashboard shows a banner on the affected repo; operator must manually release the quarantine once the incident is understood.
Step 4: Promote to Block Mode
After another week of quarantine running cleanly, switch to block:
export CHAINSAW_CHECKSUM_MODE=block
From this point, any upstream artifact whose hash doesn’t match the declared value is refused with a 502 and a policy-violation reason. The build that requested it will fail; the mismatched artifact never reaches a developer machine or a production runner.
Step 5: Alert on the Counter
Page on any non-zero rate:
# Prometheus alerting rule
- alert: ChainsawChecksumMismatch
expr: sum(rate(chainsaw_checksum_mismatch_total[5m])) > 0
for: 0m
labels:
severity: critical
annotations:
summary: "Chainsaw detected an upstream checksum mismatch"
description: "Check the quarantined repos in the dashboard and open a supply-chain incident."
A checksum mismatch in block mode is either (a) a corrupted mirror — rare but real; (b) a legitimate upstream republish without a version bump — rare and a footgun either way; or (c) a supply-chain incident caught in the act. All three are page-worthy.
Ecosystem Support
The audit runs on every fetch site. Where upstream declares a hash, the
signal is reliable. Where it doesn’t, the checksum_unavailable skip
reason is emitted and the artifact is served without gating.
| Ecosystem | Reliable hash | Notes |
|---|---|---|
| npm | ✅ | integrity field in packument |
| PyPI | ✅ | sha256 in release JSON |
| Maven / Gradle | ✅ | .sha1 / .sha256 sibling files |
| RubyGems | ✅ | sha in versions API |
| Cargo | ✅ | cksum in index |
| NuGet | ✅ | package hash in service index |
| Go | ✅ | sum.golang.org transparency |
| Docker / OCI | ✅ | digest is the identifier |
| APT / Yum / DNF | ✅ | part of provenance hash-chain |
| Hugging Face | ⚠️ | LFS hash only, not guaranteed for every blob |
| Swift / CocoaPods | ⚠️ | depends on tag — sometimes skipped |