`scripts/__tests__` has **seven** files failing on main. None are in the
merge gate, so nobody is blocked — which is exactly how this survived.
This fixes the one that names a real risk.
## It reads like a deleted security pin. It isn't.
```
AssertionError: package.json pnpm.overrides: protobufjs range undefined
must include an explicit semver version
```
#2220 moved pnpm overrides from `package.json` to `pnpm-workspace.yaml`
for pnpm 11 readiness. The assertion kept reading `package.json`, where
`pnpm.overrides` is now `{}`.
**Nothing was ever exposed**: `pnpm-workspace.yaml` still pins
`protobufjs: ^7.5.8` and the lockfile resolves `7.6.5`, comfortably
above the 7.5.5 floor. The sibling assertion that checks lockfile
resolutions has been passing the whole time — which is the only reason
this was survivable.
But no reader could distinguish this message from a genuinely removed
pin without doing the archaeology, and a guard that is permanently red
while naming a real risk teaches its readers that red means nothing
here. That is worse than no guard: it launders a real removal into
background noise.
## Measured both ways
| change to `pnpm-workspace.yaml` | result |
|---|---|
| pin removed | **fails** — `protobufjs range undefined must include an
explicit semver version` |
| pin lowered to `^7.4.0` | **fails** — `range ^7.4.0 is below required
floor 7.5.5` |
| unchanged | passes |
## Second assertion
`package.json` must declare **no** `pnpm.overrides`. If overrides move
again — or come back — that fails and points at the next reader, instead
of letting the floor go silently unchecked. That is precisely the
failure mode #2220 produced, and nothing would otherwise catch a second
occurrence.
## The other six
Left alone deliberately; each needs its own diagnosis and they are
unrelated to one another (I checked — the "seven files, one failure
each" pattern looked like a common cause and is not):
- `workflow-reliability-release-check` — manifest references
`workflow-definition-store.test.ts`, which no longer exists. **Likely a
real signal**: a release check pointing at a deleted seam file covers
nothing. Best next one to pick up.
- `ci-test-shard-timings` — snapshot references missing test files;
affects shard balancing.
- `verify-fast`, `engine-vitest-gate-policy` — validator/canary list
drift.
- `plugin-authoring-docs` — a TOC anchor for a heading containing `&`.
- `release-prompt-gate` — dry-run no longer exits before the proceed
confirmation.
## Verification
`node --test scripts/__tests__/dependency-security-floor.test.mjs` **4
passed** · `pnpm test:gate` 161 + 13 + 487 + 71 · lint · changesets —
green.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Updated security validation to correctly read protobufjs version
overrides from the workspace configuration.
* Added safeguards to detect outdated override locations and help
prevent future security-check regressions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
128 lines
5.6 KiB
JavaScript
128 lines
5.6 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readdir, readFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
import YAML from "yaml";
|
|
|
|
const root = process.cwd();
|
|
|
|
const dependencyFloors = [
|
|
{ name: "protobufjs", minimum: "7.5.5", manifestSections: ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"], lockfileMatcher: /^protobufjs@(.*)$/ },
|
|
{ name: "vitest", minimum: "4.1.0", manifestSections: ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"], lockfileMatcher: /^vitest@(.*)$/ },
|
|
{ name: "@vitest/coverage-v8", minimum: "4.1.0", manifestSections: ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"], lockfileMatcher: /^@vitest\/coverage-v8@(.*)$/ },
|
|
];
|
|
|
|
const manifestRoots = ["packages", "plugins"];
|
|
const ignoredPathParts = new Set(["node_modules", "dist", "build", "coverage", ".turbo"]);
|
|
|
|
function compareVersions(a, b) {
|
|
const pa = a.split(".").map((part) => Number.parseInt(part, 10));
|
|
const pb = b.split(".").map((part) => Number.parseInt(part, 10));
|
|
for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
|
|
const ai = Number.isFinite(pa[i]) ? pa[i] : 0;
|
|
const bi = Number.isFinite(pb[i]) ? pb[i] : 0;
|
|
if (ai !== bi) return ai - bi;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
function minVersionFromRange(range) {
|
|
const version = String(range).match(/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/u)?.[0];
|
|
return version?.split(/[+-]/u)[0] ?? null;
|
|
}
|
|
|
|
function assertRangeMeetsFloor({ location, name, range, minimum }) {
|
|
const minVersion = minVersionFromRange(range);
|
|
assert.ok(minVersion, `${location}: ${name} range ${range} must include an explicit semver version`);
|
|
assert.ok(compareVersions(minVersion, minimum) >= 0, `${location}: ${name} range ${range} is below required floor ${minimum}`);
|
|
}
|
|
|
|
async function collectPackageManifests(dir) {
|
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
const manifests = [];
|
|
for (const entry of entries) {
|
|
const fullPath = path.join(dir, entry.name);
|
|
if (ignoredPathParts.has(entry.name)) continue;
|
|
if (entry.isDirectory()) {
|
|
manifests.push(...(await collectPackageManifests(fullPath)));
|
|
} else if (entry.name === "package.json") {
|
|
manifests.push(fullPath);
|
|
}
|
|
}
|
|
return manifests;
|
|
}
|
|
|
|
async function sourceManifestPaths() {
|
|
const manifests = [path.join(root, "package.json")];
|
|
for (const manifestRoot of manifestRoots) {
|
|
manifests.push(...(await collectPackageManifests(path.join(root, manifestRoot))));
|
|
}
|
|
return manifests.sort();
|
|
}
|
|
|
|
test("source manifests keep vulnerable dependency floors out", async () => {
|
|
for (const manifestPath of await sourceManifestPaths()) {
|
|
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
const relativePath = path.relative(root, manifestPath);
|
|
for (const floor of dependencyFloors) {
|
|
for (const section of floor.manifestSections) {
|
|
const range = manifest[section]?.[floor.name];
|
|
if (range) assertRangeMeetsFloor({ location: `${relativePath} ${section}`, name: floor.name, range, minimum: floor.minimum });
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
/*
|
|
FNXC:DependencySecurity 2026-07-31-11:20:
|
|
The override moved; the guard did not follow it.
|
|
|
|
#2220 relocated pnpm overrides from `package.json` to `pnpm-workspace.yaml` for pnpm 11, and this
|
|
assertion kept reading `package.json`. It has been failing ever since, on a non-blocking lane, saying
|
|
"protobufjs range undefined" — which reads exactly like the security floor was DELETED. It was not:
|
|
`pnpm-workspace.yaml` still pins `^7.5.8` and the lockfile resolves 7.6.5.
|
|
|
|
That is the expensive kind of stale test. A red guard that names a real risk teaches its readers that
|
|
red means nothing here, and the next reader cannot tell this from an actual removed pin without
|
|
doing the archaeology. Read the file the overrides actually live in, so removing the pin fails again.
|
|
*/
|
|
test("pnpm overrides pin transitive protobufjs to a safe floor", async () => {
|
|
const workspace = YAML.parse(await readFile(path.join(root, "pnpm-workspace.yaml"), "utf8"));
|
|
assertRangeMeetsFloor({
|
|
location: "pnpm-workspace.yaml overrides",
|
|
name: "protobufjs",
|
|
range: workspace?.overrides?.protobufjs,
|
|
minimum: "7.5.5",
|
|
});
|
|
});
|
|
|
|
test("the protobufjs floor is asserted where overrides actually live", async () => {
|
|
/*
|
|
The guard above is only meaningful while `pnpm-workspace.yaml` is the overrides home. If they move
|
|
again, this fails and points at the next reader rather than letting the floor go unchecked in
|
|
silence — the failure mode #2220 produced.
|
|
*/
|
|
const rootManifest = JSON.parse(await readFile(path.join(root, "package.json"), "utf8"));
|
|
assert.equal(
|
|
Object.keys(rootManifest.pnpm?.overrides ?? {}).length,
|
|
0,
|
|
"package.json declares pnpm.overrides again — point the floor assertion above at it, or at both",
|
|
);
|
|
});
|
|
|
|
test("lockfile resolutions satisfy dependency security floors", async () => {
|
|
const lockfile = YAML.parse(await readFile(path.join(root, "pnpm-lock.yaml"), "utf8"));
|
|
const packageKeys = Object.keys(lockfile.packages ?? {});
|
|
for (const floor of dependencyFloors) {
|
|
const matches = packageKeys
|
|
.map((key) => key.replace(/^\//u, ""))
|
|
.map((key) => key.match(floor.lockfileMatcher)?.[1])
|
|
.filter(Boolean)
|
|
.map((suffix) => suffix.split("(")[0]);
|
|
assert.ok(matches.length > 0, `pnpm-lock.yaml must include at least one ${floor.name} resolution`);
|
|
for (const version of matches) {
|
|
assert.ok(compareVersions(version, floor.minimum) >= 0, `pnpm-lock.yaml resolves ${floor.name}@${version}, below required floor ${floor.minimum}`);
|
|
}
|
|
}
|
|
});
|