Fix : Antigravity MITM

This commit is contained in:
decolua
2026-04-29 17:28:38 +07:00
parent 512e3de371
commit 34da52f144
12 changed files with 313 additions and 128 deletions
+25 -26
View File
@@ -4,7 +4,7 @@ const path = require("path");
const dns = require("dns");
const { promisify } = require("util");
const { execSync } = require("child_process");
const { log, err } = require("./logger");
const { log, err, dumpRequest, createResponseDumper } = require("./logger");
const { TARGET_HOSTS, URL_PATTERNS, MODEL_SYNONYMS, getToolForHost } = require("./config");
const { DATA_DIR, MITM_DIR } = require("./paths");
const { getCertForDomain } = require("./cert/generate");
@@ -12,12 +12,9 @@ const { getCertForDomain } = require("./cert/generate");
const DB_FILE = path.join(DATA_DIR, "db.json");
const LOCAL_PORT = 443;
const IS_WIN = process.platform === "win32";
const ENABLE_FILE_LOG = true;
const LOG_DIR = path.join(DATA_DIR, "logs", "mitm");
const ENABLE_FILE_LOG = false;
const INTERNAL_REQUEST_HEADER = { name: "x-request-source", value: "local" };
if (ENABLE_FILE_LOG && !fs.existsSync(LOG_DIR)) fs.mkdirSync(LOG_DIR, { recursive: true });
// Load handlers — dev/ overrides handlers/ for private implementations
function loadHandler(name) {
try { return require(`./dev/${name}`); } catch {}
@@ -34,13 +31,17 @@ const handlers = {
// ── SSL / SNI ─────────────────────────────────────────────────
const certCache = new Map();
let rootCAPem;
function sniCallback(servername, cb) {
try {
if (certCache.has(servername)) return cb(null, certCache.get(servername));
const certData = getCertForDomain(servername);
if (!certData) return cb(new Error(`Failed to generate cert for ${servername}`));
const ctx = require("tls").createSecureContext({ key: certData.key, cert: certData.cert });
const ctx = require("tls").createSecureContext({
key: certData.key,
cert: `${certData.cert}\n${rootCAPem}`
});
certCache.set(servername, ctx);
log(`🔐 Cert generated: ${servername}`);
cb(null, ctx);
@@ -52,11 +53,10 @@ function sniCallback(servername, cb) {
let sslOptions;
try {
sslOptions = {
key: fs.readFileSync(path.join(MITM_DIR, "rootCA.key")),
cert: fs.readFileSync(path.join(MITM_DIR, "rootCA.crt")),
SNICallback: sniCallback
};
const rootKey = fs.readFileSync(path.join(MITM_DIR, "rootCA.key"));
const rootCert = fs.readFileSync(path.join(MITM_DIR, "rootCA.crt"));
rootCAPem = rootCert.toString("utf8");
sslOptions = { key: rootKey, cert: rootCert, SNICallback: sniCallback };
} catch (e) {
err(`Root CA not found: ${e.message}`);
process.exit(1);
@@ -116,24 +116,16 @@ function getMappedModel(tool, model) {
} catch { return null; }
}
function saveRequestLog(url, bodyBuffer) {
if (!ENABLE_FILE_LOG) return;
try {
const ts = new Date().toISOString().replace(/[:.]/g, "-");
const slug = url.replace(/[^a-zA-Z0-9]/g, "_").substring(0, 60);
const body = JSON.parse(bodyBuffer.toString());
fs.writeFileSync(path.join(LOG_DIR, `${ts}_${slug}.json`), JSON.stringify(body, null, 2));
} catch { /* ignore */ }
}
/**
* Forward request to real upstream.
* Optional onResponse(rawBuffer) callback — if provided, tees the response
* so it's both forwarded to client AND passed to the callback for inspection.
* Also tees full stream into a dump file when ENABLE_FILE_LOG is on.
*/
async function passthrough(req, res, bodyBuffer, onResponse) {
const targetHost = (req.headers.host || TARGET_HOSTS[0]).split(":")[0];
const targetIP = await resolveTargetIP(targetHost);
const dumper = ENABLE_FILE_LOG ? createResponseDumper(req, "passthrough") : null;
const forwardReq = https.request({
hostname: targetIP,
@@ -145,23 +137,30 @@ async function passthrough(req, res, bodyBuffer, onResponse) {
rejectUnauthorized: false
}, (forwardRes) => {
res.writeHead(forwardRes.statusCode, forwardRes.headers);
if (dumper) dumper.writeHeader(forwardRes.statusCode, forwardRes.headers);
if (!onResponse) {
if (!onResponse && !dumper) {
forwardRes.pipe(res);
return;
}
// Tee: forward to client AND buffer for callback
// Tee: forward to client AND optionally buffer + dump
const chunks = [];
forwardRes.on("data", chunk => { chunks.push(chunk); res.write(chunk); });
forwardRes.on("data", chunk => {
if (dumper) dumper.writeChunk(chunk);
if (onResponse) chunks.push(chunk);
res.write(chunk);
});
forwardRes.on("end", () => {
if (dumper) dumper.end();
res.end();
try { onResponse(Buffer.concat(chunks), forwardRes.headers); } catch { /* ignore */ }
if (onResponse) try { onResponse(Buffer.concat(chunks), forwardRes.headers); } catch { /* ignore */ }
});
});
forwardReq.on("error", (e) => {
err(`Passthrough error: ${e.message}`);
if (dumper) { dumper.writeChunk(`\n[ERROR] ${e.message}\n`); dumper.end(); }
if (!res.headersSent) res.writeHead(502);
res.end("Bad Gateway");
});
@@ -181,7 +180,7 @@ const server = https.createServer(sslOptions, async (req, res) => {
}
const bodyBuffer = await collectBodyRaw(req);
if (bodyBuffer.length > 0) saveRequestLog(req.url, bodyBuffer);
if (ENABLE_FILE_LOG) dumpRequest(req, bodyBuffer, "raw");
// Anti-loop: skip requests from 9Router
if (req.headers[INTERNAL_REQUEST_HEADER.name] === INTERNAL_REQUEST_HEADER.value) {