‹ Build a Screener Lesson 10 of 17
Contents Lesson 10 of 17

5 min read · professional

Reviewing for leaks

Lens one, on real screener code. Here is a proxy route an assistant produced for "let the page pass screener filters through".

export async function GET(req: NextRequest) {
  const incoming = new URL(req.url).searchParams;
  const upstream = new URL("https://eodhd.com/api/screener");

  for (const [k, v] of incoming.entries()) {
    upstream.searchParams.set(k, v);
  }
  upstream.searchParams.set("api_token", process.env.EODHD_API_KEY!);

  const res = await fetch(upstream);
  return Response.json(await res.json());
}

It works. It is also three separate leaks. Find them before reading on.

Leak one: the caller sets the token

The loop copies every parameter, including api_token. A caller who sends one has theirs overwritten by the server's on the next line, so that specific ordering saves you here — but reorder those two lines during a refactor and your proxy starts making requests with a stranger's token, or worse, with a token they control.

Course 1's proxy dropped api_token explicitly. This one relies on line order. Explicit beats lucky:

if (k.toLowerCase() === "api_token") continue;

Leak two: the caller sets the limit

Nothing clamps limit. limit=500 was verified to return 500 rows in one call, and a loop of those requests spends your daily allowance in minutes. Your quota is a resource a stranger can consume through a public URL, which makes this a denial-of-service against yourself.

Leak three, and this is the one people miss

The response is forwarded verbatim: Response.json(await res.json()).

Today that is screener rows and harmless. But the same pattern applied to /user forwards the account's email, payment method and invite token — which is precisely the mistake course 1 warned about, arriving here in a different costume. A proxy that reflects upstream responses unexamined is a proxy that will one day reflect something private.

Return what the page needs, not what the upstream said.

And a fourth thing that is not a leak but will bite

process.env.EODHD_API_KEY! — the non-null assertion. With the variable unset, the request goes out with api_token=undefined, the API rejects it, and your page shows an empty screen. The person debugging it has no idea the key is missing, because nothing said so. Course 1 threw a named error for exactly this.

What the review comment looks like

Three things. The parameter loop copies api_token, so this only works because the server's set happens to run afterwards — drop it explicitly instead of relying on line order. limit is unclamped and a stranger can spend my quota through the public URL, so clamp it here rather than in the page. And the response is forwarded verbatim; return only the fields the table needs, because this pattern applied to another endpoint forwards an email address. Separately, the ! on the env var turns a missing key into a blank page rather than a message.

Four sentences, each naming a consequence. That is the standard from lesson 1 of this unit, applied.

Try it now

Open your own screener proxy and check it against these four. Then try them from outside: curl your deployed URL with api_token=fake, with limit=99999, and with the key removed from the environment. Three attacks, three specific behaviours you should be able to predict before you run them.