Skip to content

fix: tunnel a Google login to the picture host only, not to googleusercontent.com - #20

Merged
sstriker merged 5 commits into
mainfrom
fix/tunnel-exact-hosts
Oct 24, 2026
Merged

sstriker merged 5 commits into
mainfrom
fix/tunnel-exact-hosts

Conversation

@sstriker

@sstriker sstriker commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

The CONNECT allowlist of the google destination had the entry .googleusercontent.com. Every Compute Engine VM has a reverse-DNS name under that domain, which resolves to the VM itself. For example, 81.59.117.34.bc.googleusercontent.com resolves to 34.117.59.81. So a harness under that destination could open a TLS tunnel, with no metering, to any VM that someone rents.

The change

  • crates/memory/src/main.rs: the entry is now lh3.googleusercontent.com, the one host that Antigravity (agy) needs there. Commit a4271d3 records this from a real Google login: "agy asks www.googleapis.com for the account and lh3.googleusercontent.com for its picture before anything else, and answered once both were tunnelled."
  • local/README.md and PLAN.md:102 name the exact host.
  • The comment says what the entry pins: the front end the tunnel connects to, not the tenant behind it. The egress does not check the TLS server name inside the tunnel. That is a separate issue, given below.

Other wildcard entries

The other dot entries are .githubcopilot.com, .chatgpt.com and .grok.com. They stay, for these reasons:

  • None is on the Public Suffix List.
  • None has wildcard DNS: random labels do not resolve.
  • GitHub's and OpenAI's network docs give only their own subdomains under these names. Users get no subdomain there.

Failing first

Against main: a tunnel to 81.59.117.34.bc.googleusercontent.com left: 200 right: 403.

The test sends CONNECT host:443 through the egress router, which never dials:

  • Refusals: the VM's reverse name, and other names on Google's and GitHub's shared front ends, get 403. The body must say "no destination allows a tunnel to".
  • Hosts the login needs: oauth2.googleapis.com, accounts.google.com, cloudcode-pa, daily-cloudcode-pa, www.googleapis.com and lh3.googleusercontent.com pass the allowlist. The body must not say "no destination allows a tunnel to".

With exact hosts refused in the router (a mutation): a tunnel to oauth2.googleapis.com ... left: 403 right: 200.

make check: 621 tests, 620 passed. The one failure was the known companion_browser race, which a separate PR fixes (branch fix/companion-blank-race). It passed when run alone.

Adversarial pass

A fresh agent attacked 7ae42d7. It confirmed these points:

  • With .googleusercontent.com put back, the test fails.
  • Gemini CLI 0.65.0 does not need the picture host.
  • No other path admits the domain by suffix.

It found these gaps, all fixed in 783cb04:

  • The test claimed to refuse "names anyone can get". It is now limited to what the change proves.
  • The positive half checked only list membership, not the router.
  • A refusal for another reason passed.

It also found a larger problem, already on main and not in this PR. The tunnel checks only the CONNECT authority. Inside the tunnel, the harness chooses the TLS server name (SNI). The agent showed, through the real egress:

  • CONNECT lh3.googleusercontent.com then TLS to a GCS bucket or an App Engine app worked;
  • chatgpt.com reached other Cloudflare customers;
  • api.githubcopilot.com reached raw.githubusercontent.com.

The fix is to read the ClientHello and require its SNI to match the CONNECT host. A separate PR adds this check.

Note for review

Merge with #13. #13 makes an admitted tunnel resolve its name before it answers. That step needs a network. So the test now raises the session label above the ceiling of google first (46c5ceb). Each host that the login needs must then get the refusal that names google: "this session has read restricted material and google accepts at most internal". That refusal comes before the egress resolves the name, so the test needs no network. Under unshare -rn it passes in 0.45 s.

Mutations of the entry list:

  • With lh3.googleusercontent.com removed: a tunnel to lh3.googleusercontent.com: {"error":{"message":"no destination allows a tunnel to lh3.googleusercontent.com:443",...}}.
  • With lh3.googleusercontent.com moved to github: ... "this session has read restricted material and github accepts at most internal" ....

Second adversarial pass. A fresh agent attacked df1b376. It found that the intermediate test (e169c6d) passed when every admitted tunnel was refused for another reason, and when another destination admitted the host. It proposed the ceiling route, which 46c5ceb uses. It also noted, as reasoning only, that a profile picture on lh4 to lh6 would be refused. The real login in a4271d3 used lh3 only. If another lhN host is needed, the fix is to add that host, not the domain.

make check after the merge of main: 642 tests, 642 passed, 3 skipped.

Posted by Claude on behalf of @sstriker

sstriker and others added 2 commits October 24, 2026 16:56
…rcontent.com

