Join our Newsletter — 33% off our NHI Course
Home› Guides› Image Processing Security Guide: ImageMagick, libvips and Pillow…
Guide Application Security

Image Processing Security Guide: ImageMagick, libvips and Pillow Hardening

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 14 November 2026 15 min read
On this page

Every thumbnail, avatar and resize endpoint feeds attacker-controlled bytes into a native-code parser that supports far more formats than the application ever needs. Image libraries are built for breadth: a single call can reach dozens of decoders, external helper programs such as Ghostscript, and in some configurations URL or script coders. That breadth is the attack surface for remote code execution, server-side request forgery (SSRF), local file reads and memory exhaustion. This guide covers how to reduce it in the three libraries most server-side pipelines use: ImageMagick, libvips and Pillow (listed alphabetically; order is not a ranking). It is for application security engineers, platform teams and developers who own image upload or transformation services. Upload validation and the wider media pipeline are covered in the Secure File Upload Handling Guide and the FFmpeg and Media Processing Security Guide; this page goes deeper on the image libraries themselves.

Key takeaways

  • Decide the formats first. Most services need three or four formats; every other decoder is unused attack surface that each library lets you switch off.
  • Bound pixels, not bytes. A small compressed file can expand into gigabytes of pixels, so cap width, height and total pixel count before decoding the full image.
  • External helpers are part of the parser. Ghostscript, delegate programs and script coders run with the worker's rights; block them unless a feature truly needs them.
  • Verify the policy that is actually loaded. Packaged defaults differ between distributions and container bases, so test the running configuration rather than the file you wrote.
  • Sandbox anyway. Format and resource limits shrink what the library will attempt; isolation limits what a compromised decoder can reach.

Why image libraries are a distinct risk

Image decoding combines three properties attackers value: it is reachable from the internet through upload or URL-fetch features, it is written largely in C or C++ for speed, and it chooses a decoder from the file's own content. That last point means the extension and the Content-Type header do not decide which code runs. The OWASP File Upload Cheat Sheet notes that the Content-Type header is trivial to spoof and that signature checks alone are easy to bypass, and it recommends processing images with an up-to-date library using restricted formats, resource limits and sandboxing.

ThreatHow it arises in image processingPrimary control
Remote code executionA memory-safety bug in a rarely used decoder, or a delegate or script coder that invokes another programFormat allowlist, delegates off, sandbox, prompt patching
SSRF and remote fetchURL-capable coders or formats such as SVG and MSL that reference external resourcesBlock URL modules and vector or script coders on read; no network egress
Local file readIndirect reads (ImageMagick @file syntax), path references inside vector formats, or text codersPath policies, read-only filesystem, write-only rights for text and vector coders
Denial of serviceDecompression bombs, huge dimensions, long animation sequences, progressive JPEGs held fully in memoryPixel and dimension caps, sequence limits, time limits, cgroup limits
Data leakageEXIF and other metadata (including GPS coordinates) passed through to public copiesRe-encode and strip metadata on output

MITRE ATT&CK tracks the entry path as T1190 Exploit Public-Facing Application and the resource-exhaustion outcome as T1499.004 Application or System Exploitation; CAPEC-664 describes the SSRF pattern.

Choosing a library: architecture and trade-offs

AspectImageMagicklibvipsPillow
Language and useC library and command-line suite (magick); bindings in many languagesC library with a demand-driven, streaming pipeline; bindings include pyvips and sharp for Node.jsPython imaging library with C extensions
Main security controlpolicy.xml security policy with resource, path, module, coder, delegate, filter and system domainsRuntime operation blocking: VIPS_BLOCK_UNTRUSTED, vips_block_untrusted_set() and vips_operation_block_set()Image.open() formats argument, MAX_IMAGE_PIXELS decompression bomb limits, plugin-level settings
External programsDelegates (for example Ghostscript for PostScript and PDF) configured in delegates.xml and governed by the delegate policy domainThe vips_system operation can spawn command-line tools; keep it blocked for untrusted workEPS reading can call the Ghostscript gs executable; EpsImagePlugin.gs_binary = False turns that off
Memory modelPixel cache in memory, memory map or disk, bounded by resource policyProcesses images in small regions; sequential access and thumbnail-on-load keep memory lowLoads image data into memory; size limits matter for large inputs
Project security policyImageMagick security policylibvips security policy, with announcements on its GitHub security advisories pagePillow security policy, with a threat model in the Pillow security handbook

