Skip to content

fix(sse): match the native Antigravity CLI wire image - #17913

Open
Junior-HJ wants to merge 2 commits into
cheaper-inference:release/v3.8.53from
Junior-HJ:fix/antigravity-wire-fidelity
Open

Junior-HJ wants to merge 2 commits into
cheaper-inference:release/v3.8.53from
Junior-HJ:fix/antigravity-wire-fidelity

Conversation

@Junior-HJ

@Junior-HJ Junior-HJ commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Why

The antigravity CLI client profile was sending the IDE fingerprint on every request. A live mitmproxy capture of the native Go binary (agy 1.4.4, Windows x64) shows four concrete divergences:

Native CLI (captured) OmniRoute before
Platform token os_type=windows; arch=amd64 (Go GOOS/GOARCH) pinned darwin/arm64
Changelist cl=1003595037 absent
Accept on streamGenerateContent absent (SSE comes from ?alt=sse) text/event-stream
x-goog-user-project (consumer) absent sent whenever a project was known
loadCodeAssist metadata {"ideType":"ANTIGRAVITY"} (38 bytes, 200 OK) numeric enums (ideType: 9, platform, pluginType: 2)

What changed

  • antigravityCliUserAgent now renders the real host in Go's dialect plus the build changelist:
    antigravity/cli/<ver> (aidev_client; os_type=<goos>; arch=<goarch>; cl=<cl>; auth_method=consumer).
    • resolveAntigravityGoPlatform() maps process.platform/process.arch (win32 → windows, x64 → amd64, …).
    • resolveAntigravityCliChangelist() resolves ANTIGRAVITY_CLI_CL, then a known-version table, then the last captured value. cl is a Google Piper changelist — not derivable from the version, and the public auto-updater manifests only expose version/url/sha512.
  • streamGenerateContent stops sending Accept and x-goog-user-project on the CLI profile. sendAntigravityRequest keeps its one-shot 403 retry without the quota-project header.
  • loadCodeAssist/onboardUser send the native minimal body on the CLI profile. Every call site (antigravityProjectBootstrap, usage/antigravity, oauth/providers/antigravity, agyAuthImport) now passes its real profile, so the IDE path keeps the numeric enums that its own live comparison proved necessary.
  • CLI_FINGERPRINTS.antigravity.headerOrder matches the capture (no Accept, no x-goog-*).
  • Cleanup: the never-read ANTIGRAVITY_USER_AGENT env knob is gone from .env.example, docs/security/STEALTH_GUIDE.md and docs/reference/ENVIRONMENT.md (no source reference existed).

The IDE profile is untouched: darwin/arm64 pin, Accept: text/event-stream, x-goog-user-project, and the numeric protobuf enums.

Tests

  • New tests/unit/antigravity-wire-order.test.ts (red → green) plus the golden fixture tests/fixtures/antigravity/native-wire.json (Authorization redacted — no bearer token is stored). It asserts the UA byte for byte against the capture, the Go platform mapping, both loadCodeAssist body shapes, and that the CLI path carries only authorization/content-type/user-agent.
  • tests/unit/executor-antigravity.test.ts: new case proving buildHeaders omits Accept/x-goog-user-project for cli and keeps Accept for ide.
  • Updated three pre-existing assertions that encoded the old behaviour (antigravity-headers.test.ts, antigravity-client-profile.test.ts).
  • 800/800 Antigravity-related unit tests green; npm run typecheck:core, npm run lint, check:docs-sync and check-changelog-integrity clean.

Notes

  • Scope is client wire consistency only — no evasion work.
  • The three ESLint no-unused-vars errors in cliFingerprints.ts:236, antigravityClientProfile.ts:2 and executor-antigravity.test.ts:688 are pre-existing on the base branch and untouched by this diff; npm run lint (with suppressions) passes.

⚠️ base-red inherited: release/v3.8.53 tip is red independently of this PR — the failures are tracked in #17219.

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thanks @Junior-HJ, this is a really careful piece of work. Basing the change on an actual mitmproxy capture of agy 1.4.4 and committing a golden fixture (with the bearer token redacted) is exactly how fingerprint fidelity should be done, and keeping the IDE profile byte-for-byte unchanged (darwin/arm64 pin, Accept, numeric enums) makes the blast radius easy to reason about. The Go GOOS/GOARCH mapping, the cl= handling and the profile-aware loadCodeAssist metadata are clean, and I confirmed the new tests go red when the source hunks are reverted and green with them (49/49 on the Antigravity suites).

