| const https = require("https"); |
| 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_HOST = "daily-cloudcode-pa.googleapis.com"; |
| const LOCAL_PORT = 443; |
| const ROUTER_URL = "http://localhost:20128/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"); |
|
|
| 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 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 { |
| |
| } |
| } |
|
|
| |
| let cachedTargetIP = null; |
| async function resolveTargetIP() { |
| if (cachedTargetIP) return cachedTargetIP; |
| const resolver = new dns.Resolver(); |
| resolver.setServers(["8.8.8.8"]); |
| const resolve4 = promisify(resolver.resolve4.bind(resolver)); |
| const addresses = await resolve4(TARGET_HOST); |
| cachedTargetIP = addresses[0]; |
| return cachedTargetIP; |
| } |
|
|
| 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 targetIP = await resolveTargetIP(); |
|
|
| |
| |
| 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: TARGET_HOST }, |
| servername: TARGET_HOST, |
| 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 response = await fetch(ROUTER_URL, { |
| method: "POST", |
| headers: { |
| "Content-Type": "application/json", |
| Authorization: `Bearer ${API_KEY}`, |
| }, |
| 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: error.message, type: "mitm_error" } })); |
| } |
| } |
|
|
| const server = https.createServer(sslOptions, async (req, res) => { |
| const bodyBuffer = await collectBodyRaw(req); |
|
|
| |
| if (bodyBuffer.length > 0) saveRequestLog(req.url, bodyBuffer); |
|
|
| |
| if (req.headers["x-omniroute-source"] === "omniroute") { |
| return passthrough(req, res, bodyBuffer); |
| } |
|
|
| const isChatRequest = CHAT_URL_PATTERNS.some((p) => req.url.includes(p)); |
|
|
| if (!isChatRequest) { |
| return passthrough(req, res, bodyBuffer); |
| } |
|
|
| const model = extractModel(bodyBuffer); |
| const mappedModel = getMappedModel(model); |
|
|
| if (!mappedModel) { |
| return passthrough(req, res, bodyBuffer); |
| } |
|
|
| console.log(`π ${model} β ${mappedModel}`); |
| return intercept(req, res, bodyBuffer, mappedModel); |
| }); |
|
|
| server.listen(LOCAL_PORT, () => { |
| console.log(`π MITM ready on :${LOCAL_PORT} β ${ROUTER_URL}`); |
| }); |
|
|
| 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)); |
| }); |
|
|