Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 22 additions & 9 deletions open-sse/config/antigravityModelAliases.ts
Original file line number Diff line number Diff line change
@@ -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: "gemini-3.5-flash-low",
name: "Gemini 3.5 Flash (Low)",
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-flash-agent",
name: "Gemini 3.5 Flash Agent",
contextLength: 1048576,
maxOutputTokens: 65536,
supportsReasoning: true,
supportsVision: true,
toolCalling: true,
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down
15 changes: 10 additions & 5 deletions src/app/api/settings/mitm/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
},
{
Expand Down
43 changes: 28 additions & 15 deletions src/mitm/dns/dnsConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The DNS_ENTRIES array is constructed using a single hardcoded TARGET_HOST. This is inconsistent with the goal of a modular target configuration. Since the ANTIGRAVITY_MITM_PROFILE now includes additionalHosts (e.g., cloudcode-pa.googleapis.com), traffic to those domains will not be redirected to the MITM proxy, causing them to bypass interception. The DNS configuration should be updated to handle all hosts defined in the active profile.


const REMOVE_HOSTS_ENTRY_SCRIPT = `
const fs = require("fs");
const filePath = process.argv[1];
Expand All @@ -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)}`
Expand All @@ -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}`);
}
}

Expand Down
19 changes: 15 additions & 4 deletions src/mitm/server.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -288,33 +290,42 @@ 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);
}
Comment on lines 310 to 315

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The CHAT_URL_PATTERNS check (line 77) only allows interception of :generateContent and :streamGenerateContent. This conflicts with the new modular ANTIGRAVITY_MITM_PROFILE which includes additional endpoints like loadCodeAssist and onboardUser. Requests to these additional endpoints will be passed through without interception due to the check on line 312, rendering the modular endpoint configuration in the profile ineffective. The proxy server logic should be synchronized with the profile's apiEndpoints.


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);
}

stats.interceptedRequests++;
stats.lastInterceptAt = new Date().toISOString();
writeStats();

console.log(`🔀 ${model} → ${mappedModel}`);
console.log(`[MITM] INTERCEPTED ${model} → ${mappedModel}`);
return intercept(req, res, bodyBuffer, mappedModel);
});

Expand Down
39 changes: 39 additions & 0 deletions src/mitm/targets/antigravity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export interface MitmTarget {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

According to the repository style guide (Rule 9), changes to production code in src/ or open-sse/ must include tests. This pull request introduces significant changes to the MITM logic, DNS configuration, and model mappings but does not include any new unit or integration tests in the tests/ directory.

References
  1. Always include tests when changing production code (src/, open-sse/, electron/, bin/). (link)

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",
],
};
Comment on lines +16 to +39

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The ANTIGRAVITY_MITM_PROFILE contains hardcoded user-facing strings for name, description, and instructions, which bypasses the project's internationalization (i18n) system. Furthermore, the instructions array appears to be unused, as the dashboard UI (AntigravityToolCard.tsx) implements its own localized instruction steps. These strings should be moved to the locale files, and unused fields should be removed from the profile definition.