Decision guidance: pick the library your stack already uses and harden it, rather than switching for security alone. Where you are choosing fresh for a high-volume thumbnail service, libvips's streaming design keeps memory per job low, which makes resource limits easier to set. ImageMagick suits pipelines that need its breadth of operations and command-line tooling, provided the policy is locked down. Pillow fits Python services handling modest image sizes where keeping the dependency tree small matters. Whatever the choice, the controls below apply.

Hardening ImageMagick

ImageMagick's security policy documentation describes the default policy as open, suited to controlled, sandboxed environments, and asks operators handling untrusted input to apply a stricter one. The last matching policy wins, so broad denials go first and exceptions after, and paths are checked in canonical form to stop symlink and traversal bypasses. From version 7.1.1-16 four predefined policies can be selected at build time with --with-security-policy: open, limited, secure and websafe. Confirm what is loaded with magick identify -list policy.

The secure and websafe policy files in the ImageMagick repository (checked November 2026) show the settings the project itself considers appropriate for untrusted images:

Policy settingwebsafe filesecure fileWhat it does
resource time30120Seconds before processing is aborted
resource memory512MiB768MiBHeap available for pixel cache
resource area / width / height32MP / 8KP / 8KP32MP / 8KP / 8KPRejects pixel floods and oversized dimensions
resource list-length1632Caps frames in a sequence such as an animated GIF
delegate and filterrights none, pattern *rights none, pattern *No external delegate programs or loadable filters
modulenone for {MSL,URL,VIDEO}none for {MSL,URL,VIDEO}Removes scripting, URL fetching and video handling
coderwrite only for {MVG,PDF,PS,SVG,TXT,XPS}write only for {MVG,PDF,PS,SVG,TXT,XPS}Can still produce these formats, never reads them from users
pathdeny -, @*, fd:, pipes, /etc, /proc, /sysdeny -, @*, fd:, pipes, /etc, traversalBlocks stdin, indirect @file reads, descriptors and sensitive paths
system filename:literaltruetrueTreats % in filenames as text, not format placeholders
  • Add an explicit format allowlist: the documentation describes websafe as limiting input to GIF, JPEG and PNG, while the current websafe file notes that it does not itself impose a format allowlist. Do not rely on either: add a module rule that denies everything, followed by a rule allowing only your formats, and check the result with identify -list policy.
  • Pin the decoder: pass input as an explicit format prefix (for example png:upload) after your own signature check, so content sniffing cannot select a different coder.
  • Older versions: before 7.1.1-16 glob patterns were case-sensitive, so coder names must be uppercase or written as case-insensitive patterns.
  • Test the policy: the documentation points to an interactive policy evaluator; review every rule and test with sample files from each blocked format to confirm they fail closed.

Hardening libvips

libvips tags loaders whose underlying format libraries are less tested against hostile input as untrusted; its security policy explains how to check a loader's status with vips -l followed by the loader name, and treats untrusted loaders as outside the scope of vulnerability reports. From version 8.13 libvips can block operations at runtime. Earlier versions need compile-time configuration to remove formats.

  • Block untrusted operations: set the VIPS_BLOCK_UNTRUSTED environment variable, or call vips_block_untrusted_set(), so any operation tagged untrusted refuses to run. The developer checklist recommends this for untrusted data.
  • Allowlist loaders: vips_operation_block_set() blocks a class and everything derived from it. The 8.13 release notes show blocking VipsForeignLoad (all loaders) and then unblocking VipsForeignLoadJpeg so that only JPEG loads; extend the same pattern to PNG or WebP loaders as needed.
  • Keep vips_system blocked: the security policy describes this operation, which spawns command-line tools, as outside its scope; untrusted pipelines have no reason to reach it.
  • Sanity-check before processing: the checklist notes that opening an image with verified loaders is fast, so read the header, reject dimensions over your limit, then process.
  • Route progressive images separately: the checklist shows a progressive JPEG needing about 4 GB of memory to thumbnail against 72 MB for the same image saved as baseline. Detect the interlaced metadata item and reject or queue those files on a separate, constrained worker.
  • Use thumbnail and sequential access: thumbnail combines load and resize so formats can shrink on load, cutting memory use; sequential access reduces it further.

