mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
build(docker): mirror the hono and valibot pins into /opt/prisma-cli
The image carries two installs. pnpm builds the app tree from pnpm-workspace.yaml; npm builds /opt/prisma-cli and reads only its own overrides field. The scan reported @hono/node-server@1.19.11 (GHSA-frvp-7c67-39w9, CVE-2026-39406) and valibot@1.2.0 (CVE-2026-59952) under that path while both packages were pinned and clean in the app tree. Same shape as the deepmerge-ts finding, same cause. Rebuilt this exact install both ways to confirm rather than assume. Without the two fields npm resolves @hono/node-server 1.19.11 and valibot 1.2.0, which are the two versions the scan named. With them it resolves 1.19.17 and 1.4.2, and prisma --version still reports 7.8.0. Both arrive through prisma -> @prisma/dev. The lockstep guard could not have caught this. Its workspace matcher started a package name at [^"@], so every scoped key was invisible to it: @hono/node-server was in the workspace file and absent from the Dockerfile, the shared set never contained it, and the guard stayed green. Scoped keys parse now, a test pins the reduction itself so the blind spot cannot return quietly, and a second test asserts that the three packages the image scan has actually flagged under /opt/prisma-cli keep their npm-side mirror. Both fail when the pin is removed or moved out of step.
This commit is contained in:
1 parent
f62cd59789
commit
c9f8aee476
2 files changed
+65
-4
No files matched your search
+10
@@ -191,11 +191,21 @@ COPY --from=builder /app/src/generated ./src/generated
|
||||
# still reports 7.8.0. Keep the range in step with the entry in
|
||||
# `pnpm-workspace.yaml` — they are one decision applied to two package
|
||||
# managers, and a fix that lands in only one of them is the defect above.
|
||||
#
|
||||
# The same class recurred on 2026-09-02: the image scan reported
|
||||
# `@hono/node-server@1.19.11` (GHSA-frvp-7c67-39w9, CVE-2026-39406) and
|
||||
# `valibot@1.2.0` (CVE-2026-59952) under this path, both reached through
|
||||
# `prisma -> @prisma/dev`, while the app tree carried 1.19.17 and a pin the
|
||||
# npm install never reads. Rebuilding this install both ways confirms it:
|
||||
# without the two fields below npm resolves 1.19.11 and 1.2.0, with them it
|
||||
# resolves 1.19.17 and 1.4.2, and `prisma --version` still reports 7.8.0.
|
||||
RUN mkdir -p /opt/prisma-cli && \
|
||||
cd /opt/prisma-cli && \
|
||||
npm init -y && \
|
||||
npm pkg set 'overrides.deepmerge-ts=^8.0.0' && \
|
||||
npm pkg set 'overrides.mysql2=^3.22.0' && \
|
||||
npm pkg set 'overrides.@hono/node-server=^1.19.15' && \
|
||||
npm pkg set 'overrides.valibot=^1.4.2' && \
|
||||
npm install --omit=dev prisma@7.8.0 @prisma/engines@7.8.0 tsx@4.23.1 dotenv@17.4.2 && \
|
||||
ln -sfn /opt/prisma-cli/node_modules/.bin/tsx /usr/local/bin/healthlog-tsx && \
|
||||
ln -sfn /opt/prisma-cli/node_modules/dotenv /app/node_modules/dotenv && \
|
||||
|
||||
@@ -20,6 +20,12 @@
|
||||
* would encode noise. It asserts agreement where both files speak, which is
|
||||
* the failure that actually happened.
|
||||
*
|
||||
* The matcher had a blind spot of its own until 2026-09-02: it refused any
|
||||
* workspace key beginning with `@`, so every scoped package was invisible to
|
||||
* it. `@hono/node-server` was pinned on one side and unpinned on the other and
|
||||
* the guard stayed green, which is the same shape of green the guard exists to
|
||||
* prevent. Scoped keys parse now, and a test below pins that they do.
|
||||
*
|
||||
* Limit, written down so the next reader does not over-trust a green run: this
|
||||
* compares declared ranges. It cannot see a NEW vulnerable transitive that
|
||||
* neither file mentions. Only the image scan can, and that job runs on
|
||||
@@ -44,17 +50,30 @@ function dockerfileOverrides(): Map<string, string> {
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces an overrides key to the bare package name. The key carries a version
|
||||
* selector, so `deepmerge-ts@<8.0.0` and a bare `deepmerge-ts` both land on the
|
||||
* same name. A scoped name opens with its own `@`, so the selector starts at
|
||||
* the SECOND one, not the first.
|
||||
*/
|
||||
function packageNameOf(key: string): string {
|
||||
const trimmed = key.trim();
|
||||
const selectorAt = trimmed.startsWith("@")
|
||||
? trimmed.indexOf("@", 1)
|
||||
: trimmed.indexOf("@");
|
||||
return (selectorAt === -1 ? trimmed : trimmed.slice(0, selectorAt)).trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* `"<name>@<selector>": "<range>"` entries under the workspace `overrides:`
|
||||
* block. The key carries a version selector, so `deepmerge-ts@<8.0.0` and a
|
||||
* bare `deepmerge-ts` both have to reduce to the same package name.
|
||||
* block.
|
||||
*/
|
||||
function workspaceOverrides(): Map<string, string> {
|
||||
const found = new Map<string, string>();
|
||||
const pattern = /^\s+"([^"@][^"]*?)(?:@[^"]*)?"\s*:\s*"([^"]+)"/gm;
|
||||
const pattern = /^\s+"([^"]+)"\s*:\s*"([^"]+)"/gm;
|
||||
const block = WORKSPACE.split(/^allowBuilds:/m)[0];
|
||||
for (const match of block.matchAll(pattern)) {
|
||||
found.set(match[1].trim(), match[2].trim());
|
||||
found.set(packageNameOf(match[1]), match[2].trim());
|
||||
}
|
||||
return found;
|
||||
}
|
||||
@@ -74,6 +93,23 @@ describe("dependency overrides — pnpm workspace and the npm install agree", ()
|
||||
expect(workspace.has("deepmerge-ts")).toBe(true);
|
||||
});
|
||||
|
||||
it("reads scoped workspace keys rather than skipping them", () => {
|
||||
// The old matcher started the name at `[^"@]`, so `@hono/node-server` was
|
||||
// never in the shared set and could disagree across the two files without
|
||||
// failing anything. Both halves are asserted: the reduction itself, and
|
||||
// that a real scoped entry survives it.
|
||||
expect(packageNameOf("@hono/node-server@<1.19.15")).toBe(
|
||||
"@hono/node-server",
|
||||
);
|
||||
expect(packageNameOf("dompurify@<3.4.13")).toBe("dompurify");
|
||||
expect(packageNameOf("js-yaml@>=4.0.0 <4.3.1")).toBe("js-yaml");
|
||||
expect(packageNameOf("deepmerge-ts")).toBe("deepmerge-ts");
|
||||
|
||||
const workspace = workspaceOverrides();
|
||||
const scoped = [...workspace.keys()].filter((name) => name.startsWith("@"));
|
||||
expect(scoped.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("pins the same range wherever both files name the same package", () => {
|
||||
const docker = dockerfileOverrides();
|
||||
const workspace = workspaceOverrides();
|
||||
@@ -91,6 +127,21 @@ describe("dependency overrides — pnpm workspace and the npm install agree", ()
|
||||
}
|
||||
});
|
||||
|
||||
it("mirrors every pin the /opt/prisma-cli tree was scanned on", () => {
|
||||
// These three were each reported by the image scan under
|
||||
// `/opt/prisma-cli`, reached through `prisma`. Losing a mirror is not a
|
||||
// style regression, it is the vulnerable version coming back.
|
||||
const docker = dockerfileOverrides();
|
||||
for (const name of ["deepmerge-ts", "@hono/node-server", "valibot"]) {
|
||||
expect(
|
||||
docker.has(name),
|
||||
`${name} was flagged under /opt/prisma-cli and needs an ` +
|
||||
`\`npm pkg set 'overrides.${name}=...'\` line in the Dockerfile; ` +
|
||||
`pnpm-workspace.yaml does not reach that install.`,
|
||||
).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps the override ahead of the version Prisma's config asks for", () => {
|
||||
// `@prisma/config` requests deepmerge-ts 7.x; the advisory is fixed in
|
||||
// 8.0.0. Anything that lets a 7.x back in re-opens CVE-2026-40345.
|
||||
|
||||
Reference in new issue
Block a user