Skip to content

fix(server): add TRUST_PROXY so the client address survives a proxy - #149

Merged
Bccorb merged 1 commit into
mainfrom
fix/auth-jwks-public-keys-and-trust-proxy
Aug 3, 2026
Merged

fix(server): add TRUST_PROXY so the client address survives a proxy#149
Bccorb merged 1 commit into
mainfrom
fix/auth-jwks-public-keys-and-trust-proxy

Conversation

@Bccorb

@Bccorb Bccorb commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Problem

Express leaves trust proxy off by default and nothing in this repo ever set it, so behind a load balancer req.ip resolved to the balancer rather than the caller. Two consequences:

  • Every client shared one bucket in the global rate limiter and the slow-down middleware. A single busy caller could throttle everyone on the deployment.
  • express-rate-limit logged an ERR_ERL_UNEXPECTED_X_FORWARDED_FOR validation error on each request.

The existing applyTrustedClientIp middleware only covers requests a trusted server adapter proxies with a signed x-seamless-client-ip header. Browsers reaching the API directly through a load balancer were never attributed.

Changes

Add an optional TRUST_PROXY env read in src/app.ts. It accepts the number of proxies in front of the server, and also the loopback and IP/CIDR allowlist forms Express supports. Numeric strings are converted so Express gets a hop count rather than a string.

It stays unset by default rather than defaulting on in production. A directly reachable server that trusts X-Forwarded-For lets a client forge its own address and pick its rate-limit bucket, so this has to be an operator decision about the deployment topology, not a default. Deployments that are not behind a proxy are unaffected either way.

applyTrustedClientIp still wins where it applies: it assigns req.ip with Object.defineProperty on the request instance, which shadows the prototype getter Express uses.

Changes included

  • src/app.ts: read and apply the setting
  • .env.example: commented entry under SERVER
  • docs/configuration.md: row in the Application table
  • tests/unit/app.spec.ts: three cases
  • changeset (patch)

Verification

  • npm run typecheck: clean
  • npm run lint: clean
  • npm run format:check: clean
  • npm run test:run: 966 passed, 1 skipped, 91 files

New coverage: unset leaves trust proxy at false; a numeric value resolves req.ip to the X-Forwarded-For address through a real request; a non-numeric value passes through to Express unchanged. Each case re-imports the module, since the setting is applied while the module body runs. Coverage stays at 99%.

Consumer note

fells-code/seamless-review-infra#8 already sets TRUST_PROXY=1 on its auth task, where the shared ALB is the only hop. That variable is inert until this lands and the image is bumped.

Express leaves trust proxy off by default, so behind a load balancer req.ip resolved
to the balancer rather than the caller. Every client shared one bucket in the global
rate limiter and the slow-down middleware, and express-rate-limit logged an
ERR_ERL_UNEXPECTED_X_FORWARDED_FOR validation error on each request. The existing
applyTrustedClientIp middleware only covers requests a trusted server adapter proxies
with a signed x-seamless-client-ip header, so browsers reaching the API directly were
never attributed.

TRUST_PROXY accepts the number of proxies in front of the server, and also the
loopback and IP/CIDR allowlist forms Express supports. It stays unset by default
because a directly reachable server that trusts the header lets a client forge its
own address and choose its rate-limit bucket.
@Bccorb
Bccorb merged commit 7b8b6c2 into main Aug 3, 2026
2 checks 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.

1 participant