- A supplier catalogue contains ten image URLs per product.
- But the URL is controlled outside the marketplace.
- It could point to a private network service.
- We built this remote-image boundary for a supplier-driven Medusa marketplace after encountering the risks created by supplier-controlled URLs.
The client problem
A supplier catalogue contains ten image URLs per product. Importing them automatically sounds straightforward: fetch each URL, resize the file and attach it to Medusa.
But the URL is controlled outside the marketplace.
It could point to a private network service. Its hostname could resolve differently between validation and connection. A redirect could jump from a safe public host to local infrastructure. A response labelled image/jpeg could contain HTML, an SVG script or a raster large enough to exhaust memory during decoding.
We built this remote-image boundary for a supplier-driven Medusa marketplace after encountering the risks created by supplier-controlled URLs. We validate the URL, every DNS answer and every redirect; pin the network connection; bound time and bytes; verify the file signature and decoded image; and isolate heavy image processing.
The business outcome is simple: suppliers can automate catalogue media without giving their URLs an open path into marketplace infrastructure.
The client problem is bigger than broken images
Most teams first think about image reliability. The supplier host may be slow, the file may disappear or the format may be unsupported.
Those are important product problems. The security problem is deeper because the marketplace backend performs the request from a privileged network position.
A server-side request can sometimes reach hosts that a public browser cannot: loopback services, cloud metadata endpoints, internal databases, container networks or administrative interfaces. This class of risk is commonly called server-side request forgery, or SSRF.
The downloaded bytes then cross another boundary. Image decoders process complex binary formats. Oversized dimensions can consume far more memory than the compressed file size suggests. A file can combine a valid image envelope with active web content.
We therefore separated acquisition into two questions:
- Is the backend allowed to connect to this destination?
- Are the received bytes safe enough to enter the image pipeline?
Passing one does not imply the other.
Accept only a narrow URL contract
The first gate rejects malformed URLs, non-HTTPS schemes and URLs containing embedded usernames or passwords.
HTTPS matters because the worker will receive supplier-controlled media across the network. The original hostname remains available for certificate verification, so encryption is tied to the name the supplier provided rather than disabled for convenience.
Literal IP addresses receive the same scrutiny as hostnames. Loopback, private, link-local, carrier-grade NAT, documentation, multicast and other special-purpose ranges are rejected for IPv4. IPv6 checks exclude loopback, local, mapped-private and other non-public ranges.
This prevents obvious attempts such as replacing a product image host with an internal address.
The allow rule is intentionally narrow: a syntactically valid URL is not enough; it must describe an HTTPS resource at a public network destination with no credential material hidden in the authority.
Validate every DNS answer, not just the hostname string
A hostname can look harmless and still resolve to a private address.
The worker performs DNS lookup before connecting and inspects every returned address. If any answer is invalid, private or special-purpose, the request is rejected rather than selecting the one convenient public result.
Why every answer? A mixed response can allow connection behaviour to choose a different destination from the one the application inspected. The validation contract should describe the complete resolution set, not one lucky address.
This also handles hosts whose records change. We do not keep a permanent assumption that a previously public hostname will always remain public. Each acquisition resolves the current target again.
DNS validation narrows the destination set, but it does not yet prove the socket will use the address that was checked. That requires the next boundary.
Pin the connection to the validated address
DNS rebinding exploits a gap between resolving a hostname and opening the connection.
An application validates a public answer. The HTTP library performs another lookup moments later and receives a private answer. The request reaches a destination that never passed policy.
Our fetcher closes that gap by choosing one address from the already validated set and supplying a pinned lookup result to the HTTPS request. The connection uses that exact address.
At the same time, the original hostname remains the TLS server name and certificate target. Pinning does not mean connecting to an IP while accepting any certificate.
This gives the request two consistent facts:
- the network address passed the public-address policy;
- the remote certificate must still authenticate the requested hostname.
Validation and connection become one operation rather than two loosely related guesses.
Treat every redirect as a new request
Image hosts often redirect to a CDN or versioned asset URL. Following redirects automatically would bypass the destination policy.
The worker handles only recognised redirect statuses and limits the number of hops. A redirect must contain a location. The new URL is resolved relative to the current one, then passes through the full URL and DNS checks before another pinned connection is made.
This prevents a public supplier host from acting as a trampoline into a private address.
It also avoids confusing a conditional 304 Not Modified response with a redirect. A 304 has a different meaning: the upstream says the resource matches a validator such as ETag. It is useful only when the image lifecycle already owns a complete compatible local asset.
Every hop can change the security decision, so every hop is validated as though it were the original URL.
Bound the network work before reading the file
A safe destination can still consume unbounded resources.
The fetcher sets separate limits for connection time, idle body time and total wall time. Temporary rate limits and server errors can be classified as retryable, but any Retry-After delay is capped rather than accepted blindly.
The request asks for JPEG, PNG or WebP and disables compressed transfer encodings. Compressed HTTP responses are rejected so a small network body cannot expand unexpectedly before the image envelope is evaluated.
If Content-Length already exceeds the byte budget, the response is stopped immediately. The streaming loop enforces the same maximum against bytes actually received, because headers are optional and untrusted.
Bytes go into a newly created private temporary file while a SHA-256 digest is calculated. The complete response never needs to sit in the workflow payload or an unbounded buffer.
Timeouts and byte limits convert hostile or broken servers into bounded failures.
Ignore the filename and verify the actual signature
An .jpg suffix and Content-Type: image/jpeg are claims, not evidence.
The content validator recognises the binary signatures for JPEG, PNG and WebP. If no supported signature exists, the file is rejected. When the server supplied a MIME type, its normalised value must agree with the detected signature.
That catches a server returning an HTML login page with a stale image header, as well as a deliberate content-type mismatch.
We keep the supported raster set small. SVG is not accepted as a product image source because it is active document content rather than a simple decoded raster. Animated or multi-page content is also outside the current contract.
The system accepts the formats the rendition pipeline is designed to transform, not every file a browser might display.
Detect active payloads hiding inside an image envelope
A file can begin with a valid raster signature and still contain suspicious active markup.
The validator examines bounded regions at the beginning and end of the file for HTML, script, SVG and document markers. If it finds an active-content pattern, it rejects the image as polyglot content.
This check complements signature validation. The signature asks whether the file begins like a supported image. The polyglot gate asks whether the same file also appears designed to be interpreted as active web content.
No lightweight scan can classify every theoretical file construction. The goal is defence in depth: narrow formats, matching MIME, active-content rejection, strict raster decoding and controlled output renditions.
The marketplace never serves the supplier source directly. It produces new owned WebP renditions after validation, which further separates public media from the untrusted input envelope.
Bound decoded dimensions, not only compressed bytes
A twenty-megabyte file can decode into an enormous pixel surface.
The validator checks width, height, total pixels and page count through the image decoder. The decoder is configured with an input-pixel limit and fails on warnings. Metadata must agree with the signature and describe a positive, supported raster.
The production image lifecycle goes further by running metadata and rendition work in isolated child processes with bounded memory and wall time. A decoder crash, excessive allocation or hang does not take the Medusa workflow worker down with it.
This is a crucial distinction. Network byte limits protect transfer and storage. Pixel limits protect decoding. Process isolation protects the long-running commerce worker from faults inside a complex media library.
Each layer controls a different resource.
Clean up temporary material on every path
Downloaded supplier bytes are working material, not permanent product assets.
The fetcher creates a private temporary directory and restrictive file. If streaming fails, it removes the directory. After validation and transformation, the lifecycle removes the temporary source.
Only admitted, policy-compliant renditions move into the platform's file service. The product receives the local marketplace URL and controlled provenance, not a path to temporary untrusted bytes.
This avoids a slow accumulation of abandoned files after timeouts, rejected content or decoder failures. It also keeps the ownership boundary clear: the temporary file is evidence under evaluation; the rendition is the asset the marketplace operates.
Cleanup is part of the failure design, not a best-effort task left to server restarts.
Classify failures for safe retries
Not every failure should be retried.
A timeout, connection error, rate limit or temporary server error may succeed later. An invalid scheme, private address, MIME mismatch, oversized decode or polyglot payload is a policy failure; repeating the same input will not make it acceptable.
The fetcher carries a retryable classification and a bounded retry delay where the remote response supports it. The image-localisation lifecycle can record retryable and permanent outcomes separately, expose them to operators and avoid hammering a supplier host after a clear rejection.
This improves both security and operations. Permanent unsafe content does not enter an endless queue. Temporary supplier outages do not require the product import itself to be rolled back.
The product exists; its media intent remains a visible, recoverable lifecycle with an honest reason for failure.
A practical remote-image security checklist
Before allowing Medusa to fetch supplier media, ask:
- Are only HTTPS URLs accepted?
- Are embedded credentials rejected?
- Are literal private and special addresses blocked for IPv4 and IPv6?
- Are all DNS answers validated?
- Is the connection pinned to a validated address while TLS verifies the hostname?
- Is every redirect revalidated and bounded?
- Are connection, idle and total timeouts separate?
- Are declared and streamed byte limits enforced?
- Are compressed transfer responses controlled?
- Does the binary signature match the claimed MIME?
- Are active HTML, SVG and script markers rejected?
- Are pixel dimensions and page count bounded?
- Does risky decoding run in an isolated process?
- Are temporary files removed after success and failure?
- Are unsafe failures permanent and transient failures bounded for retry?
- Is only a newly generated local rendition promoted publicly?
A URL field becomes a network and content-processing API the moment the backend fetches it.
Why Medusa was the right foundation
Medusa gave us an extensible product media domain. The supplier ecosystem required remote ingestion at a scale where manual downloads were not viable.
We connected them through a controlled acquisition boundary: public HTTPS destinations, validated and pinned DNS, rechecked redirects, bounded streaming, verified raster content, isolated decoding and owned renditions.
This lets suppliers provide the automation the client needs without making remote infrastructure part of the storefront runtime or giving arbitrary URLs access to the backend's network position.
The broader lesson is straightforward. Remote media is not “just an image”. Treat it as untrusted network input until every layer—destination, transfer, envelope, decode and rendition—has earned the next step.