What we will fix in place so you do not need another round: add the new ANTIGRAVITY_CLI_CL variable to .env.example and docs/reference/ENVIRONMENT.md (the check:env-doc-sync gate flags it), and rename the changelog fragment to include the PR number (17913-antigravity-cli-wire-image-fidelity.md). We may also add a numeric guard on that env value and a small test that the bootstrap/usage/OAuth call sites pass the cli profile.

Two things we would love your input on, since you have the capture: (1) does the native CLI send the same minimal {"ideType":"ANTIGRAVITY"} metadata on onboardUser, or only on loadCodeAssist? The PR extends it to both, but the capture you describe only shows loadCodeAssist. (2) Did you test a non-consumer (Workspace/enterprise) account without x-goog-user-project? Dropping it for all cli connections is the one behavioural change we cannot verify offline, so we will run a live check on our side before merging. Also worth remembering that cl= is pinned to the 1.4.4 build, so it will need a refresh when agy bumps.

@Junior-HJ

Copy link
Copy Markdown
Contributor Author

Thanks @diegosouzapw, and thanks for taking the .env.example / ENVIRONMENT.md sync and the changelog rename off our plate.

On your two questions:

1. onboardUser metadata. Our captures only ever show loadCodeAssist — a signed-in CLI never re-onboards, so we have no native onboardUser request to point at. Extending the minimal {"ideType":"ANTIGRAVITY"} shape to onboardUser was symmetry on our side, not evidence. If you would rather keep the numeric protobuf enums there until someone captures a real onboarding run, that is the safer call and we will not argue for it.

2. Non-consumer accounts. Every capture is auth_method=consumer, and the CLI never emitted x-goog-user-project on any of them. We have no Workspace/GCP-TOS account to test, so dropping the header for all cli connections is exactly the part we cannot stand behind offline. Running your live check before merge is right.

One thing we are pushing now, since you flagged it: cl= was pinned to the 1.4.4 build via a fallback, so a bumped agy would have advertised cl=1003595037 next to a version that never shipped with it. resolveAntigravityCliChangelist now returns null when the version is not in the known-version table and ANTIGRAVITY_CLI_CL is unset, and the User-Agent drops the cl= token entirely in that case. 1.4.4 still resolves 1003595037, so the golden fixture is unchanged. That means a future bump degrades to "no changelist" instead of "wrong changelist", which we think is the better failure mode for a fingerprint.

Also happy to add the numeric guard on ANTIGRAVITY_CLI_CL if you want it in this PR rather than in your follow-up.

@Junior-HJ
Junior-HJ force-pushed the fix/antigravity-wire-fidelity branch 2 times, most recently from 4074bef to ad82e99 Compare October 28, 2026 15:44
The CLI profile presented the IDE fingerprint: a pinned darwin/arm64 platform token,
an `Accept: text/event-stream` header, `x-goog-user-project` on the consumer path, and
the numeric-enum loadCodeAssist body. A live capture of the native Go binary
(agy 1.4.4, Windows x64) shows none of that.

- User-Agent now reports the real host in Go's GOOS/GOARCH dialect (windows/amd64,
  not Node's win32/x64) plus the build changelist:
  `antigravity/cli/<ver> (aidev_client; os_type=<goos>; arch=<goarch>; cl=<cl>; auth_method=consumer)`.
  The changelist is not derivable from the version nor exposed by the public
  manifests, so it resolves from ANTIGRAVITY_CLI_CL, then a known-version table
  seeded from the capture.
- streamGenerateContent drops `Accept` and `x-goog-user-project` on the CLI profile;
  SSE is requested by `?alt=sse` alone. The IDE profile is unchanged and still sends
  `Accept`, and sendAntigravityRequest keeps its one-shot 403 retry without the
  quota-project header.
- loadCodeAssist sends the native 38-byte `{"metadata":{"ideType":"ANTIGRAVITY"}}`
  body on the CLI profile; every call site passes its real profile so the IDE path
  keeps the numeric enums.
- Header order for the antigravity fingerprint matches the capture, and the
  never-read ANTIGRAVITY_USER_AGENT env knob is gone from .env.example and the docs.
resolveAntigravityCliChangelist fell back to 1.4.4's changelist for every version, so a bumped agy advertised cl=1003595037 alongside a version that never shipped with it. A stale cl is a worse fingerprint than no cl at all.

The resolver now returns null when the version is unknown and ANTIGRAVITY_CLI_CL is unset, and antigravityCliUserAgent drops the cl token entirely in that case. 1.4.4 still resolves 1003595037 from the known-version table, so the captured wire image is unchanged.
@Junior-HJ
Junior-HJ force-pushed the fix/antigravity-wire-fidelity branch from ad82e99 to 7e2930c Compare October 28, 2026 17:46

This branch has not been deployed

No deployments
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