Skip to content

Commit d651bc4

Browse files
Shinyaigeekclaude
andcommitted
build: turn on pnpm's supply-chain policies
pnpm 11 ships a set of guards that are either off or only half on by default. All of them are now set explicitly in pnpm-workspace.yaml, each with its reasoning inline: - minimumReleaseAge quarantines anything published in the last three days, and minimumReleaseAgeStrict makes an immature pick abort the install rather than quietly excluding itself. Seven days -- the value worth wanting -- is not reachable yet: it rejects 65 current lockfile entries, and ranges like oxfmt's ^0.61.0 have no version old enough to satisfy it at all. - trustPolicy rejects a version whose publish trail is weaker than that of a version published before it, which is what a package takeover looks like from outside. chokidar@4.0.3 and semver@6.3.1 predate their packages adopting trusted publishing and are excluded by name. - trustLockfile stays false, so the two policies above are re-applied to every lockfile entry on each install rather than trusted from whoever resolved it. - blockExoticSubdeps keeps git/http/file specs out of transitive manifests. - strictDepBuilds turns an unlisted install script into an error. - verifyStoreIntegrity and strictStorePkgContentCheck are pinned on. - verifyDepsBeforeRun stops `pnpm run` against a node_modules that has drifted from the lockfile. `pnpm audit` now runs as its own CI job and as the last step of `pnpm run ci`, failing at audit.level (low). It stays out of both deploy workflows: an advisory against a transitive dependency should not be what stops the blog from shipping. That audit was failing on GHSA-mh99-v99m-4gvg. brace-expansion reaches the tree only through typed-css-modules > glob > minimatch, which has no release picking the fix up, so its 2.x branch is pinned through overrides. The fix is itself younger than the cooldown, hence the one minimumReleaseAgeExclude entry. Verified on Node 26.5.1 with every policy live: clean install, frozen-lockfile install (962 entries pass), the full ci run, all three builds, the deploy workflows' --ignore-scripts path, and the blog's dev server. Both guards were also made to fire on purpose -- verifyDepsBeforeRun aborts on an uninstalled dependency, and audit exits 1 against the pre-fix lockfile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent da46cfd commit d651bc4

5 files changed

Lines changed: 343 additions & 281 deletions

File tree

.github/workflows/ci.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,23 @@ jobs:
5858
- name: Run tests
5959
run: pnpm run test
6060

61+
audit:
62+
needs:
63+
- warmup_dependencies_cache
64+
runs-on: ubuntu-latest
65+
timeout-minutes: 5
66+
env:
67+
NODE_ENV: development
68+
steps:
69+
- uses: actions/checkout@v4
70+
- uses: ./.github/actions/setup
71+
# Fails on any advisory at or above audit.level in pnpm-workspace.yaml,
72+
# currently `low`. This job is deliberately outside the deploy
73+
# workflows: an advisory published against a transitive dependency
74+
# should page us, not stop the blog from shipping.
75+
- name: Audit dependencies
76+
run: pnpm run audit
77+
6178
build:
6279
needs:
6380
- warmup_dependencies_cache

