Map the API

Draw API trust boundaries

Trace identity, data, and authority across clients, gateways, services, queues, databases, caches, and third-party APIs.

Internal traffic is not trusted by default. A compromised service or a leaked worker token is still an attacker path. “It sits behind the load balancer” is not an authorization policy.

A trust boundary is any point where a request moves from one component to another and the receiving side has to decide what to believe. Let’s find them.

Trace one request end to end

Follow an invoice request through every hop. At each one, mark three things: where authentication changes form, where an authorization decision happens, and where data leaves your control.

browser --TLS--> gateway --mTLS--> invoices-service ----> postgres
  |                 |                    |
cookie        verifies JWT,       trusts X-User-Id
              adds X-User-Id      from the gateway only

Write down every credential a platform or proxy adds along the way. Cloud metadata tokens, service accounts, forwarded headers. Then validate each boundary on its own terms.

Queues and caches are boundaries too. A message a worker consumes carries authority. A cached response served to another user carries authority. At every hop, ask the same question: who proved this identity?

The forged header problem

Here’s the classic bug. The gateway authenticates the user and adds an X-User-Id header. The service reads it. Fine so far.

But what if the service also accepts that header from anyone who can reach it directly? Then a caller picks any identity they like:

# bypassing the gateway, talking to the service directly
curl -i http://invoices-service.internal:8080/invoices \
  -H "X-User-Id: 42"
# HTTP/1.1 401 Unauthorized   <- what you want
# HTTP/1.1 200 OK             <- identity spoofing, one header away

The fix is structural, not a patch. The service accepts identity headers only over the mutually authenticated connection from the gateway. On any other path it rejects them. Being able to reach a service on the network must never count as proof of who you are.

Document header ownership

For every security header, write down which component may create it, replace it, or remove it. A short table is enough:

X-User-Id        created by gateway, stripped if present on ingress
X-Forwarded-For  appended by gateway, never trusted from the client
Authorization    verified by gateway, not forwarded downstream

Now the direct-service test above has a rule to check against. Without the table, nobody can say whether a header arriving at the service is a platform feature or an attack in progress. I’ve watched teams argue about that for an hour during an incident. Write it down while it’s calm.

Try this on your own system: trace one invoice request from the client to the database and label who creates each identity value. Then, in a test environment, bypass the gateway and send a forged identity header straight to the service. Prove it can’t authorize the request.

Lesson completed