[]previous and next page

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…

For agentsUpdated

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/image takes url, w, q and f only, each once, and w and q as plain digits. w=6.4e2, w=0640, a repeated key or an extra key is a 400. 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. imageProps writes exactly this form.
  • Redirects and credentials. A remote source answered with a redirect is a 502 and is never followed. A custom images.fetch that followed one on its own (response.redirected, or an address on another origin) is refused too. A URL with user:password@ is 403 even on an allowed origin.
  • A name that resolves to a private address. On Node the route fetches through safeImageFetch by 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. Only http and https are fetched, and a redirect is returned to the route, which refuses it. On Bun, Deno and Workers the platform's fetch is used, and the platform's egress rules must do this job. Pass images: { fetch: safeImageFetch() } to make it explicit, or your own fetch to replace it (for a proxy).
  • The image processor bounds the decode. sharpProcessor passes limitInputPixels (25 000 000 by default, set maxInputPixels), sequentialRead and animated: false to sharp, so a PNG of a few kilobytes that declares 144 megapixels is a 413 before 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 a 415 and never reaches sharp. A declared image/svg+xml is served as a locked attachment as before. Output never carries EXIF, GPS or an ICC profile, and is never SVG.
  • ImageResponse fetches nothing the request names. An image tree may carry data:image/... URIs only; an img src, an href or a CSS url() that is anything else is refused, because satori fetches it while it draws. allowRemoteImages: true is the explicit opt-in for URLs written in code. Text, attribute and data: sizes are bounded, and resvgWasm must 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.json fails the build and nothing is written; accept a reviewed change once with updateLock: true. Downloads come from fonts.googleapis.com and fonts.gstatic.com over https only, with no redirect, and are size-limited. A family, weight, fallback or file path that could carry CSS or leave public/ is a TypeError.
src/entry-server.tsts
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.