CLAUDE.md

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ pnpm format # oxfmt, writes in place
8787
pnpm format:check # oxfmt --check
8888
pnpm typecheck # tsc --noEmit in every package
8989
pnpm test # vitest in every package that has tests
90+
pnpm audit # advisories against the lockfile, needs network
9091
pnpm run ci # all of the above, the way CI runs them
9192
```
9293

@@ -106,18 +107,57 @@ that is not 11.18.0 fetches that version and hands over to it rather than
106107
running as itself. The GitHub runner image does still ship corepack, which is
107108
what `corepack enable pnpm` in the workflows picks up.
108109

109-
Dependencies' install scripts no longer run unless the package is listed under
110-
`allowBuilds`. The four that ask for one (`@swc/core`, `esbuild`, `msw`,
110+
### Supply-chain settings
111+
112+
All of these are in `pnpm-workspace.yaml`, each with the reasoning inline. The
113+
two worth knowing before an install surprises you:
114+
115+
- **`minimumReleaseAge: 4320`** quarantines anything published in the last three
116+
days, and every install re-checks the whole lockfile against it, not just new
117+
resolutions. So a version that was fine to resolve can still block a
118+
`--frozen-lockfile` install for its first three days. `pnpm install` fails with
119+
`ERR_PNPM_LOCKFILE_RESOLUTION_VERIFICATION` and names each entry. To take
120+
something early anyway — a security fix, typically — add it to
121+
`minimumReleaseAgeExclude` as `name@exact.version`, which is also what
122+
`pnpm audit --fix` writes.
123+
- **`trustPolicy: no-downgrade`** rejects a version whose publish trail is weaker
124+
than that of a version published before it. Old releases of a package that
125+
later adopted trusted publishing trip this without anything being wrong;
126+
`chokidar@4.0.3` and `semver@6.3.1` are listed under `trustPolicyExclude` for
127+
exactly that reason. Check the publish dates before adding a third.
128+
129+
Both checks cost registry round-trips on the first install after a lockfile
130+
change, and the verdict is cached afterwards.
131+
132+
Dependencies' install scripts do not run unless the package is listed under
133+
`allowBuilds`, and `strictDepBuilds` makes an unlisted script an error rather
134+
than a warning. The four that ask for one (`@swc/core`, `esbuild`, `msw`,
111135
`workerd`) are all listed as `false`: each ships its platform binary as an
112136
optional dependency and only uses the script to unpack or shortcut to it, and
113137
the deploy workflows have always installed with `--ignore-scripts` anyway.
114138

139+
`verifyDepsBeforeRun: error` means `pnpm run` and `pnpm exec` refuse to start
140+
when `node_modules` has drifted from the lockfile, with
141+
`ERR_PNPM_VERIFY_DEPS_BEFORE_RUN`. Editing a dependency in a package.json and
142+
running a script before installing is the usual way to meet it.
143+
144+
`pnpm audit` runs as its own CI job and as the last step of `pnpm run ci`,
145+
failing on any advisory at or above `audit.level` (`low`). It is deliberately
146+
not in either deploy workflow: an advisory landing against a transitive
147+
dependency should not be what stops the blog from shipping.
148+
115149
`overrides` in `pnpm-workspace.yaml` pins **rollup** to 4.62.2. From 4.62.3
116150
its prebuilt Linux binary needs GLIBC 2.32, which is newer than the dev machine
117151
(Ubuntu 20.04, GLIBC 2.31) has, so vitest dies with `ERR_DLOPEN_FAILED` on every
118152
`pnpm test`. CI runs on ubuntu-latest and is unaffected — drop the override once
119153
the dev machine is on a newer glibc.
120154

155+
The second override pins **brace-expansion**'s 2.x branch to ^2.1.4 for
156+
GHSA-mh99-v99m-4gvg. It reaches the tree only through
157+
`typed-css-modules > glob > minimatch`, which has no release picking the fix up,
158+
so the override is the only way to clear the advisory; drop it once
159+
typed-css-modules moves off that minimatch.
160+
121161
### Blog (packages/applications/blog/)
122162
```bash
123163
pnpm dev # watch everything, 日本語 on :3000 and English on :3001

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"lint:fix": "oxlint --fix ./packages",
1919
"typecheck": "pnpm -r typecheck",
2020
"test": "pnpm -r test",
21-
"ci": "pnpm run lint && pnpm run format:check && pnpm run typecheck && pnpm run test"
21+
"audit": "pnpm audit",
22+
"ci": "pnpm run lint && pnpm run format:check && pnpm run typecheck && pnpm run test && pnpm run audit"
2223
},
2324
"keywords": [],
2425
"author": "",

0 commit comments

Comments
 (0)