Skip to content

verify-action-build: normalize package.json before comparing - #1136

Open
potiuk wants to merge 1 commit into
mainfrom
verify-npm-package-json-normalization
Open

verify-action-build: normalize package.json before comparing#1136
potiuk wants to merge 1 commit into
mainfrom
verify-npm-package-json-normalization

Conversation

@potiuk

@potiuk potiuk commented Aug 3, 2026

Copy link
Copy Markdown
Member

Found while triaging #1100 (reactivecircus/android-emulator-runner), which
fails verify on four checks. Three are tooling gaps; this fixes them.

package.json is rewritten at install time

A vendored node_modules holds the manifest npm rewrote when installing,
while the registry tarball and a fresh npm ci hold the published one.
Comparing bytes reports that difference as tampering. Two separate rewrites:

  • Install bookkeeping — npm writes _-prefixed keys that the tarball never
    had. diff_node_modules already filtered these, but by enumeration, and the
    list was missing _args and _location. Now matched by the reserved _
    prefix, since the set has varied across npm versions.
  • Shorthand expansionnormalize-package-data turns an author string
    into a person object, bugs into {"url": ...}, and prefixes
    repository.url with git+.

node_modules/tunnel/package.json hits both: it was the only differing file
out of 217, and on its own failed the whole JS build check and the registry
check.

Normalisation is deliberately narrow. Only the fields npm is known to rewrite
are touched; everything else — main, bin, scripts, dependencies — is
still compared strictly, with a regression test asserting a tampered main
still fails.

Two smaller fixes

  • @actions/tool-cache's 7zdec.exe is exempted from the in-tree binary
    check. It backs tc.extractZip on Windows and ships inside the first-party
    npm package, so every action vendoring node_modules carries it verbatim
    from the published tarball. Same narrow path-suffix form as the
    gradle-wrapper.jar exemption in verify: exempt gradle-wrapper.jar from in-tree binary check #951. The general fix — crediting any
    binary under a node_modules package the registry check already verified —
    needs the two checks reordered, so I left it for a follow-up.
  • The registry mismatch line passed the offending path as the URL and the
    tree root as the text, so it never named the failing file. Diagnosing Pin reactivecircus/android-emulator-runner #1100
    meant deriving the filename from the rebuild diff instead.

Test plan

  • uv run pytest utils/tests/ — 320 passed.
  • Normaliser checked against the real bytes: committed
    node_modules/tunnel/package.json at a421e438 vs the published
    tunnel-0.0.6.tgz from the registry now compare equal.
  • prek run --all-files clean.

Not yet re-run end-to-end against the action itself — that needs a
GITHUB_TOKEN and I exhausted the unauthenticated rate limit. CI on this PR
will cover it.

A vendored node_modules holds the manifest npm rewrote at install time,
while the registry tarball and a fresh npm ci hold the published one.
Comparing bytes reported the difference as tampering.

Two rewrites are involved: npm's _-prefixed install bookkeeping (the
enumerated list missed _args and _location), and normalize-package-data
expanding shorthand — author string to a person object, bugs to {url},
and a git+ prefix on repository.url.

reactivecircus/android-emulator-runner ships exactly this shape, and one
file, node_modules/tunnel/package.json, was the only difference across
217 — enough to fail the whole JS build check and the registry check.

Also exempts @actions/tool-cache's bundled 7zdec.exe from the in-tree
binary check, and fixes the registry mismatch line, which passed the
offending path as the URL and so never named the failing file.

Generated-by: Claude Opus 5 (1M context) via Claude Code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant