In this episode we bring ImageMagick into communication with the outside world: reading and writing images via URLs, file lists with the @ notation, the SSRF risk and how to mitigate it, and reading raw pixels with the stream command for data pipeline integration.

In episode 14 you learned to treat input as a potential enemy. This episode puts that principle to its most serious test: ImageMagick can read and write directly to URLs — meaning it can be pulled out into the world by anyone who controls its input.
Episode 15 opens up ImageMagick's remote I/O capabilities: reading images from https://, writing to remote destinations, reading file lists with the @ notation, and streaming raw pixels via magick stream. Along with that, we face the most characteristic security risk of this capability — SSRF — and put together mitigations that make this capability safe to use on a server.
ImageMagick doesn't care whether the source is a local file or a URL — the HTTPS (and HTTP) coder allows both:
magick https://cdn.example.com/banner.png -resize 800x out.jpgmagick out.jpg https://cdn.example.com/upload/tmp_42.jpgBoth commands above look reasonable, but both hide a big decision: who actually makes the HTTP request? Not you — ImageMagick does, on behalf of the file you named. This isn't a problem when the input comes from you. It becomes a big problem when the input comes from a user, because the user can point ImageMagick anywhere.
The right analogy: reading a URL is like telling an assistant to deliver a package. As long as you determine the destination, it's safe. But if visitors can tell your assistant to go anywhere, then that assistant — with your server's address and privileges — roams the world on the visitors' behalf.
SSRF (Server-Side Request Forgery) is an attack where the attacker makes the server send a request to a target the server shouldn't reach. With ImageMagick's URL capability, a single malicious image file can trigger requests to:
http://169.254.169.254/ — the cloud metadata service (AWS/GCP), where secret access tokens live.http://127.0.0.1:6379/ — Redis on the same machine, without authentication.http://admin:pass@internal.example.com/ — an internal panel behind the firewall.All from a single image upload. This is why the policy.xml in episodes 13 and 14 disables the HTTPS and URL coders — they are direct SSRF gateways.
Layered mitigations for servers processing user input:
1. Disable remote coders in policy:
<policy domain="coder" rights="none" pattern="HTTPS"/>
<policy domain="coder" rights="none" pattern="HTTP"/>
<policy domain="coder" rights="none" pattern="URL"/>
<policy domain="coder" rights="none" pattern="FTP"/>2. Download in a separate layer. Don't let ImageMagick touch the network. The application process (Node, Python, Go) doing the download has far better control — timeout, maximum size, domain allowlist:
curl --max-filesize 5M --connect-timeout 5 -o /tmp/source.jpg \
"$USER_URL"
magick /tmp/source.jpg -resize 800x out.jpgHere curl does validation first: file size is limited by --max-filesize, the connection is bounded by a timeout, and the file lands on local disk before ImageMagick touches it. ImageMagick only ever sees a local file — the network is never within its reach.
3. Allowlist destination domains. If remote downloading is truly necessary, don't allow free URLs — match the domain against an allowed list, and reject private IPs (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, and cloud metadata) before the request is sent.
Warning
Don't rely on checking IPs after DNS resolution in a naive way — attackers can use DNS rebinding that answers with a public address when checked and an internal address when accessed. Download in a separate layer (service or container) that has no access to the internal network at all.
ImageMagick also reads lists of file names from a text file using the @ notation:
banner.png
hero.jpg
icon.webpmagick @filelist.txt +append combined.jpg@filelist.txt tells ImageMagick "read the file list from filelist.txt". Two important warnings: the paths inside the list can be relative or absolute, and — often forgotten — the combination of @ with user input can open the way to other files. Always sanitize, and never let a file list come from unknown input without validation.
Sometimes you don't need an image file — you need raw pixels for another program to process. That's where magick stream, ImageMagick's built-in tool, comes in. It reads pixels from an image and writes them as raw data without any file structure:
magick stream -map rgb -depth 8 input.jpg output.rgbRead the command above: -map rgb selects the three red-green-blue channels, -depth 8 sets 8 bits per channel. The result is a binary file where every 3 bytes is one R G B pixel. No header, no metadata — just data.
Why is this useful? Because the raw format is the easiest common language for data pipelines. You can read a pixel stream from Python without any image library:
import struct
width, height = 800, 600
with open("output.rgb", "rb") as f:
raw = f.read()
for y in range(0, 3):
for x in range(0, 3):
r, g, b = struct.unpack_from("BBB", raw, (y * width + x) * 3)
print(f"pixel({x},{y}) = R{r} G{g} B{b}")Notice: no import PIL or decode library — the pixels are already in the simplest format that can be read directly. That's the power of streaming: ImageMagick handles the decode and conversion, and you just read the bytes.
magick stream doesn't always have to read the whole image. The combination of -extract with coordinates lets you grab a specific region:
magick stream -extract 100x100+50+50 -map rgb -depth 8 input.jpg region.rgb-extract 100x100+50+50 limits the read to a 100x100 pixel area starting at coordinates (50,50). This is valuable for image analysis only interested in one part — for example reading an already-detected face without scanning the entire frame.
A complete pattern you'll often use in production: download → validate → stream → process. Each stage has clear ownership:
curl -s --max-filesize 5M -o /tmp/in.jpg "$SOURCE_URL"
file --brief --mime-type /tmp/in.jpg
magick stream -extract 200x200+0+0 -map rgb -depth 8 \
/tmp/in.jpg /tmp/region.rgb
python3 /tmp/process.py /tmp/region.rgbThe flow above divides responsibilities: curl downloads with limits, file validates the type, magick stream extracts raw pixels, and python3 processes the data. ImageMagick never sees the network, and the pipeline never sees image file structure — each layer handles what it's best at.
Tip
For very large image files, pixel streaming is far more memory-efficient than magick input.jpg -format %[pixel:...]. magick stream reads and writes data streams sequentially — it can process images too large to open fully in memory.
Episode 15 brought ImageMagick out of local file isolation: reading and writing via https:// URLs, opening file lists with the @ notation, and streaming raw pixels with magick stream for data pipeline integration. On the other side, we faced the accompanying risk — SSRF — and put together defenses: disabling remote coders in policy, downloading in a separate layer, and never letting ImageMagick touch the network on behalf of user input.
The core thing to remember: remote capability is a security decision, not a convenience. Letting ImageMagick read URLs means handing the request target choice to whoever controls the input — and on a server, that's the same as handing over the keys to the internal network.
In episode 16 we move from external dangers to a subtler problem: privacy and metadata — how GPS location trails and author names ride along in image files, and how -strip and output cleaning keep data where it belongs. See you then!