Assets and outbound fetches
Images, generated images, the metadata files and fonts are the places where Dusk reads something from outside the request: a remote image, a font file, text that came…
Images, generated images, the metadata files and fonts are the places where Dusk reads something from outside the request: a remote image, a font file, text that came from a row. Each has a rule about what it will fetch and what it will write. The metadata guide holds the full tables (Metadata); these are the parts that change what you deploy.
- The image route reads one spelling of each image.
/_dusk/imagetakesurl,w,qandfonly, each once, andwandqas plain digits.w=6.4e2,w=0640, a repeated key or an extra key is a400. Without this a client (or a CDN in front of the route) could ask for the same image under any number of URLs and make the server fetch and encode it each time.imagePropswrites exactly this form. - Redirects and credentials. A remote source answered with a redirect is a
502and is never followed. A customimages.fetchthat followed one on its own (response.redirected, or an address on another origin) is refused too. A URL withuser:password@is403even on an allowed origin. - A name that resolves to a private address. On Node the route fetches through
safeImageFetchby default: the address the socket connects to is the address that was checked, so a public name that resolves (or later rebinds) to loopback, a private range, link-local or the cloud metadata address is refused at connect, and so is a name with any such address among several. IPv4 written inside IPv6, NAT64 and 6to4 are decoded first. Onlyhttpandhttpsare fetched, and a redirect is returned to the route, which refuses it. On Bun, Deno and Workers the platform'sfetchis used, and the platform's egress rules must do this job. Passimages: { fetch: safeImageFetch() }to make it explicit, or your ownfetchto replace it (for a proxy). - The image processor bounds the decode.
sharpProcessorpasseslimitInputPixels(25 000 000 by default, setmaxInputPixels),sequentialReadandanimated: falsetosharp, so a PNG of a few kilobytes that declares 144 megapixels is a413before any pixel is decoded, and an animated GIF or WebP is read as its first frame. A source that is markup (an SVG, a DOCTYPE, anything starting with<) under a raster type is a415and never reachessharp. A declaredimage/svg+xmlis served as a locked attachment as before. Output never carries EXIF, GPS or an ICC profile, and is never SVG. ImageResponsefetches nothing the request names. An image tree may carrydata:image/...URIs only; animgsrc, anhrefor a CSSurl()that is anything else is refused, because satori fetches it while it draws.allowRemoteImages: trueis the explicit opt-in for URLs written in code. Text, attribute anddata:sizes are bounded, andresvgWasmmust be bytes or a module.- Metadata files refuse what they cannot write safely. Sitemap, robots and manifest values with a line break, a
control character, a script scheme or a protocol-relative host are errors, not output. Sitemaps are cut at 50 000
entries and 50 MiB of UTF-8, counted as the file is written. The same URL rules apply to the head tags (
canonical, Open Graph, icons, the manifest link). - Fonts are pinned by hash. A Google font file whose hash differs from
fonts.lock.jsonfails the build and nothing is written; accept a reviewed change once withupdateLock: true. Downloads come fromfonts.googleapis.comandfonts.gstatic.comover https only, with no redirect, and are size-limited. A family, weight, fallback or file path that could carry CSS or leavepublic/is aTypeError.
import { createHandler, safeImageFetch, sharpProcessor } from "@shadow-js/dusk/server";
// On Node this is the default; written out it also documents the choice.
const images = {
processor: sharpProcessor({ maxInputPixels: 16_000_000 }),
remotePatterns: ["https://cdn.example"],
fetch: safeImageFetch(),
};
Still the operator's: on Bun, Deno and Workers the egress firewall (a name that resolves to a private address is not
checked by the framework there); images.timeoutMs bounds the fetch and the body read, but the decode and encode have no
time limit besides the pixel limit and the concurrency caps; satori and sharp are optional peers, so keep them patched.