From 61277813b2cb2ab38a7bf2200896717b91c352b6 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sat, 25 Jul 2026 04:57:07 -0300 Subject: [PATCH] fix(backend): compute AgentBridge diagnose DNS check per-agent instead of hard-coded Antigravity (#8466) (#8502) getMitmStatus() hard-wired dnsConfigured to a single Antigravity hostname regex regardless of which agent was being diagnosed, and the diagnose route never accepted an agentId to check against. Add checkDNSEntryForAgent() reusing resolveHostsForAgent()'s existing per-target host resolution, thread an optional agentId through getMitmStatus(), and have the diagnose route parse ?agentId= and pass it through. Callers that omit agentId keep the legacy Antigravity-only behavior unchanged. Co-authored-by: ikelvingo --- .../fixes/8466-agentbridge-dns-per-agent.md | 1 + .../api/tools/agent-bridge/diagnose/route.ts | 5 +- src/mitm/dns/dnsConfig.ts | 13 ++ src/mitm/manager.ts | 25 +++- .../agent-bridge-dns-per-agent-8466.test.ts | 111 ++++++++++++++++++ 5 files changed, 147 insertions(+), 8 deletions(-) create mode 100644 changelog.d/fixes/8466-agentbridge-dns-per-agent.md create mode 100644 tests/unit/agent-bridge-dns-per-agent-8466.test.ts diff --git a/changelog.d/fixes/8466-agentbridge-dns-per-agent.md b/changelog.d/fixes/8466-agentbridge-dns-per-agent.md new file mode 100644 index 0000000000..96e0f8e751 --- /dev/null +++ b/changelog.d/fixes/8466-agentbridge-dns-per-agent.md @@ -0,0 +1 @@ +- fix(backend): compute AgentBridge diagnose `dnsConfigured` per-agent instead of hard-coded to Antigravity hosts (#8466) diff --git a/src/app/api/tools/agent-bridge/diagnose/route.ts b/src/app/api/tools/agent-bridge/diagnose/route.ts index 26dd7f2e40..2de8fd3d23 100644 --- a/src/app/api/tools/agent-bridge/diagnose/route.ts +++ b/src/app/api/tools/agent-bridge/diagnose/route.ts @@ -37,9 +37,10 @@ function probeTcp(port: number, host = "127.0.0.1", timeoutMs = 1500): Promise { +export async function GET(request: Request): Promise { try { - const status = await getMitmStatus(); + const agentId = new URL(request.url).searchParams.get("agentId") ?? undefined; + const status = await getMitmStatus(agentId); const certPath = path.join(resolveMitmDataDir(), "mitm", "server.crt"); const certExists = fs.existsSync(certPath); const certTrusted = certExists ? await checkCertInstalled(certPath) : false; diff --git a/src/mitm/dns/dnsConfig.ts b/src/mitm/dns/dnsConfig.ts index 8d089791e3..938eae5296 100644 --- a/src/mitm/dns/dnsConfig.ts +++ b/src/mitm/dns/dnsConfig.ts @@ -256,6 +256,19 @@ export function checkDNSEntry(): boolean { return ANTIGRAVITY_HOSTS.every((h) => hasHostEntry(hostsContent, h)); } +/** + * Check whether ALL hosts for the given agent are present in /etc/hosts. + * Falls back to the Antigravity legacy hosts when `agentId` is omitted or + * unknown, via `resolveHostsForAgent()` — so callers get the same host set + * that `addDNSEntry`/`removeDNSEntry` already use for that agent. Used by + * `getMitmStatus()` to answer "are THIS agent's hosts spoofed?" instead of + * always checking the Antigravity-only set (#8466). + */ +export function checkDNSEntryForAgent(agentId?: string): boolean { + const hostsContent = readHostsFile(); + return resolveHostsForAgent(agentId).every((h) => hasHostEntry(hostsContent, h)); +} + /** * Add DNS entries for the Antigravity default hosts, or for a specific agent * when `agentId` is provided. diff --git a/src/mitm/manager.ts b/src/mitm/manager.ts index d59a443516..74e0bd81d1 100644 --- a/src/mitm/manager.ts +++ b/src/mitm/manager.ts @@ -2,7 +2,7 @@ import { spawn, type ChildProcess } from "child_process"; import path from "path"; import fs from "fs"; import { resolveMitmDataDir } from "./dataDir.ts"; -import { removeDNSEntry, removeDNSEntries } from "./dns/dnsConfig.ts"; +import { removeDNSEntry, removeDNSEntries, checkDNSEntryForAgent } from "./dns/dnsConfig.ts"; import { provisionDnsEntries } from "./dns/provision.ts"; import { generateCert } from "./cert/generate.ts"; import { installCertResult, installCaCert } from "./cert/install.ts"; @@ -353,9 +353,16 @@ export async function handleExitCleanup( } /** - * Get MITM status + * Get MITM status. + * + * @param agentId - Optional agent whose hosts should be checked in DNS. When + * omitted, preserves the legacy Antigravity-only check (unchanged behavior + * for the existing no-agentId call sites: state/route.ts, server/route.ts, + * settings/mitm/route.ts, cli-tools/antigravity-mitm/route.ts). When + * provided (e.g. by the diagnose route), checks that agent's own hosts + * instead of always checking the Antigravity host set (#8466). */ -export async function getMitmStatus(): Promise<{ +export async function getMitmStatus(agentId?: string): Promise<{ running: boolean; pid: number | null; dnsConfigured: boolean; @@ -387,11 +394,17 @@ export async function getMitmStatus(): Promise<{ } } - // Check DNS configuration + // Check DNS configuration. When an agentId is provided, check THAT agent's + // own hosts (#8466) instead of always checking the Antigravity host set — + // callers that don't pass agentId keep the legacy Antigravity-only check. let dnsConfigured = false; try { - const hostsContent = fs.readFileSync("/etc/hosts", "utf-8"); - dnsConfigured = /\bdaily-cloudcode-pa\.googleapis\.com\b/.test(hostsContent); + if (agentId) { + dnsConfigured = checkDNSEntryForAgent(agentId); + } else { + const hostsContent = fs.readFileSync("/etc/hosts", "utf-8"); + dnsConfigured = /\bdaily-cloudcode-pa\.googleapis\.com\b/.test(hostsContent); + } } catch { // Ignore } diff --git a/tests/unit/agent-bridge-dns-per-agent-8466.test.ts b/tests/unit/agent-bridge-dns-per-agent-8466.test.ts new file mode 100644 index 0000000000..c9ce228bab --- /dev/null +++ b/tests/unit/agent-bridge-dns-per-agent-8466.test.ts @@ -0,0 +1,111 @@ +/** + * Regression test for issue #8466: AgentBridge diagnostics `dnsConfigured` + * was hard-wired to the Antigravity host regex (src/mitm/manager.ts) instead + * of being computed per-agent via `resolveHostsForAgent(agentId)` + * (src/mitm/dns/dnsConfig.ts). + * + * We mock fs.readFileSync so the real /etc/hosts is never touched, then + * exercise getMitmStatus() -- the exact function named in the issue -- with + * hosts-file contents representing each failure direction, plus the + * diagnose route to prove the agentId query param is threaded through. + */ +import { test, mock, afterEach } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; + +afterEach(() => { + mock.restoreAll(); +}); + +test("FALSE NEGATIVE: Claude Code host correctly spoofed, no Antigravity host present -> dnsConfigured should be true when checked for claude-code", async () => { + const realReadFileSync = fs.readFileSync.bind(fs); + mock.method(fs, "readFileSync", (p: string, enc?: BufferEncoding) => { + if (p === "/etc/hosts") { + // Claude Code target hosts = ["api.anthropic.com"] (src/mitm/targets/claudeCode.ts). + // The user correctly spoofed it. No Antigravity host present at all. + return "127.0.0.1 localhost\n127.0.0.1 api.anthropic.com\n::1 api.anthropic.com\n"; + } + return realReadFileSync(p, enc); + }); + + const { getMitmStatus } = await import("../../src/mitm/manager.ts?probe=8466-negative"); + const status = await getMitmStatus("claude-code"); + + assert.equal( + status.dnsConfigured, + true, + "dnsConfigured must be true when the agent being diagnosed (Claude Code) has its own host spoofed" + ); +}); + +test("FALSE POSITIVE: only a leftover Antigravity host is present, Claude Code host is missing -> dnsConfigured should be false when checked for claude-code", async () => { + const realReadFileSync = fs.readFileSync.bind(fs); + mock.method(fs, "readFileSync", (p: string, enc?: BufferEncoding) => { + if (p === "/etc/hosts") { + // Leftover Antigravity entry from a previous setup. Claude Code's host + // (api.anthropic.com) is NOT present. + return "127.0.0.1 localhost\n127.0.0.1 daily-cloudcode-pa.googleapis.com\n"; + } + return realReadFileSync(p, enc); + }); + + const { getMitmStatus } = await import("../../src/mitm/manager.ts?probe=8466-positive"); + const status = await getMitmStatus("claude-code"); + + assert.equal( + status.dnsConfigured, + false, + "dnsConfigured must be false when the agent being diagnosed (Claude Code) has no host spoofed, even if a leftover Antigravity host is present" + ); +}); + +test("no-agentId call sites keep legacy Antigravity-only behavior unchanged", async () => { + const realReadFileSync = fs.readFileSync.bind(fs); + mock.method(fs, "readFileSync", (p: string, enc?: BufferEncoding) => { + if (p === "/etc/hosts") { + // Claude Code host spoofed, but NO Antigravity host present. A caller + // that omits agentId (state/route.ts, server/route.ts, settings/mitm, + // cli-tools/antigravity-mitm) must still evaluate the legacy + // Antigravity-only regex, so this should remain false. + return "127.0.0.1 localhost\n127.0.0.1 api.anthropic.com\n::1 api.anthropic.com\n"; + } + return realReadFileSync(p, enc); + }); + + const { getMitmStatus } = await import("../../src/mitm/manager.ts?probe=8466-legacy"); + const status = await getMitmStatus(); + + assert.equal( + status.dnsConfigured, + false, + "callers that omit agentId must keep the legacy Antigravity-only check" + ); +}); + +test("diagnose route: threads ?agentId= query param through to getMitmStatus", async () => { + const realReadFileSync = fs.readFileSync.bind(fs); + mock.method(fs, "readFileSync", (p: string, enc?: BufferEncoding) => { + if (p === "/etc/hosts") { + return "127.0.0.1 localhost\n127.0.0.1 api.anthropic.com\n::1 api.anthropic.com\n"; + } + return realReadFileSync(p, enc); + }); + + const { GET } = await import( + "../../src/app/api/tools/agent-bridge/diagnose/route.ts?probe=8466-route" + ); + const res = await GET( + new Request("http://localhost/api/tools/agent-bridge/diagnose?agentId=claude-code") + ); + const body = (await res.json()) as { + checks: Array<{ name: string; ok: boolean }>; + }; + const dnsCheck = body.checks.find((c) => c.name === "dns-configured"); + + assert.ok(dnsCheck, "expected a dns-configured check in the diagnostics report"); + assert.equal( + dnsCheck?.ok, + true, + "diagnose route must pass agentId through to getMitmStatus so dns-configured reflects the diagnosed agent's hosts" + ); +});