From c5dded899255615ef61ae2a24e3a8e9f68c9da25 Mon Sep 17 00:00:00 2001 From: Muhammad Tamir Date: Thu, 7 May 2026 10:33:11 +0700 Subject: [PATCH] fix(mitm): prevent stub from loading at runtime via bypass module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turbopack resolveAlias (@/mitm/manager → manager.stub.ts) was designed for build-time safety but Next.js applies aliases to ALL imports — including dynamic ones. This caused await import("@/mitm/manager") at runtime to load the stub, which silently returned fake {running: true} without spawning the MITM proxy. The UI showed "MITM proxy started" but nothing was actually running. Fix introduces a two-path design: - @/mitm/manager → stub (build-time, safe for Turbopack) - @/mitm/manager.runtime → real manager (runtime, bypasses alias) Route handlers now dynamic-import from manager.runtime, which re-exports from ./manager and does NOT match the alias pattern. Additional fixes: - Make stub throw explicit errors at runtime so misconfiguration is immediately visible instead of silently faking success - Add server.cjs to outputFileTracingIncludes (NFT trace) and Dockerfile COPY so the MITM server binary exists in standalone/Docker output --- Dockerfile | 2 ++ next.config.mjs | 1 + .../api/cli-tools/antigravity-mitm/route.ts | 8 +++--- src/app/api/settings/mitm/route.ts | 6 ++--- src/mitm/manager.runtime.ts | 5 ++++ src/mitm/manager.stub.ts | 27 ++++++++++--------- 6 files changed, 31 insertions(+), 18 deletions(-) create mode 100644 src/mitm/manager.runtime.ts diff --git a/Dockerfile b/Dockerfile index 8ca152fb1c..aa217ce78a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -50,6 +50,8 @@ COPY --from=builder /app/node_modules/split2 ./node_modules/split2 # traced by Next.js standalone output — copy them explicitly. COPY --from=builder /app/src/lib/db/migrations ./migrations ENV OMNIROUTE_MIGRATIONS_DIR=/app/migrations +# MITM server.cjs is spawned at runtime via child_process — not traced by nft +COPY --from=builder /app/src/mitm/server.cjs ./src/mitm/server.cjs COPY --from=builder /app/scripts/run-standalone.mjs ./run-standalone.mjs COPY --from=builder /app/scripts/runtime-env.mjs ./runtime-env.mjs diff --git a/next.config.mjs b/next.config.mjs index eab10cb8e8..c0d33ed51d 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -83,6 +83,7 @@ const nextConfig = { // runtime and are NOT always auto-traced by webpack/turbopack. "/*": [ "./src/lib/db/migrations/**/*", + "./src/mitm/server.cjs", "./open-sse/services/compression/engines/rtk/filters/**/*.json", "./open-sse/services/compression/rules/**/*.json", ], diff --git a/src/app/api/cli-tools/antigravity-mitm/route.ts b/src/app/api/cli-tools/antigravity-mitm/route.ts index 283bc65de4..901c7ea919 100644 --- a/src/app/api/cli-tools/antigravity-mitm/route.ts +++ b/src/app/api/cli-tools/antigravity-mitm/route.ts @@ -15,7 +15,7 @@ export async function GET(request) { if (authError) return authError; try { - const { getMitmStatus, getCachedPassword } = await import("@/mitm/manager"); + const { getMitmStatus, getCachedPassword } = await import("@/mitm/manager.runtime"); const status = await getMitmStatus(); return NextResponse.json({ running: status.running, @@ -59,7 +59,8 @@ export async function POST(request) { // (#523) Extract keyId BEFORE validation — Zod strips unknown fields! const apiKeyId = typeof rawBody?.keyId === "string" ? rawBody.keyId.trim() : null; const apiKey = await resolveApiKey(apiKeyId, rawApiKey); - const { startMitm, getCachedPassword, setCachedPassword } = await import("@/mitm/manager"); + const { startMitm, getCachedPassword, setCachedPassword } = + await import("@/mitm/manager.runtime"); const isWin = process.platform === "win32"; const isRootUser = !isWin && isRoot(); const pwd = sudoPassword || getCachedPassword() || ""; @@ -114,7 +115,8 @@ export async function DELETE(request) { return NextResponse.json({ error: validation.error }, { status: 400 }); } const { sudoPassword } = validation.data; - const { stopMitm, getCachedPassword, setCachedPassword } = await import("@/mitm/manager"); + const { stopMitm, getCachedPassword, setCachedPassword } = + await import("@/mitm/manager.runtime"); const isWin = process.platform === "win32"; const isRootUser = !isWin && isRoot(); const pwd = sudoPassword || getCachedPassword() || ""; diff --git a/src/app/api/settings/mitm/route.ts b/src/app/api/settings/mitm/route.ts index dd56242e5b..88c1ee4099 100644 --- a/src/app/api/settings/mitm/route.ts +++ b/src/app/api/settings/mitm/route.ts @@ -137,7 +137,7 @@ function readStats(): MitmStats { } async function buildMitmResponse() { - const { getMitmStatus, getCachedPassword } = await import("@/mitm/manager"); + const { getMitmStatus, getCachedPassword } = await import("@/mitm/manager.runtime"); const status = await getMitmStatus(); const config = readConfig(); const stats = readStats(); @@ -204,7 +204,7 @@ export async function PUT(request: Request) { if (typeof parsed.data.enabled === "boolean") { const { getCachedPassword, setCachedPassword, startMitm, stopMitm } = - await import("@/mitm/manager"); + await import("@/mitm/manager.runtime"); const { isRoot } = await import("@/mitm/systemCommands"); const isWin = process.platform === "win32"; const isRootUser = !isWin && isRoot(); @@ -247,7 +247,7 @@ export async function POST(request: Request) { return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 }); } - const { getMitmStatus } = await import("@/mitm/manager"); + const { getMitmStatus } = await import("@/mitm/manager.runtime"); const status = await getMitmStatus(); if (status.running) { return NextResponse.json( diff --git a/src/mitm/manager.runtime.ts b/src/mitm/manager.runtime.ts new file mode 100644 index 0000000000..6d71ee20d0 --- /dev/null +++ b/src/mitm/manager.runtime.ts @@ -0,0 +1,5 @@ +// Runtime bypass for Turbopack resolveAlias. +// Turbopack maps @/mitm/manager → manager.stub.ts so the build doesn't choke +// on native module imports. Dynamic import() of @/mitm/manager.runtime does NOT +// match that alias and loads the real manager at runtime. +export * from "./manager"; diff --git a/src/mitm/manager.stub.ts b/src/mitm/manager.stub.ts index 35552d0e9c..d82f24fa4a 100644 --- a/src/mitm/manager.stub.ts +++ b/src/mitm/manager.stub.ts @@ -1,22 +1,25 @@ // Build-time stub for @/mitm/manager // Used by Turbopack during next build to avoid native module resolution errors. -// The real module is used at runtime via dynamic import in route handlers. +// Dynamic import() in route handlers should load the REAL manager at runtime. +// If this stub is reached at runtime, the build alias is incorrectly applied. + +const STUB_ERROR = + "MITM manager stub reached at runtime — build alias applied incorrectly. " + + "Use --webpack for production builds or verify Turbopack is not aliasing at runtime."; export const getCachedPassword = () => null; export const setCachedPassword = (_pwd: string) => {}; export const clearCachedPassword = () => {}; -export const getMitmStatus = async () => ({ - running: false, - pid: null, - dnsConfigured: false, - certExists: false, -}); +export const getMitmStatus = async () => { + throw new Error(STUB_ERROR); +}; export const startMitm = async ( _apiKey: string, _sudoPassword: string, _options: { port?: number } = {} -) => ({ - running: false, - pid: null, -}); -export const stopMitm = async (_sudoPassword: string) => ({ running: false, pid: null }); +): Promise => { + throw new Error(STUB_ERROR); +}; +export const stopMitm = async (_sudoPassword: string): Promise => { + throw new Error(STUB_ERROR); +};