From 51efc71af5df8be0ce7299d9febb011ee1e12093 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Wed, 5 Aug 2026 16:47:02 -0300 Subject: [PATCH] fix(docker): ship MITM _internal/ shims and selfsigned package in standalone bundle (#9451) Closes #9451 --- .../fixes/9451-selfsigned-docker-dep.md | 1 + scripts/build/assembleStandalone.mjs | 19 +++++ .../build/mitm-server-bundle-contents.test.ts | 79 +++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 changelog.d/fixes/9451-selfsigned-docker-dep.md create mode 100644 tests/unit/build/mitm-server-bundle-contents.test.ts diff --git a/changelog.d/fixes/9451-selfsigned-docker-dep.md b/changelog.d/fixes/9451-selfsigned-docker-dep.md new file mode 100644 index 0000000000..a2f525665d --- /dev/null +++ b/changelog.d/fixes/9451-selfsigned-docker-dep.md @@ -0,0 +1 @@ +- fix(docker): ship MITM `_internal/` shims and `selfsigned` package in standalone bundle (#9451) diff --git a/scripts/build/assembleStandalone.mjs b/scripts/build/assembleStandalone.mjs index 27faa6c5cf..7bda5fa527 100644 --- a/scripts/build/assembleStandalone.mjs +++ b/scripts/build/assembleStandalone.mjs @@ -116,6 +116,25 @@ const EXTRA_MODULE_ENTRIES = [ { label: "split2", src: ["node_modules", "split2"], dest: ["node_modules", "split2"] }, { label: "migrations", src: ["src", "lib", "db", "migrations"], dest: ["migrations"] }, { label: "MITM server", src: ["src", "mitm", "server.cjs"], dest: ["src", "mitm", "server.cjs"] }, + { + // #9451: server.cjs requires 6 shims from ./_internal/ (bypass, ingest, + // forwardTarget, aliasConfig, standaloneRouting, rootCaShim) which the MITM + // child process loads via require(). Next.js's standalone tracer never sees + // them (server.cjs is a separate node process, not imported by the main + // server), so the _internal/ directory must be copied explicitly or the MITM + // child crashes with MODULE_NOT_FOUND at boot. + label: "MITM _internal shims (#9451)", + src: ["src", "mitm", "_internal"], + dest: ["src", "mitm", "_internal"], + }, + { + // #9451: rootCaShim.cjs does `await import("selfsigned")` for dynamic SSL + // certificate generation. The MITM child is not traced by Next.js, so the + // package is absent from the Docker standalone bundle without this entry. + label: "selfsigned (MITM rootCaShim dynamic import — #9451)", + src: ["node_modules", "selfsigned"], + dest: ["node_modules", "selfsigned"], + }, { label: "run-standalone script", src: ["scripts", "dev", "run-standalone.mjs"], diff --git a/tests/unit/build/mitm-server-bundle-contents.test.ts b/tests/unit/build/mitm-server-bundle-contents.test.ts new file mode 100644 index 0000000000..05ee7fa7e2 --- /dev/null +++ b/tests/unit/build/mitm-server-bundle-contents.test.ts @@ -0,0 +1,79 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { syncStandaloneExtraModules } from "../../../scripts/build/assembleStandalone.mjs"; + +const repoRoot = path.resolve(new URL(".", import.meta.url).pathname, "../../.."); + +/** + * Regression guard for #9451: the MITM `server.cjs` runs as a separate `node` + * child process in the Docker standalone bundle, so neither Next.js's + * file tracer nor the main server's import graph covers its dependencies. + * `EXTRA_MODULE_ENTRIES` must therefore ship every relative `require()` target + * of `server.cjs` AND every bare-specifier dynamic `import()` its `_internal/*.cjs` + * shims perform, or the MITM proxy crashes at boot with MODULE_NOT_FOUND. + */ + +test("EXTRA_MODULE_ENTRIES ships every relative require() of MITM server.cjs (#9451)", async () => { + const serverSrc = fs.readFileSync(path.join(repoRoot, "src/mitm/server.cjs"), "utf8"); + const relRequires = [...serverSrc.matchAll(/require\("\.\/([^"]+)"\)/g)].map((m) => m[1]); + assert.ok( + relRequires.length > 0, + "server.cjs has relative require() calls to check (sanity)" + ); + + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "mitm-bundle-")); + try { + await syncStandaloneExtraModules(repoRoot, fs.promises, { log() {} }, tmp); + for (const rel of relRequires) { + assert.ok( + fs.existsSync(path.join(tmp, "src/mitm", rel)), + `server.cjs requires ./src/mitm/${rel} but EXTRA_MODULE_ENTRIES does not ship it — MITM child crashes with MODULE_NOT_FOUND` + ); + } + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } +}); + +test("EXTRA_MODULE_ENTRIES ships every dynamic import() of MITM _internal shims (#9451)", async () => { + const internalDir = path.join(repoRoot, "src/mitm/_internal"); + const shimFiles = fs + .readdirSync(internalDir) + .filter((f) => f.endsWith(".cjs")); + assert.ok(shimFiles.length > 0, "src/mitm/_internal has shim files to check (sanity)"); + + // Collect bare-specifier (non-relative, non-node:) dynamic imports across all shims. + const bareImports = new Set(); + for (const f of shimFiles) { + const src = fs.readFileSync(path.join(internalDir, f), "utf8"); + for (const m of src.matchAll(/import\("([^"]+)"\)/g)) { + const spec = m[1]; + if (spec.startsWith("node:") || spec.startsWith(".") || spec.startsWith("/")) continue; + bareImports.add(spec); + } + } + assert.ok( + bareImports.size > 0, + "MITM _internal shims have bare-specifier dynamic import() calls to check (sanity)" + ); + + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "mitm-bundle-imports-")); + try { + await syncStandaloneExtraModules(repoRoot, fs.promises, { log() {} }, tmp); + for (const spec of bareImports) { + // Bare specifiers resolve into node_modules/; scoped packages live + // under node_modules/@scope/. For selfsigned (no nested subpath used at + // link time) it suffices to check the package directory is shipped. + const pkgDir = path.join(tmp, "node_modules", ...spec.split("/")); + assert.ok( + fs.existsSync(pkgDir), + `MITM _internal shim dynamic-imports "${spec}" but EXTRA_MODULE_ENTRIES does not ship it — MITM child crashes with MODULE_NOT_FOUND` + ); + } + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } +});