How to Enable Checksum Fail-Closed Enforcement

Advanced 15 minutes Platform Engineers / Security Engineers Security & Policy

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:

ModeBehaviour
logCompute the hash, log mismatches, serve the artifact. Default on upgrade — does not change behaviour.
quarantineServe the mismatched artifact but mark the repo row as quarantined and require manual release
blockRefuse 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 log mode

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.

EcosystemReliable hashNotes
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

Next Steps