mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-07 15:52:52 +03:00
fix(mitm): add IPv6 DNS redirect, modular antigravity target, improved logging (#2514)
Integrated into release/v3.8.2
This commit is contained in:
committed by
GitHub
parent
4e476b3fdd
commit
3cbe57a8c7
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
39
src/mitm/targets/antigravity.ts
Normal file
39
src/mitm/targets/antigravity.ts
Normal 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",
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user