Bindings: sharp for Node.js exposes limitInputPixels (default 268,402,689 pixels, 0x3FFF by 0x3FFF) and failOn, whose documentation recommends the default "warning" level for untrusted input; leave unlimited at its default of false. pyvips exposes the same operation blocking as the C library.

Hardening Pillow

Pillow decides the format from file content and tries every registered plugin unless told otherwise. Its security policy names parsing untrusted image data as the primary attack surface and lists the key risks: decompression bombs, EPS files invoking Ghostscript, ImageMath.unsafe_eval(), memory safety in the C extensions and bundled codec libraries, and the value of sandboxing for high-risk deployments. The controls sit in code rather than a policy file, so they belong in a shared wrapper that every service uses.

  • Restrict formats on open: Image.open() accepts a formats argument, a list or tuple of formats to try. Pass only the formats you accept, for example JPEG, PNG and WEBP. python3 -m PIL lists what the installed build supports.
  • Keep decompression bomb protection on and strict: Pillow raises DecompressionBombWarning when an image exceeds MAX_IMAGE_PIXELS (89,478,485 pixels, about 0.25 GB for a 24-bit image) and DecompressionBombError above twice that. Promote the warning to an error with warnings.simplefilter('error', Image.DecompressionBombWarning), set MAX_IMAGE_PIXELS to what your product needs, and never set it to None for user input.
  • Turn off Ghostscript for EPS: Pillow reads some EPS files itself and others through Ghostscript if the gs executable is present. Set EpsImagePlugin.gs_binary = False, and leave EPS out of the formats list, unless EPS is a product requirement.
  • Use lambda_eval, not unsafe_eval: the ImageMath documentation warns that unsafe_eval uses Python's eval() on the expression string and recommends lambda_eval. Never build expressions from user input.
  • Call load() inside your limits: Image.open() reads the header lazily; check size and mode, then call load() inside the sandboxed worker with its memory limit in force.

Ghostscript and other helpers

PostScript, EPS and PDF inputs are usually rendered by Ghostscript, whichever image library calls it. Ghostscript documentation states that SAFER mode has been active by default since version 9.50 and that starting with -dNOSAFER lets PostScript programs read, write, rename or delete any file the process can reach. Never pass -dNOSAFER or -dDELAYSAFER in a pipeline that handles user files. The cleaner option for most services is not to accept these formats at all: block the PS, EPS and PDF coders or delegates in every library and send document rendering to a separate, isolated service. Treat metadata tools and other converters in the pipeline the same way: inventory them, pin versions and run them in the same sandbox.

Isolation and where identity fits

Run decoding in a worker that would be harmless if fully compromised: dedicated non-root user, read-only root filesystem, one scratch directory per job, a restrictive seccomp profile, cgroup CPU and memory limits, a wall-clock timeout and no outbound network. The FFmpeg and Media Processing Security Guide sets out the full sandbox table; the seccomp Guide and Container Runtime Security Guide cover the layers in depth.

The worker's own identity is the prize after a decoder compromise. Give it no standing cloud credentials: an orchestrator outside the sandbox fetches the input and writes the output using short-lived, pre-signed URLs scoped to one object, and the worker has no route to the cloud instance metadata service. That turns code execution into access to one file rather than the storage bucket. See the Workload Identity Federation Guide and, for blocking metadata and internal URLs, the SSRF Prevention Guide.

Serving results back is a separate risk. User-supplied SVG is a document that can carry script, so either rasterise it server-side or serve it as a download with a restrictive Content-Security-Policy, never inline from your main origin. See the Client-Side Security Guide.

