You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit b6e8707
Browse filesBrowse the repository at this point in the historyBrowse files
fix(cli,create-objectstack): scaffolds declare vitest ~4.1.12, outside GHSA-qxg6-89mg-rqv8, and still install on npm 10 (#25173)
Fixes#25165
Clause-②: no
Metadata-protocol: none
## What this does
- `SCAFFOLD_VITEST_RANGE` in `packages/cli/src/commands/init.ts` moves
from `~4.0.0` to `~4.1.12`. `os init -t plugin` and `os create plugin`
declare it in `devDependencies`. `SCAFFOLD_NPM_OVERRIDES`, which is
built from it, puts it in the `overrides` of every standalone `os init`
/ `os create plugin` manifest. The override's shape is unchanged.
- The blank template's `devDependencies.vitest` and `overrides.vitest`
are re-stamped from that constant by the write run of
`scripts/sync-scaffold-emission-policy.mjs`, the same run
`create-objectstack`'s `build` invokes. Both scaffolders read one
constant, and no value is kept by hand.
- A floor pin in `packages/cli/test/scaffold-npm-overrides.test.ts`: the
range's lowest admitted version must be at or above the advisory fix for
its major (4.1.12 on 4.x, 5.0.3 on 5.x). A major with no row is refused,
not passed.
- The comments on the constant, the override and the template test carry
the new measurement.
- `.changeset/25165-scaffold-vitest-advisory.md` bumps
`@objectstack/cli` and `create-objectstack` at `patch` and gives the
one-line move for existing projects.
- `content/docs/deployment/troubleshooting.mdx` (patch round): the npm
10 `edgesOut` entry's fix 2 prescribed `overrides.vitest` `~4.0.0`,
which installs vitest 4.0.18, inside the advisory. It now prescribes
`~4.1.12`. The entry names vitest 4.1.12's contradictory optional peer
as the cause, says a direct `vitest` devDependency must move with the
override (`EOVERRIDE`), and says how an existing `~4.0.0` project moves.
## The advisory
I read the advisory from the npm registry's bulk advisory endpoint
(`registry.npmjs.org/-/npm/v1/security/advisories/bulk`, 2026-10-22).
This container's egress proxy refuses `api.osv.dev` (403 at CONNECT).
- **GHSA-qxg6-89mg-rqv8**, `@vitest/mocker`: affected from 2.1.0-beta.7
up to the fix in 4.1.12, and from 5.0.0-beta.1 up to the fix in 5.0.3.
That confirms the card's range.
- **Also on the baseline.** `npm audit` of a baseline scaffold
(`~4.0.0`, which resolves vitest and `@vitest/mocker` to 4.0.18) also
lists:
- GHSA-82fw-gwwq-j7x9 (`@vitest/mocker`, from 2.1.0 up to the fix in
4.1.11);
- GHSA-5xrq-8626-4rwp (`vitest`, critical, from 4.0.0 up to the fix in
4.1.0).
- **What the new floor clears.** It clears all three, and
GHSA-xp26-3454-mjgp, which affects exactly 4.1.11 on the 4.x line.
## Route measurements
**Method.**
- Each manifest was installed in its own empty directory, with a fresh
`--cache` for every leg.
- The `@objectstack/*` ranges are `^18.0.0`, which resolves to the
published 18.0.0.
- After each install I read `vitest` and every `@vitest/mocker` copy
from `node_modules`, then ran `npm audit --json`.
**Manifests.**
- The blank template. For route B its bytes equal this PR's stamped
template.
- The `os init -t plugin`, `os create plugin`, `os init -t app` and `os
init -t empty` manifests. Their dependency blocks are byte-equal to what
this branch's source emits, driven through
`packages/cli/bin/run-dev.js`.
**npm versions.** 10.9.9 (the newest 10.x), 10.9.4 (the npm bundled with
this container's Node 22) and 11.21.1 (the newest 11.x).
In the table, "4.1.12" means the tree holds exactly one `vitest` and one
`@vitest/mocker`, both at that version.
| Route | Manifest | npm 10.9.9 | npm 11.21.1 | npm 10.9.4 |
|:--|:--|:--|:--|:--|
| Baseline: `~4.0.0` + override | blank | exit 0 · 4.0.18 · audit lists
the 3 advisories above | not measured | not measured |
| Baseline | `os init -t plugin`, `os create plugin` | exit 0 · 4.0.18 ·
audit lists them | exit 0 · 4.0.18 · audit lists them | not measured |
| A: `~4.1.12`, no override | blank, `os init -t plugin`, `os create
plugin` | **exit 1**, `Cannot read properties of null (reading
'edgesOut')` | blank: exit 0 · 4.1.12 | not measured |
| A | `os init -t app` (declares no vitest), no override | **exit 1**,
`edgesOut` | not measured | not measured |
| **B, chosen: `~4.1.12` + override** | blank, `os init -t plugin`, `os
create plugin` | exit 0 · 4.1.12 · audit: no `vitest` / `@vitest/*`
entry | exit 0 · the same | exit 0 · the same |
| B | `os init -t app`, `os init -t empty` | exit 0 · no vitest
installed · audit: none | exit 0 · the same | not measured |
| C: `^5.0.5`, no override | blank | **exit 1**, `edgesOut` | exit 0 ·
5.0.5 at the root plus a nested 4.1.12 | not measured |
| D: `^5.0.5` + override | blank, `os init -t plugin`, `os create
plugin` | exit 0 · 5.0.5 · audit: no vitest entry | exit 0 · the same |
not measured |
| D′: exact `5.0.4` + override (simulates `latest` having moved past the
range) | blank | exit 0 · 5.0.4 | exit 0 · 5.0.4 | not measured |
`os create plugin` audits to 0 entries on routes B and D. The other
manifests keep 9 moderate entries on every route, and all 9 trace to one
advisory: GHSA-w5hq-g745-h8pq on `uuid` 8.x, reached through `exceljs`
from `@objectstack/rest` (see Acceptance notes).
## Why route B holds on npm 10
- **The crash comes from vitest 4.1.12's own peer set.** It peers
`@vitest/browser-webdriverio ^4.1.11`, and that package's only release,
4.1.11, peers `vitest: 4.1.11` exactly. With the range at `~4.1.12`, the
root vitest reaches that peer set directly, so route A crashes every
manifest that declares vitest. `-t app` crashes too, through optional
vitest peers deeper in its closure.
- **The `vitest` override alone settles it.** With `overrides.vitest`
equal to the declared range, npm 10.9.4 and 10.9.9 install every
manifest. No `@vitest/browser-webdriverio` override is needed. The
override is the existing `SCAFFOLD_NPM_OVERRIDES` mechanism, unchanged
in shape.
- **It does not depend on where `latest` points.** `latest` is vitest
5.0.5, outside `~4.1.12`, and the range still installs. The old
comment's reading against `^5.0.0` ("installs only because it resolves
to exactly the version `latest` names") was taken without an override.
With the override, an exact pin below `latest` (5.0.4) installs too.
- **#24462's canary.** On npm 10.9.9 the full blank template (every
template file, with this PR's `package.json`):
- installs with exit 0;
- passes `npm test`: `RUN v4.1.12`, `Test Files 1 passed (1)`, `Tests 1
passed (1)`;
- builds with `npm run build`, exit 0, `Artifact:
dist/objectstack.json`.
The canary installs published artifacts, so it reads this change only
after a release.
- **The plugin scaffolds.** Each was emitted from this branch's source
and installed on npm 10.9.9 (vitest and `@vitest/mocker` 4.1.12). `npm
run build` and `npm run typecheck` exit 0 for both. Their `npm test`
result is in Acceptance notes.
## Why 4.1.x and not 5.x
Routes B and D both install on npm 10 and npm 11, and both clear the
advisory. On the four axes:
- **Business need.** The scaffold's test (`note.test.ts`) needs nothing
from vitest 5. vitest 5.0.5 declares `engines.node` `^22.12.0 || ^24.0.0
|| >=26.0.0`, which is narrower than the `>=22.0.0` that
`@objectstack/cli` and `create-objectstack` declare. vitest 4.1.12
declares `^20.0.0 || ^22.0.0 || >=24.0.0`, the same as 4.0.18. So only
route D gives a new project on Node 22.0 to 22.11 an engine warning, or
a refusal under `engine-strict`.
- **Long-term.** The monorepo's own manifests run `vitest ^4.1.11`,
re-locked to 4.1.12 for the same advisory family (#25163). A scaffold on
that line runs the vitest the platform is tested with. Moving to 5 is a
deliberate change of its own, for the monorepo and the scaffolds
together.
- **AI error-proofing.** A patch-line move inside major 4 changes no API
that a generated test can call. The floor pin turns a move below the fix
into a red test instead of a quiet regression.
- **Startup scope.** This is the smallest change that clears the
advisory: one constant, its stamp and its pin.
## Tests (head `4eb2ff4c4`)
- **CLI unit tier.** `pnpm --filter @objectstack/cli exec vitest run
--project unit --maxWorkers=2`, run under the verify lock.
- First run: `VERDICT command-exit 1`, `Test Files 2 failed | 320 passed
(322)`, `Tests 4728 passed | 29 skipped`.
- Both failures were `test/published-subpath-console.pin.test.ts` and
`test/published-subpath-hook-body.pin.test.ts` refusing an unbuilt
`packages/cli/dist`: a missing build, not a test failure.
- After the package build, those two files plus
`test/scaffold-npm-overrides.test.ts` (verbose): `VERDICT command-exit
0`, `Test Files 3 passed (3)`, `Tests 41 passed (41)`. That run includes
the new floor case: `its lowest admitted version is at or above the
advisory fix for its major`.
- The integration tier is left to CI: the diff touches no spawn entry
and no integration-tier file.
- **create-objectstack.** `pnpm --filter create-objectstack test`: `Test
Files 18 passed (18)`, `Tests 278 passed (278)`.
- **Typecheck.** `pnpm --filter @objectstack/cli typecheck && pnpm
--filter create-objectstack typecheck`: `VERDICT command-exit 0`. `tsc
-p tsconfig.test.json --listFiles` in `packages/cli` lists
`scaffold-npm-overrides.test.ts` once. `create-objectstack`'s `tsc
--listFiles` lists `template-consistency.test.ts` once.
- **ESLint.** `pnpm exec eslint --no-inline-config` over the 3 changed
TypeScript files: exit 0.
- **Ablation of the floor pin.** The fix was committed first, and the
mutation ran through `scripts/ablation-replace.mjs` in wrap mode.
- Mutation: `SCAFFOLD_VITEST_RANGE = '~4.1.12'` became `'~4.0.0'`. The
anchor went from 1 to 0 hits, and the blob changed from `47a2518d9a21`
to `8da3d5de0b48`.
- Result: `Tests 1 failed | 11 passed (12)`, with `SCAFFOLD_VITEST_RANGE
"~4.0.0" admits 4.0.0, below the fix 4.1.12`. The 11 derived-equality
cases stay green under the mutation, which is why the floor pin is
needed.
- Restore: the blob equals the HEAD blob (`47a2518d9a21`), `git diff
HEAD` is empty and `git status` is clean.
- The test imports `../src/commands/init.js`, so no `dist` is on its
path.
## Gates (head `4eb2ff4c4`)
- **Derivation.** `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`: 84 commands for 5 paths against merge base
`425c743fd`.
- **Runs.** Each command was run byte for byte, with its exit code
captured before any pipe.
- First pass: 80 commands exited 0.
- `check:dual-build-cjs-loads`, `check:i18n`, `check:i18n-coverage` and
`check:i18n-walk-parity` exited 3 (PREREQUISITE NOT MET: no built
`dist`).
- After a full package build (`turbo run build`, 75/75 tasks, under the
lock), all four re-ran to exit 0.
- **Reconciliation.** `--ran`: `84 derived, 84 run, 0 NOT-MEASURED, 0
UNRUN`, exit 0.
- **Governed surfaces.** `node scripts/pm/check-governed-merges.mjs
--branch claude/issue-25165-scaffold-vitest-advisory`: exit 0, 0 of 5
paths governed, 176 changed lines.
- **The stamp gates.** Both are in the derived set and exited 0:
`check:scaffold-emission-policy` (the template and the constant agree)
and `check:template-version-sync`.
## Patch round (head `f355f41a2`)
- `origin/main` was merged first (`a22074c89`), then the troubleshooting
page was fixed (`f355f41a2`). The diff is now 6 paths against merge base
`ab488d9b7`.
- `dispatch-gates --commands` derives 112 commands. Each was run with
its exit code captured before any pipe, after a full package build
(75/75 tasks). All 112 exited 0. `--ran` reports `112 run, 0
NOT-MEASURED`, exit 0.
- `check-governed-merges`: 0 of 6 paths governed, 186 changed lines.
- On the merged head, `test/scaffold-npm-overrides.test.ts` passes 12/12
and `create-objectstack` passes 278/278.
- CI on `f355f41a2`: 39 check-runs succeeded and 7 were skipped. They
include `Check Documentation Links`, `Build Docs`, `Scaffold outside the
monorepo, install, build` and `Check Changeset`.
## Acceptance notes
- **A stale comment.** In `scripts/sync-scaffold-emission-policy.mjs`,
the comment on the `devDependencies.vitest` row still says the constant
"holds why it is `~4.0.0`". It is a comment outside the file surface,
and the script reads the constant's value, not the comment.
- **The remaining audit entries.** A fresh scaffold's other `npm audit`
entries all come from GHSA-w5hq-g745-h8pq on `uuid` 8.x, through
`exceljs` `^4.4.0` in the published `@objectstack/rest`. The workspace
overrides `uuid` in `pnpm-workspace.yaml`, which npm consumers never
read. That is not this card.
- **The plugin scaffolds' `npm test`.** `os init -t plugin` and `os
create plugin` emit a `test` script but no test file, so `npm test`
exits 1 with "No test files found". It does the same on the baseline
4.0.18 (measured on `-t plugin`). That is not this card.
- **origin/main** (`ab488d9b7`) was merged into the branch in the patch
round (merge commit `a22074c89`). It had no conflicts.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01AFrKwndmrCLtF5V9hNtrw3)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
fix(cli,create-objectstack): new projects declare vitest `~4.1.12`, outside GHSA-qxg6-89mg-rqv8, and still install on npm 10
7
+
8
+
Clause-②: no
9
+
Metadata-protocol: none
10
+
11
+
The vitest range every scaffold declares moves from `~4.0.0` to `~4.1.12`: the `create-objectstack` blank template, `os init -t plugin` and `os create plugin` (in `devDependencies`), and the npm `overrides` block that every standalone scaffold emits, `os init -t app` and `-t empty` included. `~4.0.0` resolved to vitest and `@vitest/mocker` 4.0.18, inside GHSA-qxg6-89mg-rqv8 (`@vitest/mocker` from 2.1.0-beta.7 up to the fix in 4.1.12), which has no 4.0.x fix. A new project now resolves vitest and `@vitest/mocker` 4.1.12 or a later 4.1.x patch, and `npm audit` of a fresh scaffold lists no vitest or `@vitest/*` advisory.
12
+
13
+
npm 10, the npm Node 22 bundles, still installs every scaffold. The `overrides.vitest` entry, which equals `devDependencies.vitest`, is what makes that hold: it settles vitest 4.1.12's contradictory optional peer (`@vitest/browser-webdriverio`), which npm 10 otherwise crashes on with `Cannot read properties of null (reading 'edgesOut')`. Measured on npm 10.9.4, 10.9.9 and 11.21.1. pnpm ignores npm's `overrides` field.
14
+
15
+
**An existing project made by an earlier `create-objectstack`, `os init` or `os create plugin`** keeps its own `~4.0.0`. To move it, set `overrides.vitest` to `~4.1.12` in `package.json`, and `devDependencies.vitest` too where the project declares it, then run `npm install`. npm refuses an override that differs from the direct dependency (`EOVERRIDE`), so the two move together.
Copy file name to clipboardExpand all lines: content/docs/deployment/troubleshooting.mdx
+6-4Lines changed: 6 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,19 +12,21 @@ Solutions to the most common issues encountered when working with ObjectStack.
12
12
13
13
### "Cannot read properties of null (reading 'edgesOut')" from npm install
14
14
15
-
**Symptom:** npm exits 1 with `npm error Cannot read properties of null (reading 'edgesOut')` while installing `@objectstack/cli` or `@objectstack/plugin-auth`. This includes the install step of `npm create objectstack` and `npm install -g @objectstack/cli`.
15
+
**Symptom:** npm exits 1 with `npm error Cannot read properties of null (reading 'edgesOut')` while installing `@objectstack/cli` or `@objectstack/plugin-auth`. This includes `npm install -g @objectstack/cli`, and `npm install` in a project whose `package.json` has no `vitest` override (fix 2).
16
16
17
-
**Cause:** npm 10, the npm that Node 22 ships, crashes on an optional `vitest` peer in the auth library's dependency tree: the newest vitest 4 has no matching `@vitest/browser-webdriverio` release. npm 11 and pnpm install the same packages, and `npm ci` from a committed lockfile is not affected.
17
+
**Cause:** npm 10, the npm that Node 22 ships, crashes on the newest vitest 4, 4.1.12. Its optional peer `@vitest/browser-webdriverio ^4.1.11` has one matching release, 4.1.11, and that release requires exactly vitest 4.1.11. npm 10 reaches vitest through an optional `vitest` peer in the auth library's dependency tree, or through your project's own `vitest`. npm 11 and pnpm install the same packages, and `npm ci` from a committed lockfile is not affected.
18
18
19
19
**Fix** (either one):
20
20
21
21
1.**Use npm 11** (it needs Node 22.9+): run `npm install -g npm@11`, then repeat the install. This is the only fix for `npm install -g`, because a global install reads no `overrides`. In a Dockerfile, install under a pinned npm 11, as the [self-built runtime recipe](/docs/deployment/self-hosting#docker-official-image--the-standard-path) does.
22
-
2.**Stay on npm 10 and pin vitest** in your project's root `package.json`. Then run `npm install` again and commit the lockfile:
22
+
2.**Stay on npm 10 and pin vitest** in your project's root `package.json`. The override puts every `vitest` in the tree, optional peers included, on one range, and npm 10 installs it. Then run `npm install` again and commit the lockfile:
23
23
24
24
```json title="package.json"
25
-
"overrides": { "vitest": "~4.0.0" }
25
+
"overrides": { "vitest": "~4.1.12" }
26
26
```
27
27
28
+
If `devDependencies` lists `vitest`, set it to the same `~4.1.12`: npm refuses an override that differs from a direct dependency (`EOVERRIDE`). A project created by `npm create objectstack`, `os init` or `os create plugin` already has this block. If yours reads `~4.0.0`, an earlier scaffold's value, change both to `~4.1.12`: `~4.0.0` installs vitest 4.0.18, which is affected by security advisory GHSA-qxg6-89mg-rqv8.
29
+
28
30
This entry, the npm 11 steps in [Self-hosting](/docs/deployment/self-hosting), and the npm 10 notes in [Getting Started](/docs/getting-started#prerequisites) and [Your First Project](/docs/getting-started/your-first-project#prerequisites) can be removed once `@vitest/browser-webdriverio` publishes the version that `npm view vitest dist-tags` lists as `V4`, and a plain npm 10 install of `@objectstack/plugin-auth` passes.
0 commit comments