Skip to content

feat(agent): send X-Client version header to our proxy for log attribution (#254) - #308

Merged
cboettig merged 1 commit into
mainfrom
feat/x-client-254
Jul 10, 2026
Merged

feat(agent): send X-Client version header to our proxy for log attribution (#254)#308
cboettig merged 1 commit into
mainfrom
feat/x-client-254

Conversation

@cboettig

Copy link
Copy Markdown
Member

What

Sends X-Client: geo-agent/<ref> on requests to our proxy so its logs can attribute a session to the exact build (the client column, currently always null).

  • Version source: import.meta.url — the pinned jsDelivr ref the app actually loaded (geo-agent@v3.13.1, or a commit SHA). There's no build step and package.json is stale (1.0.0), so the module URL is the only accurate source. Falls back to dev on a file:// URL (local/headless).

The BYO-endpoint risk this avoids (gating)

geo-agent runs in the browser, so a custom request header extends the CORS preflight. A bring-your-own endpoint whose Access-Control-Allow-Headers is an explicit list without X-Client would have the browser block every request to it — a hard break, not just missing attribution. (The server ignoring an unknown header is harmless; CORS is the actual failure mode.)

So the header is gated: sent only to hosts matching client_header_hosts (default suffix nrp-nautilus.io — the same origin the proxy's own CORS regex trusts), never to external BYO endpoints. Attribution only matters in our logs anyway, so gating costs nothing.

Proxy side is already done

Verified in open-llm-proxy: llm_proxy.py:490 reads x-client → logs it as client; the HAProxy ingress cors-allow-headers already lists X-Client (ingress.yaml:21, with a comment noting it was pre-added for this). So this is purely the client half — no proxy change needed.

Tests

test/agent-client-header.test.js (4 cases): header sent to our proxy, not sent to an external BYO endpoint (auth/content-type unaffected), suffix matching (incl. bare apex, rejects evil-nrp-nautilus.io substring attack and non-URLs), and the client_header_hosts override. Full suite: 489 passing.

Closes #254

…ution (#254)

Adds X-Client: geo-agent/<ref> so proxy logs can attribute a session to the
exact build. The version comes from import.meta.url (the pinned jsDelivr ref
the app loaded, e.g. geo-agent@v3.13.1) — there is no build step and
package.json is stale, so the module URL is the only accurate source. Falls
back to 'dev' for local/headless (file:// URL).

Gated to trusted proxy hosts (default suffix nrp-nautilus.io; override via
client_header_hosts). geo-agent runs in the browser, so a custom request
header extends the CORS preflight — a bring-your-own endpoint that doesn't
allowlist X-Client would have the browser block *every* request to it. Only
our proxy allowlists it (ingress cors-allow-headers) and only our logs use
it, so it's never sent to external BYO endpoints.

Proxy side is already wired (llm_proxy.py reads x-client → logs 'client';
ingress CORS allows it) — this is the client half.

Closes #254
@github-actions

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 56.63% 4949 / 8738
🔵 Statements 56.61% 4949 / 8742
🔵 Functions 63.6% 180 / 283
🔵 Branches 83.74% 1082 / 1292
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
app/agent.js 96.1% 85.61% 88.88% 96.1% 87-89, 106-107, 132, 248-250, 257-258, 272-276, 328-333, 342-346, 381-382, 807-808, 879-880, 1005-1007
Generated in workflow #160 for commit a804ba2 by the Vitest Coverage Report Action

@cboettig
cboettig merged commit 0c275d3 into main Jul 10, 2026
1 check passed
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.

Send a client/version header (X-Client) to the LLM proxy for log attribution

1 participant