Implementation roadmap

  1. Inventory: find every code path that decodes images, including avatar uploads, URL previews, PDF thumbnails, admin imports and background jobs. Record library, version, binding and container base image.
  2. Define the format contract: agree the accepted input formats, maximum dimensions, maximum pixel count, maximum frames and output formats with the product owner. This becomes the documented file policy OWASP ASVS 5.0 V5.1.1 asks for.
  3. Apply library controls in report mode: log, without blocking, every decode that would fail the new allowlist or limits for one to two weeks to find legitimate files you missed.
  4. Enforce in a pilot: switch on the policy, operation block or formats argument for one service or a percentage of traffic, with alerting on rejection rates.
  5. Isolate: move decoding to sandboxed workers with no egress and no standing credentials.
  6. Roll out and pin: extend to all paths; build the policy into the container image and add a start-up test that fails the deployment if the loaded policy is weaker than expected.
  7. Roll back safely: if legitimate uploads fail, widen the allowlist for the specific format rather than reverting to the open policy, and record the exception with an expiry.

Detection and measurement

  • Rejections by reason: blocked format, pixel limit, time limit; a sudden rise in blocked formats such as SVG, MVG or PostScript suggests probing.
  • Worker anomalies: child processes spawned by decode workers, outbound connection attempts (should be zero), out-of-memory kills and timeouts.
  • Policy drift: start-up check results comparing the loaded policy or block list with the expected baseline.
  • Patch latency: days from a library security release to deployment across all images; track through software composition analysis. See the Software Composition Analysis (SCA) Guide.
  • Coverage: share of decode paths running in sandboxed workers with format allowlists.

Framework mapping

ControlOWASP ASVS 5.0NIST SP 800-53 Rev. 5NIST CSF 2.0MITRE D3FEND
Format allowlist and content validationV5.2.2SI-10 Information Input Validation; CM-7 Least FunctionalityPR.PS-01None specific
Pixel, size and resource limitsV5.2.1, V5.2.6SI-10PR.PS-01None specific
Sandboxed decode workersV5.1.1 (documented file policy)SC-39 Process IsolationPR.PS-01D3-SCF System Call Filtering; D3-OTF Outbound Traffic Filtering
Library patching and inventoryNone specificSI-2 Flaw Remediation; RA-5ID.RA-01; PR.PS-02None specific

Common failure patterns

  • Policy written, never loaded: a custom policy.xml copied to the wrong path or overridden by the base image's packaged policy. Only identify -list policy, or an equivalent start-up test, proves what is active.
  • Extension checks as the only gate: renaming an SVG or PostScript file to .jpg does not stop content-based decoder selection unless the library is restricted to specific formats.
  • Limits removed to fix one large file: setting MAX_IMAGE_PIXELS to None, unlimited to true or deleting resource policies to satisfy a single customer image, which reopens the decompression bomb path for everyone.
  • Probing on the API server: reading headers or generating previews in the web tier "to check the file first", which puts the full parser outside the sandbox.
  • Document formats by accident: PDF or EPS thumbnails enabled through Ghostscript in the same worker as user avatars, with no separate isolation.
  • Workers with broad credentials: decode workers holding the storage service account for the whole bucket, so a decoder compromise becomes a data breach.

Practitioner checklist

  • List every image decode path, library, binding and version.
  • Document accepted formats, maximum dimensions, pixels and frames.
  • ImageMagick: start from the secure or websafe policy, add an explicit module allowlist and verify with identify -list policy.
  • libvips: set VIPS_BLOCK_UNTRUSTED and block all loaders except the ones you accept.
  • Pillow: pass formats to Image.open(), make DecompressionBombWarning an error and set gs_binary to False.
  • Keep Ghostscript in SAFER mode or out of the image pipeline entirely.
  • Run decoding in sandboxed workers with no egress and no standing cloud credentials.
  • Strip metadata on output and never serve user SVG inline.
  • Track library security advisories and patch latency.

Standards and references

Related NHI Mgmt Group resources: FFmpeg and Media Processing Security Guide · Secure File Upload Handling Guide · Client-Side Security Guide · Memory-Safe Languages Guide

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 14 November 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org