How Zespan resolves the address
The API is configured to trust the whole proxy chain. For each request it takes the leftmost entry ofX-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.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 runzespan 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.
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.

