Skip to content

Dependency integrity is disabled end to end: no committed lockfile, checksums auto-updated, CI cache keyed on package.json #130

Description

@woksin

Summary

This repository and its sibling both ignore their lockfile, configure Yarn to silently rewrite checksums and permit mutable installs, and key the CI dependency cache on package.json rather than on the lockfile. On any cache miss the npm-publishing job resolves dependency ranges fresh, executes the resulting code, and publishes the output.

Filed against both Arc and Components because the configuration is identical in each and the fix should land in both.

Evidence

Arc Components
lockfile tracked? No — .gitignore:300 yarn.lock; git ls-files finds none No — .gitignore:300 yarn.lock; git ls-files finds none
checksumBehavior update update
immutable installs enableImmutableInstalls: false enableImmutableInstalls: false
cache key ${{ runner.os }}-yarn-${{ hashFiles('**/package.json') }} identical
cache path .yarn/cache, **/node_modules, **/.eslintcache, **/yarn.lock identical
install command run: yarn (bare) run: yarn (bare)
publishes to npm? Yes — publish.yml publish-npm-packages (cache L183-191, yarn L193, yarn build + yarn publish-version L196-207) Yes — publish.yml (cache L100-108, yarn L110, yarn build + yarn publish-version L113-122)
caret-ranged deps 13 deps + 30 devDeps + 5 peer across 9 workspace manifests (root: 12 of 53 devDeps) 1 dep + 58 devDeps + 3 peer (root: 57 of 59 devDeps)

Arc's cache/install/publish shape repeats four times (javascript-build.yml:39-50, pull-requests.yml:68-79, publish.yml:110-121 and 183-194); Components twice (javascript-build.yml:40-51, publish.yml:100-111). Neither repository passes --immutable or --frozen-lockfile anywhere. Arc additionally runs a root postinstall (node ./link-eslint-typescript.js), so install itself executes project code.

Both repositories use npm OIDC trusted publishing (id-token: write, npm ≥ 11.5.1 enforced). That authenticates the publisher well — but provenance attests who built the package, not what it was built from, so it does not mitigate any of the above.

Failure scenario

hashFiles('**/package.json') changes on every version bump, so a publish run routinely misses its cache. Yarn then re-resolves the caret ranges fresh, and checksumBehavior: update accepts a changed tarball for any transitive dependency without a warning. That code runs — as a lifecycle script, or as rollup/tsc/vitest during yarn build — inside the job holding the npm OIDC identity, and its output is published with a provenance attestation. An accidental upstream regression reaches npm by exactly the same path as a deliberate one, and both look fully attested.

Caching **/yarn.lock as a cache path while keying on package.json is its own hazard: the restored lockfile is whatever the last matching run produced, so it neither pins nor participates in the key.

Suggested fix

  1. Untrack yarn.lock from .gitignore:300 and commit it, in both repositories.
  2. Set enableImmutableInstalls: true and change every CI install to yarn install --immutable (Arc ×4, Components ×2).
  3. Drop checksumBehavior: update so a changed tarball fails the build rather than being adopted.
  4. Key the cache on hashFiles('**/yarn.lock') and remove **/yarn.lock from the cached path:.

Steps 1–3 want to land together; committing a lockfile without turning on immutable installs buys very little.

Not verified

Cache hit/miss rates on real publish runs were not measured; the transitive dependency graph was not audited; run-task-on-workspaces.js was not read to confirm what publish-version invokes; and npm-side organization settings (2FA-required, trusted-publisher configuration) are not visible from the repositories and were not checked.

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