Skip to content

fix(proxy): use Header.Set instead of Header.Add to overwrite backend request headers#240

Open
wthrajat wants to merge 1 commit into
lightninglabs:masterfrom
wthrajat:fix-headers
Open

fix(proxy): use Header.Set instead of Header.Add to overwrite backend request headers#240
wthrajat wants to merge 1 commit into
lightninglabs:masterfrom
wthrajat:fix-headers

Conversation

@wthrajat
Copy link
Copy Markdown

@wthrajat wthrajat commented May 24, 2026

description

Aperture allows configuring custom backend headers under the services section of aperture.yaml. For example, setting the upstream authentication credential:

services:
  - name: "my-service"
    address: "api.openai.com"
    protocol: "https"
    headers:
      Authorization: "Bearer sk-proj-..."

Currently, in proxy/proxy.go, when Aperture forwards a client request to the target backend, it appends these configured headers using req.Header.Add(name, value).

If a client sends a request containing credentials (such as an Authorization: L402 ... or Authorization: LSAT ... header), the outgoing request forwarded to the target backend will accumulate both values. It contains:

Authorization: L402 <macaroon>:<preimage>
Authorization: Bearer sk-proj-...

issue

Many strict API gateways and WAFs (such as Cloudflare protecting api.openai.com for example) will reject HTTP requests containing multiple conflicting Authorization header values, immediately dropping the request with a 400 Bad Request.

steps to reproduce

  1. Start Aperture configured to proxy to an upstream host (e.g. api.openai.com) with a configured Authorization: Bearer <key> header.
  2. Send an unauthenticated client request to the proxy. Aperture will return a 402 Payment Required challenge.
  3. Pay the invoice and retry the request including the L402 credentials:
curl -i -X POST http://localhost:8081/v1/chat/completions \
  -H "Authorization: L402 <macaroon>:<preimage>" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"ping"}]}'
  1. Expected (with patch): Aperture forwards the request with a single Authorization: Bearer <key> header. The upstream server returns 200 OK.
  2. Actual (unpatched): Aperture appends the configured header, sending multiple Authorization headers. The upstream server (Cloudflare in rhis case) blocks the request and returns 400 Bad Request.

fix

We can use req.Header.Set(name, value) instead.
This correctly replaces/overwrites any client side credentials with the configured upstream credentials before the request leaves the gateway, matching the comment's stated intent: "Now overwrite header fields of the client request with the fields from the configuration file."


I was working on a small project to understand L402 better when I came across this issue. As a workaround, I'm using this script as of now https://github.com/wthrajat/autocommit-l402/blob/main/aperture_sanitizer.go which does the job.

If not this workaround, then re-compiling aperture using this branch wthrajat:fix-headers fixes the issue.

@wthrajat
Copy link
Copy Markdown
Author

From the official docs:

  1. add(): https://cs.opensource.google/go/go/+/refs/tags/go1.26.3:src/net/http/header.go;l=30
// Add adds the key, value pair to the header.
// It appends to any existing values associated with key.
// The key is case insensitive; it is canonicalized by
// [CanonicalHeaderKey].
func (h Header) Add(key, value string) {
	textproto.MIMEHeader(h).Add(key, value)
}
  1. set(): https://cs.opensource.google/go/go/+/refs/tags/go1.26.3:src/net/http/header.go;l=39
// Set sets the header entries associated with key to the
// single element value. It replaces any existing values
// associated with key. The key is case insensitive; it is
// canonicalized by [textproto.CanonicalMIMEHeaderKey].
// To use non-canonical keys, assign to the map directly.
func (h Header) Set(key, value string) {
	textproto.MIMEHeader(h).Set(key, value)
}

The comment here https://github.com/lightninglabs/aperture/blob/master/proxy/proxy.go#L540-L541 says

// Now overwrite header fields of the client request
// with the fields from the configuration file.

to overwrite the header field, so we should use req.Header.Set() instead of req.Header.Add()

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