| const https = require("https");
|
| const net = require("net");
|
| const fs = require("fs");
|
| const path = require("path");
|
| const dns = require("dns");
|
| const { promisify } = require("util");
|
| const os = require("os");
|
|
|
|
|
|
|
| function getDataDir() {
|
| if (process.env.DATA_DIR) return path.resolve(process.env.DATA_DIR.trim());
|
| return path.join(os.homedir(), ".omniroute");
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
| const TARGET_HOSTS = new Set([
|
| "daily-cloudcode-pa.sandbox.googleapis.com",
|
| "daily-cloudcode-pa.googleapis.com",
|
| "cloudcode-pa.googleapis.com",
|
| "autopush-cloudcode-pa.sandbox.googleapis.com",
|
| ]);
|
|
|
|
|
| const TARGET_HOST_AGENT = new Map();
|
| for (const h of TARGET_HOSTS) TARGET_HOST_AGENT.set(h, "antigravity");
|
|
|
| const parsedLocalPort = Number.parseInt(process.env.MITM_LOCAL_PORT || "443", 10);
|
| const LOCAL_PORT =
|
| Number.isInteger(parsedLocalPort) && parsedLocalPort > 0 && parsedLocalPort <= 65535
|
| ? parsedLocalPort
|
| : 443;
|
| const ROUTER_BASE_URL = (
|
| process.env.OMNIROUTE_BASE_URL ||
|
| process.env.BASE_URL ||
|
| "http://localhost:20128"
|
| )
|
| .trim()
|
| .replace(/\/+$/, "");
|
| const ROUTER_URL = `${ROUTER_BASE_URL}/v1/chat/completions`;
|
| const API_KEY = process.env.ROUTER_API_KEY;
|
| const DATA_DIR = getDataDir();
|
| const DB_FILE = path.join(DATA_DIR, "db.json");
|
| const SQLITE_FILE = path.join(DATA_DIR, "storage.sqlite");
|
|
|
|
|
|
|
|
|
|
|
| const TARGETS_JSON_FILE = path.join(DATA_DIR, "mitm", "targets.json");
|
| function loadDynamicTargets() {
|
| try {
|
| if (!fs.existsSync(TARGETS_JSON_FILE)) return 0;
|
| const raw = fs.readFileSync(TARGETS_JSON_FILE, "utf-8");
|
| const parsed = JSON.parse(raw);
|
| if (!parsed || !Array.isArray(parsed.targets)) return 0;
|
| let added = 0;
|
| for (const t of parsed.targets) {
|
| if (!t || typeof t !== "object") continue;
|
| const id = typeof t.id === "string" ? t.id : "unknown";
|
| const hosts = Array.isArray(t.hosts) ? t.hosts : [];
|
| for (const host of hosts) {
|
| if (typeof host !== "string" || !host) continue;
|
| const lower = host.toLowerCase();
|
| if (!TARGET_HOSTS.has(lower)) {
|
| TARGET_HOSTS.add(lower);
|
| TARGET_HOST_AGENT.set(lower, id);
|
| added++;
|
| }
|
| }
|
| }
|
| return added;
|
| } catch (err) {
|
| console.error(`[MITM] Failed to load targets.json: ${err.message}`);
|
| return 0;
|
| }
|
| }
|
|
|
| const _dynamicAdded = loadDynamicTargets();
|
| if (_dynamicAdded > 0) {
|
| console.log(`[MITM] Loaded ${_dynamicAdded} additional host(s) from targets.json`);
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| const SANITIZE_MAX_LEN = 4096;
|
| const SANITIZE_SOURCE_EXT = ["ts", "tsx", "js", "jsx", "mjs", "cjs"];
|
| function looksLikeAbsolutePath(tok) {
|
| if (tok.length < 4 || tok.length > 2048) return false;
|
| const isPosix = tok.charCodeAt(0) === 0x2f;
|
| const isWindows =
|
| tok.length > 2 && tok.charCodeAt(1) === 0x3a && /[A-Za-z]/.test(tok[0]);
|
| if (!isPosix && !isWindows) return false;
|
| const dot = tok.lastIndexOf(".");
|
| if (dot <= 0 || dot === tok.length - 1) return false;
|
| const ext = tok.slice(dot + 1).split(":", 1)[0].toLowerCase();
|
| return SANITIZE_SOURCE_EXT.includes(ext);
|
| }
|
| function sanitizeErrorMessage(message) {
|
| let str =
|
| typeof message === "string" ? message : String(message == null ? "" : message);
|
| if (str.length > SANITIZE_MAX_LEN) str = str.slice(0, SANITIZE_MAX_LEN);
|
| const nl = str.indexOf("\n");
|
| const firstLine = nl >= 0 ? str.slice(0, nl) : str;
|
| const parts = firstLine.split(/(\s+)/);
|
| for (let i = 0; i < parts.length; i++) {
|
| if (looksLikeAbsolutePath(parts[i])) parts[i] = "<path>";
|
| }
|
| return parts.join("");
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| const bypassShim = require("./_internal/bypass.cjs");
|
|
|
| const BYPASS_JSON_FILE = path.join(DATA_DIR, "mitm", "bypass.json");
|
| let _userBypassPatterns = [];
|
|
|
| function loadUserBypassPatterns() {
|
| try {
|
| if (!fs.existsSync(BYPASS_JSON_FILE)) {
|
| _userBypassPatterns = [];
|
| return 0;
|
| }
|
| const raw = fs.readFileSync(BYPASS_JSON_FILE, "utf-8");
|
| _userBypassPatterns = bypassShim.parseBypassJson(raw);
|
| return _userBypassPatterns.length;
|
| } catch (err) {
|
| console.error(`[MITM] Failed to load bypass.json: ${err.message}`);
|
| _userBypassPatterns = [];
|
| return 0;
|
| }
|
| }
|
|
|
| function routeBypass(hostname) {
|
| return bypassShim.routeBypass(hostname, TARGET_HOSTS, _userBypassPatterns);
|
| }
|
|
|
| const _bypassLoaded = loadUserBypassPatterns();
|
| if (_bypassLoaded > 0) {
|
| console.log(
|
| `[MITM] Loaded ${_bypassLoaded} user bypass pattern(s) from bypass.json`
|
| );
|
| }
|
|
|
| let _sqliteDb = null;
|
|
|
|
|
| const ENABLE_FILE_LOG = false;
|
|
|
| if (!API_KEY) {
|
| console.error("β ROUTER_API_KEY required");
|
| process.exit(1);
|
| }
|
|
|
|
|
| const certDir = path.join(DATA_DIR, "mitm");
|
| const STATS_FILE = path.join(certDir, "stats.json");
|
| const stats = {
|
| startedAt: null,
|
| totalRequests: 0,
|
| interceptedRequests: 0,
|
| activeConnections: 0,
|
| lastRequestAt: null,
|
| lastInterceptAt: null,
|
| };
|
|
|
| function writeStats() {
|
| try {
|
| fs.writeFileSync(STATS_FILE, JSON.stringify(stats, null, 2));
|
| } catch {
|
|
|
| }
|
| }
|
|
|
| const sslOptions = {
|
| key: fs.readFileSync(path.join(certDir, "server.key")),
|
| cert: fs.readFileSync(path.join(certDir, "server.crt")),
|
| };
|
|
|
|
|
| const CHAT_URL_PATTERNS = [":generateContent", ":streamGenerateContent"];
|
|
|
|
|
| const LOG_DIR = path.join(__dirname, "../../logs/mitm");
|
| if (ENABLE_FILE_LOG && !fs.existsSync(LOG_DIR)) fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
|
|
|
| function safeLogPath(name) {
|
| const safe = name.replace(/[^a-zA-Z0-9_\-]/g, "_").substring(0, 80);
|
| const resolved = path.resolve(LOG_DIR, safe);
|
| if (!resolved.startsWith(path.resolve(LOG_DIR) + path.sep)) {
|
| throw new Error("Path traversal attempt detected in log filename");
|
| }
|
| return resolved;
|
| }
|
|
|
| function saveRequestLog(url, bodyBuffer) {
|
| if (!ENABLE_FILE_LOG) return;
|
| try {
|
| const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
| const urlSlug = url.replace(/[^a-zA-Z0-9]/g, "_").substring(0, 60);
|
| const filePath = safeLogPath(`${ts}_${urlSlug}.json`);
|
| const body = JSON.parse(bodyBuffer.toString());
|
| fs.writeFileSync(filePath, JSON.stringify(body, null, 2));
|
| console.log(`πΎ Saved request: ${filePath}`);
|
| } catch {
|
|
|
| }
|
| }
|
|
|
| function saveResponseLog(url, data) {
|
| if (!ENABLE_FILE_LOG) return;
|
| try {
|
| const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
| const urlSlug = url.replace(/[^a-zA-Z0-9]/g, "_").substring(0, 60);
|
| const filePath = safeLogPath(`${ts}_${urlSlug}_response.txt`);
|
| fs.writeFileSync(filePath, data);
|
| console.log(`πΎ Saved response: ${filePath}`);
|
| } catch {
|
|
|
| }
|
| }
|
|
|
|
|
| const cachedTargetIPs = new Map();
|
| function getTargetHost(req) {
|
| const host = String(req.headers.host || "")
|
| .split(":")[0]
|
| .toLowerCase();
|
| return TARGET_HOSTS.has(host) ? host : "daily-cloudcode-pa.sandbox.googleapis.com";
|
| }
|
|
|
| async function resolveTargetIP(targetHost) {
|
| if (cachedTargetIPs.has(targetHost)) return cachedTargetIPs.get(targetHost);
|
| const resolver = new dns.Resolver();
|
| resolver.setServers(["8.8.8.8"]);
|
| const resolve4 = promisify(resolver.resolve4.bind(resolver));
|
| const addresses = await resolve4(targetHost);
|
| const targetIP = addresses[0];
|
| cachedTargetIPs.set(targetHost, targetIP);
|
| return targetIP;
|
| }
|
|
|
| function collectBodyRaw(req) {
|
| return new Promise((resolve, reject) => {
|
| const chunks = [];
|
| req.on("data", (chunk) => chunks.push(chunk));
|
| req.on("end", () => resolve(Buffer.concat(chunks)));
|
| req.on("error", reject);
|
| });
|
| }
|
|
|
| function extractModel(body) {
|
| try {
|
| return JSON.parse(body.toString()).model || null;
|
| } catch {
|
| return null;
|
| }
|
| }
|
|
|
| |
| |
| |
|
|
| function getSqliteDb() {
|
| if (_sqliteDb) return _sqliteDb;
|
| try {
|
| const Database = require("better-sqlite3");
|
| if (fs.existsSync(SQLITE_FILE)) {
|
| _sqliteDb = new Database(SQLITE_FILE, { readonly: true });
|
| return _sqliteDb;
|
| }
|
| } catch {
|
|
|
| }
|
| return null;
|
| }
|
|
|
| function getMappedModel(model) {
|
| if (!model) return null;
|
|
|
|
|
| try {
|
| const db = getSqliteDb();
|
| if (db) {
|
| const row = db
|
| .prepare(
|
| "SELECT value FROM key_value WHERE namespace = 'mitmAlias' AND key = 'antigravity'"
|
| )
|
| .get();
|
| if (row) {
|
| const mappings = JSON.parse(row.value);
|
| return mappings[model] || null;
|
| }
|
| }
|
| } catch {
|
|
|
| }
|
|
|
|
|
| try {
|
| if (fs.existsSync(DB_FILE)) {
|
| const db = JSON.parse(fs.readFileSync(DB_FILE, "utf-8"));
|
| return db.mitmAlias?.antigravity?.[model] || null;
|
| }
|
| } catch {
|
|
|
| }
|
|
|
| return null;
|
| }
|
|
|
| async function passthrough(req, res, bodyBuffer) {
|
| const targetHost = getTargetHost(req);
|
| const targetIP = await resolveTargetIP(targetHost);
|
|
|
|
|
|
|
| const rejectUnauthorized = process.env.MITM_DISABLE_TLS_VERIFY !== "1";
|
|
|
| const forwardReq = https.request(
|
| {
|
| hostname: targetIP,
|
| port: 443,
|
| path: req.url,
|
| method: req.method,
|
| headers: { ...req.headers, host: targetHost },
|
| servername: targetHost,
|
| rejectUnauthorized,
|
| },
|
| (forwardRes) => {
|
| res.writeHead(forwardRes.statusCode, forwardRes.headers);
|
| forwardRes.pipe(res);
|
| }
|
| );
|
|
|
| forwardReq.on("error", (err) => {
|
| console.error(`β Passthrough error: ${err.message}`);
|
| if (!res.headersSent) res.writeHead(502);
|
| res.end("Bad Gateway");
|
| });
|
|
|
| if (bodyBuffer.length > 0) forwardReq.write(bodyBuffer);
|
| forwardReq.end();
|
| }
|
|
|
| async function intercept(req, res, bodyBuffer, mappedModel) {
|
| try {
|
| const body = JSON.parse(bodyBuffer.toString());
|
| body.model = mappedModel;
|
|
|
|
|
|
|
|
|
|
|
|
|
| const reqHost = String(req.headers.host || "").split(":")[0].toLowerCase();
|
| const agentId = TARGET_HOST_AGENT.get(reqHost) || "unknown";
|
|
|
| const response = await fetch(ROUTER_URL, {
|
| method: "POST",
|
| headers: {
|
| "Content-Type": "application/json",
|
| Authorization: `Bearer ${API_KEY}`,
|
| "x-omniroute-source": "agent-bridge",
|
| "x-omniroute-agent": agentId,
|
| },
|
| body: JSON.stringify(body),
|
| });
|
|
|
| if (!response.ok) {
|
| const errText = await response.text().catch(() => "");
|
| throw new Error(`OmniRoute ${response.status}: ${errText}`);
|
| }
|
|
|
| res.writeHead(200, {
|
| "Content-Type": "text/event-stream",
|
| "Cache-Control": "no-cache",
|
| Connection: "keep-alive",
|
| "X-Accel-Buffering": "no",
|
| });
|
|
|
| const reader = response.body.getReader();
|
| const decoder = new TextDecoder();
|
|
|
| while (true) {
|
| const { done, value } = await reader.read();
|
| if (done) {
|
| res.end();
|
| break;
|
| }
|
| res.write(decoder.decode(value, { stream: true }));
|
| }
|
| } catch (error) {
|
|
|
|
|
| console.error(`β ${error.message}`);
|
| if (!res.headersSent) res.writeHead(500, { "Content-Type": "application/json" });
|
| res.end(
|
| JSON.stringify({
|
| error: {
|
| message: sanitizeErrorMessage(error && error.message),
|
| type: "mitm_error",
|
| },
|
| })
|
| );
|
| }
|
| }
|
|
|
| const server = https.createServer(sslOptions, async (req, res) => {
|
| stats.totalRequests++;
|
| stats.lastRequestAt = new Date().toISOString();
|
| writeStats();
|
|
|
| const bodyBuffer = await collectBodyRaw(req);
|
| const host = String(req.headers.host || "").split(":")[0].toLowerCase();
|
| const model = bodyBuffer.length > 0 ? extractModel(bodyBuffer) : null;
|
|
|
| console.log(`[MITM] ${req.method} ${host}${req.url} | body: ${bodyBuffer.length}B | model: ${model || "N/A"}`);
|
|
|
| if (bodyBuffer.length > 0) saveRequestLog(req.url, bodyBuffer);
|
|
|
| if (req.headers["x-omniroute-source"] === "omniroute") {
|
| console.log(`[MITM] β PASSTHROUGH (OmniRoute source loop)`);
|
| return passthrough(req, res, bodyBuffer);
|
| }
|
|
|
| if (!TARGET_HOSTS.has(host)) {
|
| console.log(`[MITM] β PASSTHROUGH (host ${host} not in target list)`);
|
| return passthrough(req, res, bodyBuffer);
|
| }
|
|
|
| const isChatRequest = CHAT_URL_PATTERNS.some((p) => req.url.includes(p));
|
|
|
| if (!isChatRequest) {
|
| console.log(`[MITM] β PASSTHROUGH (URL ${req.url} does not match chat patterns)`);
|
| return passthrough(req, res, bodyBuffer);
|
| }
|
|
|
| const mappedModel = getMappedModel(model);
|
|
|
| if (!mappedModel) {
|
| console.log(`[MITM] β PASSTHROUGH (model "${model}" has no MITM alias mapping)`);
|
| return passthrough(req, res, bodyBuffer);
|
| }
|
|
|
| stats.interceptedRequests++;
|
| stats.lastInterceptAt = new Date().toISOString();
|
| writeStats();
|
|
|
| console.log(`[MITM] INTERCEPTED ${model} β ${mappedModel}`);
|
| return intercept(req, res, bodyBuffer, mappedModel);
|
| });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| function parseConnectAuthority(authority) {
|
|
|
| const idx = authority.lastIndexOf(":");
|
| if (idx === -1) return { host: authority.toLowerCase(), port: 443 };
|
| const host = authority.slice(0, idx).toLowerCase();
|
| const port = Number.parseInt(authority.slice(idx + 1), 10);
|
| return {
|
| host,
|
| port: Number.isInteger(port) && port > 0 && port <= 65535 ? port : 443,
|
| };
|
| }
|
|
|
| function rawTcpForward(clientSocket, head, host, port, label) {
|
| const targetSocket = net.connect(port, host, () => {
|
| clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
| if (head && head.length > 0) targetSocket.write(head);
|
| targetSocket.pipe(clientSocket);
|
| clientSocket.pipe(targetSocket);
|
| });
|
|
|
|
|
| const onErr = (label2) => (err) => {
|
| console.error(`[MITM] ${label} TCP forward ${label2} error: ${err.message}`);
|
| try {
|
| clientSocket.destroy();
|
| } catch {
|
|
|
| }
|
| try {
|
| targetSocket.destroy();
|
| } catch {
|
|
|
| }
|
| };
|
| targetSocket.on("error", onErr("upstream"));
|
| clientSocket.on("error", onErr("client"));
|
| clientSocket.on("close", () => {
|
| try {
|
| targetSocket.destroy();
|
| } catch {
|
|
|
| }
|
| });
|
| targetSocket.on("close", () => {
|
| try {
|
| clientSocket.destroy();
|
| } catch {
|
|
|
| }
|
| });
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| server.on("connect", (req, clientSocket, head) => {
|
| const authority = String(req.url || "");
|
| const { host: connectHost, port: connectPort } = parseConnectAuthority(authority);
|
|
|
| const decision = routeBypass(connectHost);
|
|
|
| if (decision === "bypass") {
|
|
|
|
|
| console.log(`[MITM] CONNECT ${connectHost}:${connectPort} β BYPASS (TCP tunnel)`);
|
| rawTcpForward(clientSocket, head, connectHost, connectPort, "bypass");
|
| return;
|
| }
|
|
|
| if (decision === "target") {
|
|
|
|
|
|
|
|
|
| console.log(
|
| `[MITM] CONNECT ${connectHost}:${connectPort} β TARGET (TLS terminate locally)`
|
| );
|
| clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
| if (head && head.length > 0) clientSocket.unshift(head);
|
| server.emit("connection", clientSocket);
|
| return;
|
| }
|
|
|
|
|
| console.log(
|
| `[MITM] CONNECT ${connectHost}:${connectPort} β PASSTHROUGH (TCP tunnel)`
|
| );
|
| rawTcpForward(clientSocket, head, connectHost, connectPort, "passthrough");
|
| });
|
|
|
| server.listen(LOCAL_PORT, () => {
|
| stats.startedAt = new Date().toISOString();
|
| writeStats();
|
| console.log(`π MITM ready on :${LOCAL_PORT} β ${ROUTER_URL}`);
|
| });
|
|
|
| server.on("connection", (socket) => {
|
|
|
|
|
| if (socket.__mitmCounted) return;
|
| socket.__mitmCounted = true;
|
| stats.activeConnections++;
|
| writeStats();
|
| socket.on("close", () => {
|
| stats.activeConnections = Math.max(0, stats.activeConnections - 1);
|
| writeStats();
|
| });
|
| });
|
|
|
| server.on("error", (error) => {
|
| if (error.code === "EADDRINUSE") {
|
| console.error(`β Port ${LOCAL_PORT} already in use`);
|
| } else if (error.code === "EACCES") {
|
| console.error(`β Permission denied for port ${LOCAL_PORT}`);
|
| } else {
|
| console.error(`β ${error.message}`);
|
| }
|
| process.exit(1);
|
| });
|
|
|
| process.on("SIGTERM", () => {
|
| server.close(() => process.exit(0));
|
| });
|
| process.on("SIGINT", () => {
|
| server.close(() => process.exit(0));
|
| });
|
|
|