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
- Untrack
yarn.lock from .gitignore:300 and commit it, in both repositories.
- Set
enableImmutableInstalls: true and change every CI install to yarn install --immutable (Arc ×4, Components ×2).
- Drop
checksumBehavior: update so a changed tarball fails the build rather than being adopted.
- 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.
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.jsonrather 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
.gitignore:300yarn.lock;git ls-filesfinds none.gitignore:300yarn.lock;git ls-filesfinds nonechecksumBehaviorupdateupdateenableImmutableInstalls: falseenableImmutableInstalls: false${{ runner.os }}-yarn-${{ hashFiles('**/package.json') }}.yarn/cache,**/node_modules,**/.eslintcache,**/yarn.lockrun: yarn(bare)run: yarn(bare)publish.ymlpublish-npm-packages(cache L183-191,yarnL193,yarn build+yarn publish-versionL196-207)publish.yml(cache L100-108,yarnL110,yarn build+yarn publish-versionL113-122)Arc's cache/install/publish shape repeats four times (
javascript-build.yml:39-50,pull-requests.yml:68-79,publish.yml:110-121and183-194); Components twice (javascript-build.yml:40-51,publish.yml:100-111). Neither repository passes--immutableor--frozen-lockfileanywhere. Arc additionally runs a rootpostinstall(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, andchecksumBehavior: updateaccepts a changed tarball for any transitive dependency without a warning. That code runs — as a lifecycle script, or asrollup/tsc/vitestduringyarn 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.lockas a cache path while keying onpackage.jsonis its own hazard: the restored lockfile is whatever the last matching run produced, so it neither pins nor participates in the key.Suggested fix
yarn.lockfrom.gitignore:300and commit it, in both repositories.enableImmutableInstalls: trueand change every CI install toyarn install --immutable(Arc ×4, Components ×2).checksumBehavior: updateso a changed tarball fails the build rather than being adopted.hashFiles('**/yarn.lock')and remove**/yarn.lockfrom the cachedpath:.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.jswas not read to confirm whatpublish-versioninvokes; and npm-side organization settings (2FA-required, trusted-publisher configuration) are not visible from the repositories and were not checked.