From f2052fbdaa24dbbe1c10d2479cc6967f895905ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Tue, 4 Aug 2026 17:58:07 +0000 Subject: [PATCH] freeze installs: bun resolves from the lockfile or fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bunfig.toml sets [install] frozenLockfile = true, so `bun install` resolves from bun.lock and nothing else — it fails rather than quietly picking up a newer version, including a transitive one nobody chose. Config rather than a documented habit, because a supply-chain compromise does not wait for the one time somebody forgets a flag. On 2026-08-04 eleven cache packages — keyv, flat-cache, file-entry-cache, cacheable-request, cache-manager, the @cacheable/* scope and ecto — were published with a preinstall dropper that harvested npm and GitHub tokens, AWS and Kubernetes credentials, SSH and PEM keys, .env files and .claude/settings.json, then republished itself through any npm token it found, reaching 434 further packages across 1381 versions. This machine was unaffected only because nothing had installed since 2026-08-02. Verified on bun 1.3.10 rather than assumed, and the first result was wrong: with NO lockfile present, neither the flag nor the config refuses — bun simply creates one, so an initial test made the setting look ignored. Against a lockfile that no longer satisfies package.json, both exit 1 with "lockfile had changes, but lockfile is frozen". The config is honoured; the earlier reading was a bad test. `bun install` in this repo still reports 1058 installs, no changes. Applied to monorepo-mobile too, where flat-cache, file-entry-cache and keyv are present as eslint/got transitive deps — at versions old enough to be unaffected, which was luck rather than design. Documented in platform/CLAUDE.md and the workspace root, including the part that matters most: do NOT add --no-frozen-lockfile to a script, a Dockerfile or CI to make the error go away. The error means the lockfile and package.json disagree, and an unexplained lockfile change in a diff is precisely the signal this exists to produce. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 22 ++++++++++++++++++++++ bunfig.toml | 16 ++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 61f3840e..cdecf292 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -134,6 +134,28 @@ bun setup # guided install (writes .env, incl. PUBLIC_BUILD_ENV=productio Sidecar control is PM2, not npm scripts: `pm2 restart officer-`, `pm2 logs officer-`. See `docs/working-on-officer.md` for which process a given change needs restarted. +### Installs are frozen. Never resolve a dependency you did not ask for. + +`bunfig.toml` sets `[install] frozenLockfile = true`, so **`bun install` resolves from `bun.lock` and +nothing else** — it fails rather than quietly picking up a newer version, including a transitive one +nobody chose. Verified on bun 1.3.10: a lockfile that no longer satisfies `package.json` exits 1 with +`error: lockfile had changes, but lockfile is frozen`. + +It is config rather than a habit because a supply-chain compromise does not wait for the one time +somebody forgets a flag. On **2026-08-04** eleven cache packages — `keyv`, `flat-cache`, +`file-entry-cache`, `cacheable-request`, `cache-manager`, the `@cacheable/*` scope, `ecto` — were +published with a `preinstall` dropper that harvested npm and GitHub tokens, AWS and Kubernetes +credentials, SSH and PEM keys, `.env` files and `.claude/settings.json`, then republished itself +through any npm token it found. It reached 434 further packages across 1,381 versions. This machine was +unaffected only because nothing had installed since 2026-08-02. + +**To change a dependency:** edit `package.json`, run `bun install --no-frozen-lockfile` deliberately, +**read the lockfile diff**, and commit it. The friction is the point — an unexplained lockfile change +in a diff is the signal this exists to produce. + +**Do not** add `--no-frozen-lockfile` to a script, a Dockerfile or CI to make an error go away. The +error means the lockfile and `package.json` disagree, and that is worth a human look every time. + **Format your own files, not the whole dirty tree.** `bun format` globs `git diff --name-only HEAD`, so it rewrites every uncommitted file — including work in progress that isn't yours, which then shows up as unexplained whitespace churn in someone else's diff. Run `bunx prettier --write ` on the diff --git a/bunfig.toml b/bunfig.toml index 1cbcca00..a541ec98 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -1,3 +1,19 @@ +# Every install resolves from bun.lock and nothing else. `bun install` fails rather than quietly +# resolving a new version — including a transitive one nobody asked for. +# +# This is here rather than in a habit or a README because a supply-chain compromise does not wait for +# the one time somebody forgets the flag. On 2026-08-04 eleven cache packages (keyv, flat-cache, +# file-entry-cache, cacheable-request and friends) were published with a `preinstall` dropper that +# harvested npm and GitHub tokens, cloud credentials, SSH keys, .env files and .claude/settings.json, +# then republished itself through any token it found. It spread to 434 more packages. A floating +# transitive range is all it takes; a frozen lockfile is the difference between "we were not affected" +# and "we ran `bun install` on the wrong afternoon". +# +# When a dependency genuinely needs to change: edit package.json, run `bun install --no-frozen-lockfile` +# deliberately, READ the lockfile diff, and commit it. The friction is the point. +[install] +frozenLockfile = true + [serve.static] plugins = ["bun-plugin-tailwind"] env = "BUN_PUBLIC_*"