File size: 2,236 Bytes
cd8bd0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
 * Best-effort build of the TPROXY IP_TRANSPARENT native addon so the production
 * build can copy `build/Release/transparent.node` into the standalone bundle
 * (assembleStandalone's NATIVE_ASSET_ENTRIES). Called from build-next-isolated.mjs
 * before the standalone is assembled.
 *
 * IP_TRANSPARENT is Linux-only, so this is a no-op everywhere else. A missing C
 * toolchain is NOT fatal — the TPROXY capture mode degrades gracefully when the
 * addon is absent (transparentSocket.ts returns "unavailable"). Every effectful
 * seam (platform/run/exists) is injectable so the decision logic is unit-testable.
 *
 * Hard Rule #13: the command + args are a fixed allowlist (no interpolation of
 * external/runtime values); `cwd` is derived from `projectRoot`, never user input.
 */
import { execFileSync } from "node:child_process";
import { existsSync } from "node:fs";
import path from "node:path";

/**
 * @param {string} projectRoot
 * @param {{ platform?: string, run?: (cmd:string, args:string[], cwd:string) => void,
 *           exists?: (p:string) => boolean }} [opts]
 * @returns {{ built: boolean, reason?: string }}
 */
export function buildTproxyNative(projectRoot, opts = {}) {
  const platform = opts.platform ?? process.platform;
  const run = opts.run ?? defaultRun;
  const exists = opts.exists ?? existsSync;

  if (platform !== "linux") {
    return { built: false, reason: "non-linux host (IP_TRANSPARENT is Linux-only)" };
  }

  const nativeDir = path.join(projectRoot, "src", "mitm", "tproxy", "native");
  if (!exists(path.join(nativeDir, "binding.gyp"))) {
    return { built: false, reason: "native sources absent (binding.gyp not found)" };
  }

  const out = path.join(nativeDir, "build", "Release", "transparent.node");
  try {
    run("npx", ["--yes", "node-gyp", "rebuild"], nativeDir);
  } catch (err) {
    return { built: false, reason: `toolchain/build failed: ${err?.message ?? String(err)}` };
  }
  if (!exists(out)) {
    return { built: false, reason: "node-gyp produced no transparent.node" };
  }
  return { built: true };
}

/** @type {(cmd: string, args: string[], cwd: string) => void} */
function defaultRun(cmd, args, cwd) {
  execFileSync(cmd, args, { cwd, stdio: "inherit" });
}