Signing and source concealment

Copy Markdown View Source

A signed URL proves that your application generated it, so the server rejects any URL a client edited or made up. A concealed source goes further and hides which image the URL points at. Both are optional, and both work the same for image_pipe_server and ImagePipe.Plug.

Purpose of signing

Without signing, anyone who can reach the server can request any size, crop, or effect of any image the server can read. Each new combination is a new image to fetch, process, and store in the cache. With signing keys configured, the server serves only the URLs your application generated, and answers every other request with 403 before it fetches the source or reads the cache.

What a signature covers

https://img.example.com/images/sig=<signature>/w=400/expires=1767225600/src/photos/beach.jpg

The signature is an HMAC-SHA256 of everything after the sig= segment: the options, the expiry, and the image path, byte for byte. Changing any of them gives 403 with the body invalid signature.

The hostname and the path the server is mounted at (/images here) are outside the signature. A URL signed for your CDN's address is also valid at the server's internal address, and moving the server to another path needs no new signatures.

The signature covers a preset's name, not its definition. When the server's card preset changes from 400 to 480 pixels wide, every signed preset=card URL stays valid and serves the wider image.

Expiry

A signed URL can carry an expires time, in Unix seconds. The server accepts the URL up to and including that second and answers 410 with the body expired after it, before fetching the source. The expiry is inside the signed part of the URL, so a client can't extend it. Without signing keys, the server still checks expires, but anyone can edit it.

An expiry is part of the URL, so a URL generated with a new expiry on every page view is a new URL every time, and browsers and CDNs can't reuse their cached copy.

Signing keys

A signing key is a secret, written as hex. The server and the URL builder each hold a list of keys. The builder signs with the first key in its list, and the server accepts a signature made with any key in its list.

The list is what makes it possible to replace a key without breaking URLs already in use: the server accepts the old and the new key while URLs signed with either are still requested. Signing URLs and rotating keys gives the steps.

Anyone holding a key can sign any URL, which is why URLs are generated in server code and the browser receives only the finished URL.

Source concealment

A concealed source replaces src/photos/beach.jpg with an encrypted token:

/images/sig=XzyslPgT…/w=400/enc/Aa_ELvrY…

The token hides the whole source, including a private hostname, the path, and credentials in its query string. Watermark sources in the same URL are encrypted too. The token is encrypted with source encryption keys, which are separate from the signing keys, and concealed URLs are always signed. Shared URL settings shows how to configure the keys on both sides.

Deterministic and random tokens

By default, the same source and encryption key always give the same token, whatever the processing options, and in any process. A URL for beach.jpg at w=400 and one at w=400/-/blur=2 carry the same enc/ token. Generating the same URL again gives the same string, so browsers and CDNs keep reusing their cached copy.

In random mode (the URL builder's iv_mode option, see source encryption keys), each URL gets a new token. Random tokens hide that two URLs show the same image, at the cost of a new URL each time for browsers and CDNs. The server caches the processed image by its decrypted source, so both modes share the server's stored copies and ETags with each other and with the same source written as src/.

What a token reveals

  • A token's length shows the source's length to within 16 bytes.
  • Deterministic tokens show which URLs have the same source.

The token protects the source only while nobody else can encrypt with your keys, so no endpoint may encrypt any source it's given. With such an endpoint, someone can encrypt a guessed source and compare the token with a URL they've seen.