mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
fix(mitm): prevent stub from loading at runtime via bypass module
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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
|
||||
@@ -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() || "";
|
||||
|
||||
@@ -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(
|
||||
|
||||
5
src/mitm/manager.runtime.ts
Normal file
5
src/mitm/manager.runtime.ts
Normal file
@@ -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";
|
||||
@@ -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<never> => {
|
||||
throw new Error(STUB_ERROR);
|
||||
};
|
||||
export const stopMitm = async (_sudoPassword: string): Promise<never> => {
|
||||
throw new Error(STUB_ERROR);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user