fix(mitm): add IPv6 DNS redirect, modular antigravity target, improved logging (#2514)

Integrated into release/v3.8.2
This commit is contained in:
Hernan Javier Ardila Sanchez
2026-05-21 20:53:26 +02:00
committed by GitHub
parent 4e476b3fdd
commit 3cbe57a8c7
5 changed files with 114 additions and 33 deletions

View File

@@ -1,18 +1,28 @@
export const ANTIGRAVITY_PUBLIC_MODELS = Object.freeze([
// Gemini 3.5 Flash — flagship model in Antigravity 2.0 (May 2026)
{
id: "claude-opus-4-6-thinking",
name: "Claude Opus 4.6 (Thinking)",
contextLength: 250000,
maxOutputTokens: 64000,
id: "gemini-3.5-flash-preview",
name: "Gemini 3.5 Flash (High)",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
},
{
id: "claude-sonnet-4-6",
name: "Claude Sonnet 4.6 (Thinking)",
contextLength: 250000,
maxOutputTokens: 64000,
id: "gemini-3.5-flash-low",
name: "Gemini 3.5 Flash (Low)",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
},
{
id: "gemini-3-flash-agent",
name: "Gemini 3.5 Flash Agent",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
@@ -135,10 +145,12 @@ export const ANTIGRAVITY_PUBLIC_MODELS = Object.freeze([
export const ANTIGRAVITY_MODEL_ALIASES = Object.freeze({
"gemini-3-pro-preview": "gemini-3.1-pro-high",
"gemini-3.5-flash-preview": "gemini-3-flash-agent",
"gemini-3.5-flash-preview": "gemini-3.5-flash-high",
"gemini-3-flash-preview": "gemini-3-flash",
"gemini-3-pro-image-preview": "gemini-3-pro-image",
"gemini-2.5-computer-use-preview-10-2025": "rev19-uic3-1p",
// Deprecated: Claude models were removed from Antigravity 2.0 (May 2026).
// These aliases are kept for backward compatibility but will 404 on new requests.
"gemini-claude-sonnet-4-5": "claude-sonnet-4-6",
"gemini-claude-sonnet-4-5-thinking": "claude-sonnet-4-6",
"gemini-claude-opus-4-5-thinking": "claude-opus-4-6-thinking",
@@ -148,6 +160,7 @@ type AntigravityModelAliasMap = Record<string, string>;
export const ANTIGRAVITY_REVERSE_MODEL_ALIASES: AntigravityModelAliasMap = Object.freeze({
"gemini-3.1-pro-high": "gemini-3-pro-preview",
"gemini-3.5-flash-high": "gemini-3.5-flash-preview",
"gemini-3-flash-agent": "gemini-3.5-flash-preview",
"gemini-3-flash": "gemini-3-flash-preview",
"gemini-3-pro-image": "gemini-3-pro-image-preview",

View File

@@ -8,6 +8,7 @@ import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
import { resolveApiKey } from "@/shared/services/apiKeyResolver";
import { resolveMitmDataDir } from "@/mitm/dataDir";
import { KIRO_MITM_PROFILE } from "@/mitm/targets/kiro";
import { ANTIGRAVITY_MITM_PROFILE } from "@/mitm/targets/antigravity";
type MitmTargetRoute = {
id: string;
@@ -70,14 +71,18 @@ function getKeyPath() {
}
function defaultTargets(port = DEFAULT_PORT): MitmTargetRoute[] {
const allHosts = [
ANTIGRAVITY_MITM_PROFILE.targetHost,
...(ANTIGRAVITY_MITM_PROFILE.additionalHosts || []),
];
return [
{
id: "antigravity",
name: "Antigravity",
targetHost: "daily-cloudcode-pa.googleapis.com",
targetPort: 443,
id: ANTIGRAVITY_MITM_PROFILE.id,
name: ANTIGRAVITY_MITM_PROFILE.name,
targetHost: allHosts.join(", "),
targetPort: ANTIGRAVITY_MITM_PROFILE.targetPort,
localPort: port,
endpoints: [":generateContent", ":streamGenerateContent"],
endpoints: ANTIGRAVITY_MITM_PROFILE.apiEndpoints,
enabled: true,
},
{

View File

@@ -13,6 +13,10 @@ const HOSTS_FILE = IS_WIN
? path.join(process.env.SystemRoot || "C:\\Windows", "System32", "drivers", "etc", "hosts")
: "/etc/hosts";
// Both IPv4 and IPv6 entries are needed — modern Windows apps often resolve
// to IPv6 first, bypassing an IPv4-only MITM redirect.
const DNS_ENTRIES = [`127.0.0.1 ${TARGET_HOST}`, `::1 ${TARGET_HOST}`];
const REMOVE_HOSTS_ENTRY_SCRIPT = `
const fs = require("fs");
const filePath = process.argv[1];
@@ -25,34 +29,45 @@ const filtered = content.split(/\\r?\\n/).filter((line) => {
fs.writeFileSync(filePath, filtered.join("\\n").replace(/\\n*$/, "\\n"));
`;
/**
* Check if DNS entry already exists
*/
export function checkDNSEntry(): boolean {
try {
const hostsContent = fs.readFileSync(HOSTS_FILE, "utf8");
const lines = hostsContent.split(/\r?\n/);
return lines.some((line) => {
const parts = line.trim().split(/\s+/);
return parts.length >= 2 && parts[0] === "127.0.0.1" && parts.some((p) => p === TARGET_HOST);
return DNS_ENTRIES.every((entry) => {
const entryIp = entry.split(/\s+/)[0];
return lines.some((line) => {
const parts = line.trim().split(/\s+/);
return parts.length >= 2 && parts[0] === entryIp && parts.some((p) => p === TARGET_HOST);
});
});
} catch {
return false;
}
}
/**
* Add DNS entry to hosts file
*/
export async function addDNSEntry(sudoPassword: string): Promise<void> {
if (checkDNSEntry()) {
console.log(`DNS entry for ${TARGET_HOST} already exists`);
console.log(`DNS entries for ${TARGET_HOST} already exist (IPv4 + IPv6)`);
return;
}
const entry = `127.0.0.1 ${TARGET_HOST}`;
const entriesToAdd = DNS_ENTRIES.filter((entry) => {
const entryIp = entry.split(/\s+/)[0];
try {
const hostsContent = fs.readFileSync(HOSTS_FILE, "utf8");
const lines = hostsContent.split(/\r?\n/);
return !lines.some((line) => {
const parts = line.trim().split(/\s+/);
return parts.length >= 2 && parts[0] === entryIp && parts.some((p) => p === TARGET_HOST);
});
} catch {
return true;
}
});
try {
if (entriesToAdd.length === 0) return;
for (const entry of entriesToAdd) {
if (IS_WIN) {
await runElevatedPowerShell(
`Add-Content -LiteralPath ${quotePowerShell(HOSTS_FILE)} -Value ${quotePowerShell(entry)}`
@@ -65,9 +80,7 @@ export async function addDNSEntry(sudoPassword: string): Promise<void> {
`${entry}\n`
);
}
console.log(`Added DNS entry: ${entry}`);
} catch (error) {
throw new Error(`Failed to add DNS entry: ${getErrorMessage(error)}`);
console.log(`Added DNS entry: ${entry}`);
}
}

View File

@@ -13,10 +13,12 @@ function getDataDir() {
}
// Configuration
// Keep in sync with src/mitm/targets/antigravity.ts
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 parsedLocalPort = Number.parseInt(process.env.MITM_LOCAL_PORT || "443", 10);
const LOCAL_PORT =
@@ -288,25 +290,34 @@ const server = https.createServer(sslOptions, async (req, res) => {
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"}`);
// Save request log if enabled
if (bodyBuffer.length > 0) saveRequestLog(req.url, bodyBuffer);
// Anti-loop: requests from OmniRoute bypass interception
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 model = extractModel(bodyBuffer);
const mappedModel = getMappedModel(model);
if (!mappedModel) {
console.log(`[MITM] → PASSTHROUGH (model "${model}" has no MITM alias mapping)`);
return passthrough(req, res, bodyBuffer);
}
@@ -314,7 +325,7 @@ const server = https.createServer(sslOptions, async (req, res) => {
stats.lastInterceptAt = new Date().toISOString();
writeStats();
console.log(`🔀 ${model}${mappedModel}`);
console.log(`[MITM] INTERCEPTED ${model}${mappedModel}`);
return intercept(req, res, bodyBuffer, mappedModel);
});

View File

@@ -0,0 +1,39 @@
export interface MitmTarget {
id: string;
name: string;
description: string;
targetHost: string;
targetPort: number;
localPort: number;
userAgentPattern: string | null;
apiEndpoints: string[];
authHeader: string;
additionalHosts?: string[];
instructions: string[];
referenceIde?: string;
}
export const ANTIGRAVITY_MITM_PROFILE: MitmTarget = {
id: "antigravity",
name: "Antigravity IDE",
description:
"Intercepts Antigravity IDE requests to cloudcode-pa.googleapis.com and routes them through OmniRoute.",
targetHost: "daily-cloudcode-pa.googleapis.com",
targetPort: 443,
localPort: 443,
userAgentPattern: null,
apiEndpoints: [
"/v1internal:generateContent",
"/v1internal:streamGenerateContent",
"/v1internal:loadCodeAssist",
"/v1internal:onboardUser",
],
authHeader: "authorization",
additionalHosts: ["cloudcode-pa.googleapis.com", "daily-cloudcode-pa.sandbox.googleapis.com"],
instructions: [
"1. Install OmniRoute's root certificate",
"2. Start the MITM proxy via Dashboard or CLI",
"3. Configure model mappings in Dashboard → CLI Tools → Antigravity",
"4. Open Antigravity IDE — API calls will be routed through OmniRoute",
],
};