Skip to main content
The Zespan API runs behind a load balancer, reverse proxy, or CDN in every deployment shape, so it derives the caller’s address from the proxy chain rather than from the TCP socket. That derivation is only as honest as the edge in front of it.
Your edge must overwrite X-Forwarded-For, not append to it — and the API must not be reachable except through that edge. If a caller’s own X-Forwarded-For survives to the API, that caller chooses the address Zespan records and displays. What breaks is one specific control: the device line on the CLI approval screen.

How Zespan resolves the address

The API is configured to trust the whole proxy chain. For each request it takes the leftmost entry of X-Forwarded-For, falling back to the socket address when the header is absent. Three things read the result:
Zespan never reads X-Forwarded-For directly in application code, and the CLI cannot name its own address in the request body — the API stamps the resolved address itself. Neither of those helps if the header arrives already poisoned: a chain the edge did not sanitise is indistinguishable from a real one.

What to configure

The rule is the same everywhere: replace the inbound header with what the edge itself observed, and refuse traffic that bypasses the edge.
1

Make the edge overwrite X-Forwarded-For

Set the header to the connecting address the edge saw. Do not use a directive that appends to a client-supplied value.
2

Close the path around the edge

Bind the API to a private interface, or restrict it with a firewall or security group so the only route in is the proxy. An attacker who can open a TCP connection directly to the API supplies the entire chain, and no header configuration at the edge can help.
3

Verify it with a request, not by reading config

Send a request through your public edge carrying a bogus chain, and look at what Zespan recorded:
Open your dashboard’s /device screen, enter the user_code from the response, and press Continue. Read the Request came from row: it must show your address, not 203.0.113.9. Press Cancel this request when you are done — nothing is granted either way.
The audit log answers the same question after the fact: sign in through the edge with a bogus X-Forwarded-For and check the IP on the resulting entry under Platform → Audit. That value is truncated to a /24, so compare the first three octets.

What a forgeable IP costs you

Be precise about the exposure, because it is narrower than it first looks and sharper than “an IP might be wrong”. A device grant is phishable by construction. Someone can run zespan auth login on their own machine and read you the eight-character code over the phone. Everything else on the approval screen — the code you typed, the fact that you are signed in, your session — is exactly the same in that attack as in a legitimate login. The device line is the only part of the screen that differs, and the IP is the row an attacker cannot influence any other way: they control the CLI, so they control the reported OS and CLI version, but not the address the API observed. So a forgeable IP silently weakens the one screen defending the flow. It does not throw an error or look wrong — it shows a plausible address, which is worse than showing nothing, because the row exists to be trusted. An attacker who can reach the API directly can make the screen read as the victim’s own office. Nothing else in the flow depends on it. State this plainly to yourself before deciding how urgent this is:
  • Ownership — a grant can only be approved by the signed-in account that claimed it, checked server-side.
  • The claim step — the code must be entered by a human in a browser; the address is never part of that check.
  • The second factor — an account with an enrolled second factor must re-prove it within five minutes of approving, independently of any header.
  • Revocation — a revoked device is refused at approval and again at token exchange, keyed on its device key.
  • Attribution — the audit trail names the user, session, and device from server-side records, not from a header.
A poisoned IP also lets a caller sidestep the device-code rate limit by rotating the header, since the bucket is keyed on the same resolved address. That costs storage rather than access: the rows created are short-lived and swept on a timer.

Next steps

Environment variables

NEXT_PUBLIC_APP_URL and the rest of the server configuration.

zespan auth

The CLI sign-in this screen approves, and what the device line shows.

Security

Encryption, authentication, tenant isolation, and data controls.

Audit log

What Zespan records about every action, including the caller’s address.