Skip to content

feat!: the membership token travels in x-httpeers-token, never Authorization - #9

Merged
mkotelnikov merged 5 commits into
mainfrom
mesh-auth-header
Sep 19, 2026
Merged

mkotelnikov merged 5 commits into
mainfrom
mesh-auth-header

Conversation

@mkotelnikov

Copy link
Copy Markdown
Contributor

Why

LiteLLM's Playground, opened through the mesh (/peers/<hub>/llm/ui/), failed every call with 401 "malformed token". The Playground sends its key through the OpenAI SDK, which always puts it in Authorization: Bearer sk-…. The mesh token was in the same header, and the ServiceWorker edge never overwrites a page's Authorization. So no mesh token was attached, and withAccess tried to verify the LiteLLM key as a mesh token. LiteLLM never saw the request.

Authorization belongs to the application a request is addressed to. The mesh now uses its own header for its token.

What changes

  • httpeers-core: adds MESH_TOKEN_HEADER = x-httpeers-token (the bare token, no Bearer prefix), setMeshToken / readMeshToken, and MESH_CREDENTIAL_HEADERS (the token header plus x-httpeers-peer), which lists the headers a request leaving the mesh must not carry.
  • httpeers-access: withAccess reads only MESH_TOKEN_HEADER. A token sent in Authorization is treated as absent, and Authorization is left on the request for the handler.
  • httpeers-member: peerRequest and the edge write the new header. The edge attaches the token whatever the page put in Authorization, and still never overwrites a mesh token the page set itself.
  • httpeers-ghost: the pin writes the viewer's token to the new header.
  • hub llm passthrough: strips MESH_CREDENTIAL_HEADERS. A Bearer value in Authorization moves to x-litellm-api-key (the header the appliance's LiteLLM reads) when that header is absent. Authorization never reaches LiteLLM.
  • demos proxy page: its upstream strips MESH_CREDENTIAL_HEADERS. The builder moved to shared/proxy-upstream.ts so it can be tested.
  • conformance: a source-scan test fails if any library reads or writes Authorization, or spells the header name outside httpeers-core.
  • Docs: root README (a third "rule the packages are built on"), the core and access READMEs, docs/security-model.md, and the LiteLLM config comments.

All tests were written first and watched fail. tests/llm-mesh-credentials.test.ts reproduces the Playground request end to end through withAccess and the llm module.

Breaking: deploy the hub and the pages together

An old page sends its token in Authorization, and a new hub refuses it with 401 "membership token required". The reverse mismatch fails the same way. The hub and the static pages must be redeployed together.

Related

  • statewalker/webrun-wire mesh-auth-header: urlUpstream stops dropping authorization. This PR does not depend on it: with the published webrun-http-proxy 0.1.0, the passthrough moves the key before the proxy drops authorization, and the mesh headers are stripped either way.
  • statewalker/umbrella mesh-auth-header: dated amendments to the design specs.

Verification

A clean pnpm -r run build, test, typecheck and lint:check of the httpeers + webrun-wire assembly all pass, with the same skips as main. Not yet checked against the live server.

🤖 Generated with Claude Code

mkotelnikov and others added 5 commits September 19, 2026 15:19
…ization

Authorization belongs to the application a mesh request is addressed to.
While the mesh kept its token there, the two collided: LiteLLM's Playground
(the OpenAI SDK) sends its own key as Authorization: Bearer sk-..., the
ServiceWorker edge would not overwrite a page's Authorization, so no mesh
token was attached and withAccess refused the LiteLLM key as
401 "malformed token" -- LiteLLM never saw the call.

- httpeers-core: MESH_TOKEN_HEADER (x-httpeers-token, bare token),
  setMeshToken/readMeshToken, and MESH_CREDENTIAL_HEADERS -- the mesh
  headers a request leaving the mesh must not carry.
- httpeers-access: withAccess reads only MESH_TOKEN_HEADER.
- httpeers-member: peerRequest and the edge write it; the edge attaches
  the token whatever the page put in Authorization, and still never
  overwrites a membership token the page set itself.
- httpeers-ghost: the pin writes the viewer's token there.
- hub llm passthrough: strips MESH_CREDENTIAL_HEADERS; any Bearer key in
  Authorization moves to x-litellm-api-key; Authorization never reaches
  LiteLLM.
- conformance: a source scan pins that no library touches Authorization
  and only httpeers-core spells the header name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…peers-token

- demos proxy page: its upstream strips MESH_CREDENTIAL_HEADERS. The
  proxy now forwards what it is not told to strip, so without this every
  visitor's membership token would reach whatever origin a route names.
  The builder moved to shared/proxy-upstream.ts so that is tested.
- llm-chat, LiteLLM config: the reasons given for x-litellm-api-key no
  longer mention a mesh token in Authorization; behaviour is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…lication's

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
0.2.0 is the release where urlUpstream forwards Authorization, which the
demos proxy and the hub passthrough now rely on. On 0.x, ^0.1.0 never
matches it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mkotelnikov
mkotelnikov merged commit db78ae5 into main Sep 19, 2026
3 checks passed
@mkotelnikov
mkotelnikov deleted the mesh-auth-header branch September 20, 2026 07:50
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.

1 participant