skip to content

A credential endpoint answers only requests carrying a token obtained by a prior write — which property of a forged fetch does that exploit?

level: seniorimportance: should knowfreq 40%

answer

  1. the attacker supplies a URL, nothing else
  2. two steps instead of one
  3. a write first, then the read
  4. the reply must be carried forward
  5. one-way fetch cannot complete an exchange

basics

~10 s

That a forged fetch is one-shot and shape-constrained. The attacker controls a URL, not the method and not the headers, and cannot carry a value returned by one request into the next one.

solid answer

~40 s

The hardened path turns a single read into a two-step exchange: a write-shaped request returns a short-lived token for talking to the endpoint, and the credential read is refused unless it carries that token in a header. A typical forging primitive supplies only a URL — it cannot choose the method, cannot add a header of its own, and above all cannot feed one response into the next request. So the defence does not try to tell a good caller from a bad one; it requires a capability the forging bug does not have. What it does not stop is an attacker running code on the machine, a richer primitive that controls method and headers, or an endpoint that still answers the older unauthenticated read path alongside the hardened one.

code

pseudocode · 10 lines
pseudocode
# Step 1 - a write-shaped request obtains a short-lived endpoint token
token = httpPut(localCredentialEndpoint + "/session-token",
                headers: { requestedTokenSeconds: 300 })

# Step 2 - the credential read is accepted only with that token
credential = httpGet(localCredentialEndpoint + "/credentials",
                     headers: { endpointSessionToken: token })

# What a forged fetch can issue - refused, no token presented
httpGet(localCredentialEndpoint + "/credentials")   # -> rejected

go deeper

for a junior

Remember the shape: reading the credential takes two steps, a write that returns a token and a read that must carry it, so a plain one-step fetch gets nothing.

for a middle

Explain the mechanism: the defence tests a capability rather than an identity, and a URL-only forging bug lacks method control, header control and the ability to carry a value between requests.

for a senior

Show the rollout judgment: the hardened path must be required rather than merely available, and requiring it breaks old libraries and unowned tooling, so you inventory the callers before you flip it.

for a principal

Argue where this sits among defences. It closes one primitive cleanly and does nothing about a foothold, so it belongs in a stack with a narrow attached identity and machines that do not answer at all.

## What a forged fetch can and cannot do A request-forging bug hands an attacker one thing: influence over a URL your server will fetch. It rarely hands them more. In the common shape — a link preview, a webhook tester, an importer — they control the scheme, host, port and path. They do not control the HTTP method; they cannot add a header of their choosing; they can read the response only if the feature reflects it somewhere; and, critically, they **cannot use the result of one request as the input to the next**. The fetch is a single shot in one direction. The handshake is designed around exactly that constraint. ## The handshake 1. The workload sends a **write-shaped** request to the endpoint — a method an ordinary fetcher never issues — asking for an endpoint session token, optionally with a requested lifetime. 2. The endpoint returns a short-lived token that is good only for talking to this endpoint on this machine. 3. The workload reads the credential path again, this time carrying that token in a header. 4. A read without the token is refused. Keep two tokens apart here, because the vocabulary collides. The token the handshake issues is a **ticket for the endpoint**; it is not a platform credential and cannot sign anything. The credential set the endpoint eventually returns contains a session token of its own, which is part of what platform calls are signed with. Different values, different audiences, different lifetimes. ## Why that shape defeats the forgery | | A forged fetch | Real code on the machine | |---|---|---| | Chooses the URL | yes | yes | | Chooses the method | no | yes | | Sets an arbitrary header | no | yes | | Reads the response | only if reflected | yes | | Carries a value between requests | no | yes | Three things follow from that table: - The defence is a **capability test**, not an identity test — it never asks who is calling. - Choosing a cleverer URL cannot help, because the missing capabilities are not expressible in a URL. - It degrades gracefully: a primitive that gains one of those capabilities weakens the defence rather than removing it, because it still needs the others. ## What it does not stop - **Code execution on the machine.** An attacker who can run a process performs the handshake exactly as your service does. This defence is aimed at forgery, not at a foothold. - **A richer forging primitive.** A misconfigured forwarding proxy, a client that follows an attacker-steered redirect into an odd method, or a request-smuggling path can restore method or header control, and the handshake comes back into reach. - **A credential that has already left.** Once the values are out, the handshake is irrelevant. It protects the door, not the key that walked through it. - **An endpoint that still answers the old way.** This is the failure worth naming: if the older unauthenticated read path remains *available* beside the hardened one, an attacker simply uses the path that answers, and the hardening is decorative. It has to be *required*, not merely offered. ## Making it actually required Requiring it is one setting per machine and a real inventory problem, because anything that reads the endpoint without the handshake stops working: - old client libraries that predate the hardened path - hand-rolled scripts that fetch the credential path directly - images and tooling nobody owns any more The safe order is: make the hardened path available, update the clients, watch for callers still using the old path, and only then require it. Doing it the other way round is how the change gets reverted during an outage and never attempted again. ## A note on the token's own lifetime The endpoint token has a short lifetime of its own, requested by the caller and capped by the endpoint. A longer one means fewer handshakes; a shorter one narrows the window in which a leaked **endpoint** token would help anyone. It is a minor knob next to whether the handshake is required at all, and saying that out loud is better than treating it as the interesting part of the design.

  • Is the token the handshake returns the same thing as the session token inside the credential set?
    No, and conflating them is common. The handshake token is a ticket for talking to the local endpoint on that machine; it signs nothing and the platform has never heard of it. The session token inside the returned credential set is part of what platform API calls are signed with. They have different audiences, different lifetimes and different consequences if leaked.
  • Does requiring the handshake help against an attacker who already has a shell on the machine?
    Barely. Code running on the machine can issue the write, read the token and present it, exactly as the client library does. The handshake targets a specific weakness of forging bugs — one-way, URL-only requests — so against a real foothold you are relying on other things entirely: how narrow the attached identity's permissions are, and whether the endpoint answers on that machine at all.

A door that opens only after you knock in a pattern the doorman has just told you. Someone shouting a single sentence through the letterbox can deliver a message but can never complete the exchange.

saying these in an interview costs you the question

  • Thinks the handshake token is itself a platform credential
  • Believes it stops an attacker with code execution on the box
  • Says enabling it is enough while the old path still answers
  • Confuses the endpoint token with the credential's session token
  • Assumes it protects a credential that has already been stolen