The tunnel list of the google destination had the entry
.googleusercontent.com. This entry admits all names under that domain. The
reverse DNS name of each Compute Engine VM is under that domain. For
example, 81.59.117.34.bc.googleusercontent.com resolves to 34.117.59.81.
Thus a harness could open a tunnel to a VM that any person can rent. A
tunnel has no metering, and the egress cannot see the data in it.

The Antigravity eligibility check gets the picture of the account from
lh3.googleusercontent.com. A run on a real login on 7 October showed this
(commit a4271d3). Replace the domain entry with this one host.

The other domain entries are .githubcopilot.com, .chatgpt.com and
.grok.com. The vendor operates each of these zones. None is on the Public
Suffix List, and none has a wildcard DNS record. Thus they stay as they are.

Add a test. It makes sure that no login destination admits a tunnel to a
name that any person can get (a VM reverse name, appspot.com, run.app,
cloudfunctions.net, a storage bucket, github.io, githubusercontent.com).
It also makes sure that the google destination admits the hosts that a
login needs. The test sends the CONNECT through the egress router with
tower, which is a new dev-dependency of amem.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…overs

The test of the last change examined only the strings in the tunnel list
for the hosts that a login needs. It passed also when the egress admitted
no exact host. The test now sends CONNECT host:443 for each of these
hosts through the egress router and expects 200. A oneshot request has no
upgrade, so the egress connects to nothing.

The refusal check expected only 403. A refusal for a different reason,
such as the ceiling, also passed it. The check now also expects the text
"no destination allows a tunnel to" in the body.

The egress examines only the CONNECT authority. It does not examine the
TLS server name in the tunnel. Thus a tunnel is pinned to a front end, not
to the tenant behind it. The test name, its comments and the comment on
the google entry now state only what the change does: the google
destination does not admit a name under googleusercontent.com, because a
Compute Engine VM has a reverse name there that resolves to the VM.

PLAN.md now gives lh3.googleusercontent.com in place of the domain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sstriker
sstriker requested review from andrewmusselman and a balanced review from Copilot October 24, 2026 15:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

It changes a security-sensitive egress tunnel allowlist whose correctness depends on external Google behaviour, and the author documents a merge-order hazard with PR #13 that will break the test without a network stand-in, so human oversight is warranted.

No open issues 🎉

What changed in this PR

This PR tightens the google CONNECT tunnel allowlist used by the Antigravity (agy) Google-login route. It replaces the wildcard entry .googleusercontent.com with the single exact host lh3.googleusercontent.com. The wildcard was a security hole: every Compute Engine VM has a reverse-DNS name under googleusercontent.com that resolves to the VM itself, so any harness on the google destination could open an unmetered TLS tunnel to any rented VM. The fix pins the one picture host that the eligibility check actually uses, observed on a real login.

Changes:

  • crates/memory/src/main.rs: narrow the google destination tunnel entry from .googleusercontent.com to lh3.googleusercontent.com, with an explanatory comment, plus a new #[tokio::test] that drives the egress router to assert the login hosts are tunnelled and VM/shared-front-end names are refused.
  • Docs (PLAN.md, local/README.md): update the named host to match the code.
  • Build (crates/memory/Cargo.toml, Cargo.lock): add tower (dev-dependency, util feature) to drive the router via oneshot in the test.
File Description
crates/​memory/​src/​main.rs Replaces the wildcard tunnel entry with the exact picture host and adds a router-level test for the allow/deny behaviour.
PLAN.md Updates the M5 row to name lh3.googleusercontent.com instead of the wildcard domain.
local/​README.md Updates the Antigravity row to name the exact picture host.
crates/​memory/​Cargo.toml Adds tower as a dev-dependency for the new test's oneshot call.
Cargo.lock Records the new tower dependency edge for the amem crate.

Review effort: Balanced

sstriker and others added 2 commits October 25, 2026 00:40
The test asked for a 200 from each host that a Google login needs.
After #13, the egress resolves the name and checks the addresses
before the 200. That step needs a network. The test now asserts
only that the allowlist lets each host through.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The change touches a security-sensitive egress tunnel boundary, so despite finding no defects it warrants final human sign-off rather than automated approval.

No open issues 🎉

Review effort: Balanced

The test asserted only that the allowlist did not refuse these hosts.
A refusal for another reason passed, and so did a host that another
destination admits. The test now raises the session label above the
ceiling of google. Each host must then get the refusal that names
google. That refusal comes before the egress resolves the name, so
the test needs no network.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

It modifies the egress CONNECT allowlist, a security boundary, so a human should confirm the host-pinning decision before approval.

No open issues 🎉

Review effort: Balanced

@sstriker
sstriker merged commit 39f11f3 into main Oct 24, 2026
9 checks passed
@sstriker
sstriker deleted the fix/tunnel-exact-hosts branch October 24, 2026 23:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants