mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
Merge pull request #808 from MBombeck/fix/prisma-cli-deepmerge-override
Pin deepmerge-ts in the npm install the workspace override cannot reach
This commit is contained in:
2 files changed
+119
No files matched your search
+18
@@ -157,9 +157,27 @@ COPY --from=builder /app/src/generated ./src/generated
|
||||
# the maintenance scripts already shipped in the standalone tree. The Prisma
|
||||
# config loads dotenv from /app, so expose the isolated copy there before
|
||||
# stripping npm/Corepack and their caches from the runtime surface.
|
||||
#
|
||||
# This install belongs to npm, not pnpm, so the `overrides` block in
|
||||
# `pnpm-workspace.yaml` does not reach it: a package pinned there for the app
|
||||
# tree still arrives here at whatever version Prisma asks for. That is not
|
||||
# hypothetical. `prisma@7.8.0` resolves `@prisma/config@7.8.0`, which resolves
|
||||
# `deepmerge-ts@7.1.5`, and the image scan reported CVE-2026-40345 (HIGH,
|
||||
# stack exhaustion on recursive merges) against `/opt/prisma-cli` on a commit
|
||||
# whose dependency audit over the app tree was green. Two installs, two
|
||||
# override mechanisms, one of them set. `overrides` is npm's own mechanism and
|
||||
# `npm pkg set` writes it into the package.json that `npm init -y` just
|
||||
# created, before anything resolves.
|
||||
#
|
||||
# Measured by rebuilding this exact install both ways: without the field
|
||||
# `deepmerge-ts@7.1.5` lands, with it `8.0.1` lands and `prisma --version`
|
||||
# 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.
|
||||
RUN mkdir -p /opt/prisma-cli && \
|
||||
cd /opt/prisma-cli && \
|
||||
npm init -y && \
|
||||
npm pkg set 'overrides.deepmerge-ts=^8.0.0' && \
|
||||
npm install --omit=dev prisma@7.8 @prisma/engines@7.8 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 && \
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* The two override mechanisms agree, or the build fails here.
|
||||
*
|
||||
* The image carries two independent installs. `pnpm install` builds the app
|
||||
* tree and honours the `overrides` block in `pnpm-workspace.yaml`. A second,
|
||||
* npm-owned install builds `/opt/prisma-cli` (Dockerfile) and honours nothing
|
||||
* from that file — it reads its own `overrides` field instead.
|
||||
*
|
||||
* On 2026-08-21 only the first one was set. The dependency audit over the app
|
||||
* tree passed, and the Trivy image scan reported CVE-2026-40345 against
|
||||
* `deepmerge-ts@7.1.5` under `/opt/prisma-cli`, pulled in by
|
||||
* `prisma -> @prisma/config`. A pin that covers one of two installs reads
|
||||
* exactly like a pin that covers both, right up until something scans the
|
||||
* artefact rather than the lockfile.
|
||||
*
|
||||
* So: every package pinned in BOTH places must be pinned to the same range,
|
||||
* and the Dockerfile's pin must survive as long as the workspace one does.
|
||||
* This does NOT assert that every workspace override is mirrored — most exist
|
||||
* for packages `/opt/prisma-cli` never installs, and demanding a mirror there
|
||||
* would encode noise. It asserts agreement where both files speak, which is
|
||||
* the failure that actually happened.
|
||||
*
|
||||
* 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
|
||||
* main-push, not on pull requests.
|
||||
*/
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const ROOT = process.cwd();
|
||||
const DOCKERFILE = readFileSync(join(ROOT, "Dockerfile"), "utf8");
|
||||
const WORKSPACE = readFileSync(join(ROOT, "pnpm-workspace.yaml"), "utf8");
|
||||
|
||||
/** `npm pkg set 'overrides.<name>=<range>'` occurrences in the Dockerfile. */
|
||||
function dockerfileOverrides(): Map<string, string> {
|
||||
const found = new Map<string, string>();
|
||||
const pattern = /npm\s+pkg\s+set\s+['"]overrides\.([^='"]+)=([^'"]+)['"]/g;
|
||||
for (const match of DOCKERFILE.matchAll(pattern)) {
|
||||
found.set(match[1].trim(), match[2].trim());
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* `"<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.
|
||||
*/
|
||||
function workspaceOverrides(): Map<string, string> {
|
||||
const found = new Map<string, string>();
|
||||
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());
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
describe("dependency overrides — pnpm workspace and the npm install agree", () => {
|
||||
it("finds the Dockerfile's npm-side overrides at all", () => {
|
||||
const docker = dockerfileOverrides();
|
||||
// A zero-match regex would make every assertion below vacuously true.
|
||||
// This is the check that keeps the guard honest about its own matcher.
|
||||
expect(docker.size).toBeGreaterThan(0);
|
||||
expect(docker.has("deepmerge-ts")).toBe(true);
|
||||
});
|
||||
|
||||
it("finds the workspace overrides at all", () => {
|
||||
const workspace = workspaceOverrides();
|
||||
expect(workspace.size).toBeGreaterThan(0);
|
||||
expect(workspace.has("deepmerge-ts")).toBe(true);
|
||||
});
|
||||
|
||||
it("pins the same range wherever both files name the same package", () => {
|
||||
const docker = dockerfileOverrides();
|
||||
const workspace = workspaceOverrides();
|
||||
|
||||
const shared = [...docker.keys()].filter((name) => workspace.has(name));
|
||||
expect(shared.length).toBeGreaterThan(0);
|
||||
|
||||
for (const name of shared) {
|
||||
expect(
|
||||
docker.get(name),
|
||||
`Dockerfile pins ${name} to ${docker.get(name)} while ` +
|
||||
`pnpm-workspace.yaml pins it to ${workspace.get(name)}. The image ` +
|
||||
`carries both installs, so the looser of the two is what ships.`,
|
||||
).toBe(workspace.get(name));
|
||||
}
|
||||
});
|
||||
|
||||
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.
|
||||
const range = dockerfileOverrides().get("deepmerge-ts");
|
||||
expect(range).toBeDefined();
|
||||
expect(range).toMatch(/\^?8\./);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user