Skip to content

FYI: npm publish-time malware scanning makes the npm info probe / already-published path a routine case #2195

Description

@trivikr

Heads-up rather than a bug report. I don't have a reproduction, and part of this depends on registry behavior npm hasn't documented yet. Filing so it's on the radar.

What changed

npm has started scanning packages at publish time. A newly published version is scanned before it becomes installable, so there is now a window — typically around five minutes, longer at peak — between a successful npm publish and the version being available. A publish can also be held for manual review, or blocked.

The changelog says npm dist-tag keeps working during the window while npm deprecate and npm unpublish do not until the version becomes available. It does not say what a registry read returns during the window.

Where this touches publish

The probe is info in lib/npm.ts: a bare npm info <name> --json, with an exact name@version query as a fallback when the bare one comes back empty. getPublishPlan then decides with !publishedVersions.includes(localVersion).

On npmjs.org the bare query succeeds, so the exact fallback doesn't run and publishedVersions comes from the packument's versions. If a pending-scan version is absent from versions, the plan reads it as never published.

The good news is that the already-published path already exists: handlePublishError returns failed:already-published, and publish skips it without failing the run. So the primary failure mode I first worried about is already covered.

Two things scanning changes about that path.

It stops being rare. The comment on isAlreadyPublishedError frames it as stale npm info data, and the skip site says the error should rarely appear since only unpublished packages are attempted. With a multi-minute availability window, re-running a release that failed part-way through becomes a reliable way to hit it — not an occasional cache artifact. We publish ~60 packages per release from one monorepo, so a partial failure mid-set is the case we actually recover from, and the retry lands inside the window by construction.

Skipped packages don't get git tags. Only successfulNpmPublishes feeds gitTagReleases, and the failed:already-published branch continues without pushing there. So a retry inside the window exits 0 with versions on the registry and no corresponding tags. That's defensible when the error is a rare stale read; it's less so when it's the expected outcome of a normal retry.

Also worth flagging: the detection is a substring match on the English error summary, "cannot publish over the previously published". That's the only thing standing between the scan window and a hard failure now, which makes it load-bearing in a way it wasn't before.

What I have and haven't verified

Verified by reading main at the commit above: the probe commands, the versions.includes filter, the failed:already-published classification and skip, and that skipped packages are excluded from git tagging.

Not verified: whether a pending-scan version is actually absent from the packument, and whether re-publishing it returns the already-published error rather than succeeding. Both need a real publish inside the window. I've asked npm about the registry-side contract here: https://github.com/orgs/community/discussions/203395.


Assisted-by: claude:opus-5

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions