skip to content

An image CDN accepts arbitrary width, height and quality parameters on a public transformation endpoint. How can that endpoint be abused, and what controls does a team put in front of it?

level: seniorimportance: nice to knowfreq 30%

answer

  1. the attacker picks the cache key
  2. every novel combination is a paid miss
  3. prove the URL came from you
  4. bound what a valid URL can ask

basics

~20 s

An open transformation endpoint lets anyone mint unlimited novel derivatives, each forcing an origin fetch and a CPU-heavy encode that you pay for. Teams close it with signed URLs, an allowlist of permitted transformations or named presets, dimension caps, and edge rate limiting.

solid answer

~60 s

The endpoint's usefulness is exactly its weakness: a stranger can vary parameters freely, and each novel combination is a guaranteed cache miss that costs an origin fetch plus a decode and re-encode. A script iterating widths from 1 to 4000 generates thousands of expensive misses, inflates your transformation bill, evicts the derivatives real users need, and can saturate the transform tier. If the source image URL is itself a parameter, it gets worse — the endpoint becomes an open proxy that will fetch and re-serve arbitrary remote content from your domain. The standard control is **signed URLs**: the server computes an HMAC over the path and parameters using a secret and appends it, and the edge rejects any request whose signature does not match, so only URLs your application emitted are honoured. Layer on top of that an allowlist of permitted transformations — ideally named presets rather than free-form numbers — a hard cap on output dimensions, restriction of source hosts, and rate limits at the edge. Then alarm on cache-miss rate, because a sudden spike is what abuse looks like from the outside.

code

javascript · 15 lines
javascript
const crypto = require('node:crypto');

const SECRET = process.env.IMAGE_SIGNING_SECRET || 'server-side-secret';

function signImageUrl(path, params) {
  const query = new URLSearchParams(params).toString();
  const signature = crypto
    .createHmac('sha256', SECRET)
    .update(`${path}?${query}`)
    .digest('hex')
    .slice(0, 16);
  return `${path}?${query}&s=${signature}`;
}

console.log(signImageUrl('/photos/hero.jpg', { width: 640, format: 'auto' }));

go deeper

for a junior

Know that an image URL which accepts any width lets strangers request work from your servers, and that teams sign those URLs so only their own links are accepted.

for a middle

Explain why each novel parameter combination is a guaranteed cache miss, and describe how an HMAC signature over the path and parameters is checked at the edge.

for a senior

Layer the controls and say what each one buys: signing for provenance, presets and caps for bounded cost, host allowlists against proxying, and miss-rate alarming for detection.

for a principal

Own the cost and blast-radius model — metered transformations as an availability risk, whether remote source URLs are supported at all, and how private media is authorized rather than merely signed.

## Why an open transform endpoint is a target A transformation endpoint is unusual among public URLs: each *distinct* request can force real computation. A normal static asset is cheap to request repeatedly because the second request is a cache hit. Here, an attacker chooses the cache key, so they can guarantee a miss every single time simply by picking a width nobody has used. That asymmetry — a trivial request producing an expensive server-side decode and re-encode — is the classic shape of a resource-exhaustion vector, and it is why this comes up in senior interviews as a cost and availability question rather than a data-breach one. ## Three abuse shapes **Derivative flooding.** A loop over widths, heights, and quality values mints thousands of unique keys. Consequences stack: your transformation bill grows (most vendors meter transforms), the derivative cache fills with junk that evicts the variants real visitors need, and the transform tier queues, so genuine misses get slower for everyone. **Open-proxy fetching.** Some pipelines accept the source image location as a parameter — a remote URL to fetch and transform. Unless the allowed hosts are restricted, the endpoint will fetch arbitrary URLs on your behalf and re-serve the result under your domain. That is a server-side request-forgery surface pointed at whatever the transform tier can reach, and it also means abusive third-party content can be laundered through your hostname. **Hotlinking.** Other sites embed your image URLs directly, so you serve their bandwidth and their transformations. Less dramatic, steadily expensive. ## Signed URLs The primary control is to make URLs unforgeable. The application computes a keyed hash over the path and the transformation parameters using a secret that only the server and the edge know, and appends it: ``` /photos/hero.jpg?width=640&format=auto&s=9f2c1ab7d4e05c68 ``` The edge recomputes the hash from the request and rejects anything that does not match. An attacker can still replay URLs your pages have published, but cannot invent new ones, which collapses the attack surface from "every parameter combination" to "the finite set you already generate". Two details matter in practice. First, the signature must be **stable for a given transformation** — if you fold a timestamp into it, every request is unique and you have destroyed your own cache. If you need expiry, put the expiry in the URL as its own parameter, sign that, and accept that the URL changes only when the expiry window rolls. Second, the signing secret has to stay server-side; a secret shipped to the browser so client code can build URLs is not a control at all. ## Bounding what is expressible Signing proves provenance; it does not bound cost. Complement it by shrinking what a valid URL can even ask for: - **Named presets** instead of free-form numbers — `?preset=card` rather than `?width=317&height=211&q=63`. This bounds the key space by construction and doubles as the cache-friendly ladder. - **Caps** on maximum output dimensions and on source file size, so no single request can ask for a gigantic decode. - **A source-host allowlist**, if remote source URLs are supported at all. Prefer not supporting them. - **Edge rate limiting** per client on cache misses specifically, since misses are the expensive path and legitimate traffic is dominated by hits. ## Detecting it Abuse is visible before the invoice arrives if you watch the right signal. The one that moves is **cache-miss rate on image requests**: normal traffic sits high on hits and boring; flooding drives misses toward 100% while total request volume may barely change. Alarm on that ratio, not on request count. Transform-tier queue depth and origin egress are useful corroborating signals. ## What these controls do not solve A signed URL is public once it appears in a page — anyone can copy and replay it, so signing is not access control for private images. Genuinely private media needs authorization on top: short-lived signed URLs issued per session, or an authenticated origin the CDN fetches from with its own credentials. Similarly, signing does nothing about hotlinking of URLs that are legitimately public; that needs referrer checks or per-session URLs, both of which trade cacheability for control. Being clear about which problem each control solves is most of what an interviewer is listening for here.

  • Why is folding a fresh timestamp into every URL signature a mistake?
    Because the signature is part of the cache key. A timestamp that changes per request makes every URL unique, so no derivative is ever reused and every visitor pays a full transform — you have turned a cache into a very expensive proxy. If you need expiry, sign a coarse expiry window that many requests share, so URLs stay stable for its duration.
  • Does signing image URLs make private images private?
    No. A signed URL is visible to anyone who can see the page and can be copied and replayed until it expires. Signing proves the URL was minted by your application; it does not authenticate the person requesting it. Private media needs real authorization — short-lived per-session URLs, or an authenticated origin that the CDN fetches with its own credentials.
  • Which single metric would you alarm on to catch transformation abuse early?
    Cache-miss ratio on image requests. Legitimate traffic is overwhelmingly hits, so a sustained climb toward all-misses means novel keys are being generated even if total request volume looks normal. Request count alone will not catch it, because flooding needs surprisingly little volume to be expensive.

saying these in an interview costs you the question

  • Says rate limiting alone is enough for a transform endpoint
  • Puts a fresh timestamp in every URL signature
  • Thinks a signed URL makes the image private
  • Ships the signing secret to client-side code

context