How to Detect Compromised Packages with publisherChanged and versionAnomaly

Intermediate 20 minutes Security Engineers Security & Policy

Catch Axios-style maintainer-takeover drops, semver regressions, multi-major skips, and backdated publishes before they reach your builds.

Overview

Two of the highest-signal supply-chain attack patterns leave identifiable metadata fingerprints on the publish event itself:

  • Maintainer account takeover — the attacker publishes from a never-before-seen npm user, email, or Maven developer ID. The publisherChanged condition diffs the incoming version’s publisher set against the last persisted version’s set.
  • Version-number games — the Axios v1.14.1 → v0.30.4 drop flipped the major to make the malicious release look “old”, and subsequent variants skipped majors or backdated the publish timestamp. The versionAnomaly condition detects all three shapes.

Both conditions run inside the sync-check budget and persist their results to package_metadata so the BOM can show you the history.

Prerequisites

  • Admin or Manager role in Chainsaw
  • At least one of: npm, PyPI, RubyGems, NuGet, Maven, Gradle repositories proxied through Chainsaw for long enough to accumulate a baseline

Step 1: Let Chainsaw Learn the Baseline

The publisherChanged signal requires at least one prior persisted version of the package. Give Chainsaw a week or two of normal traffic before turning the block on — otherwise the first install of every package looks like a publisher change and nothing passes.

Step 2: Create the publisherChanged Policy

  1. Navigate to Policies → Create Policy
  2. Name: Warn — publisher changed
  3. Condition: publisherChanged = true
  4. Action: Warn (start in monitor mode)
  5. Scope: All repositories

After ~14 days, review the violations. Legitimate publisher changes (a maintainer handing the project off, a bot account being rotated) will show up alongside the attack signal; add those to an allowlist by package before promoting the policy to Block.

Pair publisherChanged with trustScoreMin and packageAge — a genuine handoff still has a reasonable trust score and usually isn’t published within 24 hours of the previous release, while an account-takeover drop often fails both additional checks.

Step 3: Create the versionAnomaly Policy

versionAnomaly fires when any of three sequence flags are set on the incoming version:

FlagWhat it catchesExample
semver_regressionMajor version flips backwards1.14.x0.30.4
major_skip2+ majors skipped with no intermediate release1.x3.x directly
timestamp_regressionPublish time is before a previous version’s publish timeBackdated to look “older”

Create a policy that narrows to the highest-signal flags first:

  1. Name: Block — version anomaly (regression + major skip)
  2. Condition: versionAnomaly = true
  3. Kinds filter: versionAnomalyKinds = [semver_regression, major_skip]
  4. Action: Block
  5. Scope: Production repositories

Leaving timestamp_regression out of the block condition is intentional — some upstream mirrors reclock publish timestamps for infrastructure reasons. Track it in a separate warn-mode policy while you triage the signal.

Step 4: Ecosystem Support

EcosystempublisherChangedversionAnomaly
npmmaintainers[].name
PyPIinfo.author_email + info.maintainer_email
RubyGemsauthors
NuGetauthors
Maven / Gradle✅ POM developers[].id / <email>
Go❌ (no per-version publisher field)
Swift
HuggingFace
Cargo
Composer
CocoaPods⚠️ (no release-date for timestamp_regression)
Docker❌ (tags not SemVer)
APT / Yum / DNF❌ (non-SemVer versioning)

For ecosystems that lack publisherChanged support, pair versionAnomaly with trustScoreMin and the provenance rule for that ecosystem.

Step 5: CLI Verification

chainsaw pkg scan npm/axios \
  --version 0.30.4 \
  --with-publisher-change \
  --with-version-anomaly

Output includes publisher_set_diff, version_anomaly_flags, and the persisted diff against the previous version.

Step 6: Investigate a Detection

When publisherChanged fires, open the package in the BOM and review:

  1. The prior publisher set vs. the incoming set
  2. Whether the new publisher has recent activity on other packages (cross-reference with publishVelocityAnomaly)
  3. The version-number shape (cross-reference with versionAnomaly)
  4. The trust score delta since the last version

Three of the four signals lighting up at once is a high-confidence account-takeover. Block the version, open an incident, and quarantine any repos that installed it during the detection window.

Next Steps