Dev News Daily ENDE

Your dependency list does not contain the code most likely to be attacked

Every team I have worked with can produce a dependency list in about four seconds. npm ls, mvn dependency:tree, pip freeze, the SBOM out of the build. It is a real artefact and it answers a real question: what did we choose to depend on?

It does not answer the question attackers are asking, which is: what code will run if I send you this file?

The two lists are not the same list

A service that accepts an image upload and makes a thumbnail typically reaches, in order: a web framework, an image wrapper such as ImageMagick, libvips or Sharp, and then a native C or C++ decoder chosen by the bytes — not by the extension, not by the content-type header, and certainly not by the manifest. For HEIC and AVIF that decoder is usually libheif, sitting on libde265.

Nobody in that chain necessarily decided to support HEIC. The wrapper supports what it was compiled against, the container base image ships what the distribution packaged, and the distribution packages what the format ecosystem produced. The application's manifest lists the wrapper. The decoder is two layers below the lowest line in it.

This week's HEIF Heist disclosure is the current example, but the pattern is not new: ImageTragick, ForcedEntry and the libwebp flaw had the same shape. A small native library, embedded nearly everywhere, reached by untrusted bytes, running in the same process as your session tokens.

Your dependency list does not contain the code most likely to be attacked
Your dependency list does not contain the code most likely to be attacked — Dev News Daily

Four questions that find it

1. What can reach a parser, and which parser? For every place your system accepts bytes it did not generate — uploads, avatars, email attachments, imported documents, scraped pages, QR codes, uploaded fonts — name the library that will actually decode them. If the answer is "whatever ImageMagick picks", that is the finding.

2. What does the container actually link against? Not what the Dockerfile installs: what the resulting image contains. ldd on the binaries, the distribution's package list inside the image, and the base image's own manifest. Most teams have never looked, and the answer usually includes decoders for formats the product does not support.

3. Can an outsider tell which version you run? Format-probing to fingerprint a decoder version is what turns a memory-safety bug into a targeted exploit: the attacker learns which payload to send before sending it. Differences in error messages, timing and accepted edge cases all leak this. You cannot fully prevent it, but you can stop returning decoder errors verbatim.

4. What is the blast radius of a decode? This is the only question with a durable answer. A decoder that runs in the request process, as the service user, with the network and the secrets available, converts one memory bug into total compromise. The same bug in a separate process with no network, a dropped privilege set, a memory cap and a wall-clock timeout is a crash and a 500.

The uncomfortable part

The defences that work here are the ones that do not appear in a dependency report and cannot be bought: decode out of process, cap dimensions and memory before allocating, set a timeout, drop privileges, and re-encode to a single canonical format at the boundary so that everything downstream handles bytes you produced.

None of that shows up as a green tick in a scanner. The scanner reads the manifest — and the manifest describes what you chose, in a part of the stack where the choosing was done for you.