feat(vercel): add image optimization - #13
Conversation
📝 WalkthroughWalkthroughChangesVercel image optimization
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant Client
participant VercelEnvRunner
participant VercelImageHandler
participant IPX
participant Worker
Client->>VercelEnvRunner: Request /_vercel/image
VercelEnvRunner->>VercelImageHandler: Forward image Request
VercelImageHandler->>IPX: Process validated image modifiers
IPX->>Worker: Fetch local image data
Worker-->>IPX: Return image data
IPX-->>VercelImageHandler: Return optimized response
VercelImageHandler-->>VercelEnvRunner: Return image response
VercelEnvRunner-->>Client: Return Vercel image response
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (7)
src/runners/vercel/image.ts (4)
33-46: Explicitreturn undefinedin thecatchbranch.Readability nit: the catch sets
_ipxLoadResult = falseand falls through to an implicitundefinedreturn. Make the contract obvious and silence potential lint complaints:♻️ Tweak
} catch { _ipxLoadResult = false; console.warn( "ipx is not installed. Install it for Vercel image optimization: npx nypm i -D ipx", ); + return undefined; } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/runners/vercel/image.ts` around lines 33 - 46, The catch branch of loadIPX currently assigns _ipxLoadResult = false and relies on implicit undefined return; make the contract explicit by adding an explicit return undefined in the catch so callers and linters see the intended Promise<IPXModule | undefined> outcome. Update the catch block inside function loadIPX to set _ipxLoadResult = false, log the warning as before, then return undefined.
321-366: Minor: short-circuit SVG on explicitf=image/svg+xmlbeforeprocess().If the URL-based SVG check is bypassed (e.g. source doesn’t have a
.svgextension) and the caller setsf=image/svg+xml,modifiers.formatbecomes"svg+xml", IPX runs a full decode/encode, and only then is the response rejected at line 326. Cheap to short-circuit earlier right after thefparse:♻️ Proposed tweak
if (f && config?.formats?.length && !config.formats.includes(f)) { return new Response(`"f" must be one of: ${config.formats.join(", ")}`, { status: 400 }); } + if (!config?.dangerouslyAllowSVG && f === "image/svg+xml") { + return new Response('"url" parameter is valid but image type is not allowed', { status: 400 }); + }Also,
catch (error: any)at line 363 could becatch (error)with aunknownnarrowing for type hygiene, though it’s not user-visible.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/runners/vercel/image.ts` around lines 321 - 366, Short-circuit SVG handling by checking the parsed modifiers.format immediately after parsing the request and before calling ipx/process(): if modifiers.format === "svg+xml" and config?.dangerouslyAllowSVG is falsy, return the same 400 Response used for blocked SVGs (matching the message at the later format check) to avoid running ipx.process(); also change the catch clause from catch (error: any) to catch (error) and narrow error (e.g., extract statusCode/message safely) for better type hygiene. Ensure you reference the existing modifiers, ipx(sourceUrl, modifiers), process(), format check and the Response construction so the behavior and responses remain consistent.
62-82:matchPatternregex heuristic is fragile and exposes ReDoS from config.Two concerns:
- Any pattern starting with
^or ending with$is interpreted as a regex (line 64). Glob patterns that happen to contain these characters would be silently misinterpreted. Consider a more explicit marker (e.g. only treat patterns that both start with^and end with$as regex, which matches the Build Output API convention), or an explicit discriminator in the config type.new RegExp(pattern)on user-supplied input can trigger catastrophic backtracking (ast-grep flagged CWE-1333). The config originates from the developer (not end-users), so impact is bounded, but worth either documenting or guarding with a tested regex library (e.g.recheck) or a compile-time timeout.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/runners/vercel/image.ts` around lines 62 - 82, The matchPattern function currently treats any pattern starting with "^" OR ending with "$" as a regex and passes user-supplied input straight to new RegExp, which mis-classifies globs and can enable ReDoS; update matchPattern so it only treats a pattern as a regex when it both startsWith("^") and endsWith("$") (or when an explicit discriminator is present), and avoid direct unsafe RegExp compilation by validating and constraining the input before compiling: enforce a sensible max pattern length, sanitize/escape or reject obviously dangerous constructs, and wrap RegExp construction/test in a try/catch (or switch to a safe regex library like recheck) so that if compilation fails or is potentially unsafe you fall back to the glob-to-regex path; reference the matchPattern function, the pattern variable, and the sites where new RegExp(pattern) is called and replace those usages accordingly.
175-222: Race on concurrent IPX initialization.If two requests hit
getIPX()before_ipxis assigned (e.g. two concurrent/_vercel/imagerequests on a cold runner), both willawait loadIPX()and both will callipxModule.createIPX(...), so the first assignment is overwritten and an extra IPX instance is created without cleanup. Memoize the pending promise:♻️ Proposed fix
- let _ipx: ReturnType<IPXModule["createIPX"]> | undefined; + let _ipx: ReturnType<IPXModule["createIPX"]> | undefined; + let _ipxPromise: Promise<ReturnType<IPXModule["createIPX"]> | undefined> | undefined; async function getIPX() { if (_ipx) return _ipx; - const ipxModule = await loadIPX(); - ... - _ipx = ipxModule.createIPX({ ... }); - return _ipx; + if (_ipxPromise) return _ipxPromise; + _ipxPromise = (async () => { + const ipxModule = await loadIPX(); + if (!ipxModule) return undefined; + // ...workerStorage + createIPX... + _ipx = ipxModule.createIPX({ /* ... */ }); + return _ipx; + })(); + return _ipxPromise; }Also reset
_ipxPromisealongside_ipxinclose().🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/runners/vercel/image.ts` around lines 175 - 222, getIPX has a race: concurrent callers can each call loadIPX() and createIPX(), so memoize the pending initialization by introducing a module-scoped _ipxPromise alongside _ipx; in getIPX, if _ipx exists return it, else if _ipxPromise exists await it, otherwise set _ipxPromise = (async () => { const ipxModule = await loadIPX(); if (!ipxModule) return undefined; const instance = ipxModule.createIPX({...}); _ipx = instance; _ipxPromise = undefined; return instance; })(); then await and return the resolved instance instead of calling createIPX multiple times; also update close() to clear both _ipx and _ipxPromise so subsequent initialization can run cleanly (references: getIPX, loadIPX, _ipx, _ipxPromise, createIPX, close).src/runners/vercel/runner.ts (1)
85-102: Minor: forwarded image Request drops original method/body.
new Request(requestUrl, { headers })on line 97 only carries the URL and the (already-merged) headers. If the inboundinputwas aRequestwith a non-GET method or a body, those are silently dropped. In practice/_vercel/imageis always GET, but for consistency with the other branches (which preserveinput/init), consider propagating method and body when applicable:♻️ Proposed tweak
- res = await this._imageHandler.handle(new Request(requestUrl, { headers })); + const imageReq = + input instanceof Request + ? new Request(requestUrl, { ...init, headers, method: input.method, body: input.body, duplex: "half" } as any) + : new Request(requestUrl, { ...init, headers }); + res = await this._imageHandler.handle(imageReq);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/runners/vercel/runner.ts` around lines 85 - 102, The image request branch creates a new Request with only URL and headers which drops method/body from the original input; update the logic around requestUrl handling so when constructing the Request passed to this._imageHandler.handle you propagate the original request semantics (if input is a Request use input.method, input.body and other relevant properties; otherwise use init.method and init.body when present) and merge headers, ensuring this._imageHandler and createVercelImageHandler usage stays the same; locate symbols requestUrl, input, init, this._imageHandler, and _imageHandler.handle to apply the change.test/vercel.test.ts (1)
267-280: Remote-URL tests can hit real DNS and slow/flake CI.Tests that expect “validation passed but fetch fails” (e.g.
allowed.example.com,cdn.example.com,any.example.com,other.com) will cause IPX (or the fallback) to perform actual outboundfetch()calls. Depending on the network sandbox:
- DNS resolution may hang until timeout (slow tests).
example.com/other.comactually resolve, so the request will connect and potentially return real bytes rather than fail.- In offline/airgapped CI, behavior is non-deterministic.
Prefer pointing at a guaranteed-unroutable target for deterministic failure, or assert on the specific validation behavior without relying on the downstream fetch to fail:
♻️ Example
- images: { domains: ["allowed.example.com"] }, + images: { domains: ["allowed.invalid"] }, ... - "http://localhost/_vercel/image?url=https://allowed.example.com/img.png&w=100&q=75", + "http://localhost/_vercel/image?url=https://allowed.invalid/img.png&w=100&q=75",
.invalidis reserved by RFC 2606 and is guaranteed not to resolve.Also applies to: 282-303, 305-339, 365-376
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/vercel.test.ts` around lines 267 - 280, Replace tests that rely on real DNS/remote fetch failures by using an unroutable reserved TLD (e.g., hostnames under .invalid) or by asserting validation behavior only; specifically update VercelEnvRunner test cases (the "allows remote URL when domain matches" test and the other tests referencing allowed.example.com, cdn.example.com, any.example.com, other.com) to point their image URL to a .invalid host (e.g., https://allowed.example.invalid/img.png) so fetch() deterministically fails, or change the assertions to check the validation result from the server (via runner.fetch response status/body) without depending on downstream network resolution. Ensure changes reference the VercelEnvRunner instantiation and the runner.fetch(...) calls in those tests.package.json (1)
59-81: Document ipx 4.0.0-alpha requirement for image optimization.
ipx@^4.0.0-alpha.1is a pre-release (stable 3.1.1 available). While the handler has a graceful fallback when ipx is unavailable, there's no documentation explaining why the alpha is required. Consider adding a note to the README clarifying the ipx version requirement and that it's optional—users may be surprised by install-time warnings or unclear why a pre-release is pinned.Alternatively, pin to an exact version (
4.0.0-alpha.1) until a stable 4.x release to prevent accidental prerelease jumps.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` around lines 59 - 81, The package pins a prerelease ipx range ("ipx": "^4.0.0-alpha.1" in both dependencies and peerDependencies) without documentation or an exact pin; update either README and package.json: add a short note in the README explaining that ipx@4.0.0-alpha.1 is required for the new image optimization handler, that it's optional (referenced by peerDependenciesMeta "ipx"), and that users may see install-time warnings, or instead change the package.json entries for "ipx" in dependencies and peerDependencies to an exact pin ("4.0.0-alpha.1") to avoid accidental prerelease upgrades until a stable 4.x is available. Ensure the README note references the handler and the optional peerDependency semantics so users understand the fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/runners/vercel/image.ts`:
- Around line 105-113: The TypeScript error arises because pathname can be
undefined after destructuring sourceUrl.split("?") in validateLocalUrl; ensure
pathname (and search) have explicit string defaults so matchPattern(p.pathname,
pathname) always receives a string—update the destructuring to provide defaults
(e.g., pathname = "" and search = "") and keep the rest of the logic the same so
p.pathname and p.search comparisons remain valid.
- Around line 48-54: resolveWorkerUrl currently returns an invalid URL for unix
socket addresses (when WorkerAddress has socketPath); update resolveWorkerUrl to
not attempt to construct a unix URL: detect "socketPath" in the WorkerAddress
and throw a clear error (or assert) that the image handler only supports TCP
addresses (host/port), and add a short comment documenting that image handling
requires TCP (or alternatively implement an undici fetch path with a custom
Dispatcher using connect:{socketPath} elsewhere if unix sockets must be
supported). Ensure references to WorkerAddress and resolveWorkerUrl remain, and
make the function consistently return only TCP http://host:port... URLs or fail
fast when socketPath is present.
---
Nitpick comments:
In `@package.json`:
- Around line 59-81: The package pins a prerelease ipx range ("ipx":
"^4.0.0-alpha.1" in both dependencies and peerDependencies) without
documentation or an exact pin; update either README and package.json: add a
short note in the README explaining that ipx@4.0.0-alpha.1 is required for the
new image optimization handler, that it's optional (referenced by
peerDependenciesMeta "ipx"), and that users may see install-time warnings, or
instead change the package.json entries for "ipx" in dependencies and
peerDependencies to an exact pin ("4.0.0-alpha.1") to avoid accidental
prerelease upgrades until a stable 4.x is available. Ensure the README note
references the handler and the optional peerDependency semantics so users
understand the fallback.
In `@src/runners/vercel/image.ts`:
- Around line 33-46: The catch branch of loadIPX currently assigns
_ipxLoadResult = false and relies on implicit undefined return; make the
contract explicit by adding an explicit return undefined in the catch so callers
and linters see the intended Promise<IPXModule | undefined> outcome. Update the
catch block inside function loadIPX to set _ipxLoadResult = false, log the
warning as before, then return undefined.
- Around line 321-366: Short-circuit SVG handling by checking the parsed
modifiers.format immediately after parsing the request and before calling
ipx/process(): if modifiers.format === "svg+xml" and config?.dangerouslyAllowSVG
is falsy, return the same 400 Response used for blocked SVGs (matching the
message at the later format check) to avoid running ipx.process(); also change
the catch clause from catch (error: any) to catch (error) and narrow error
(e.g., extract statusCode/message safely) for better type hygiene. Ensure you
reference the existing modifiers, ipx(sourceUrl, modifiers), process(), format
check and the Response construction so the behavior and responses remain
consistent.
- Around line 62-82: The matchPattern function currently treats any pattern
starting with "^" OR ending with "$" as a regex and passes user-supplied input
straight to new RegExp, which mis-classifies globs and can enable ReDoS; update
matchPattern so it only treats a pattern as a regex when it both startsWith("^")
and endsWith("$") (or when an explicit discriminator is present), and avoid
direct unsafe RegExp compilation by validating and constraining the input before
compiling: enforce a sensible max pattern length, sanitize/escape or reject
obviously dangerous constructs, and wrap RegExp construction/test in a try/catch
(or switch to a safe regex library like recheck) so that if compilation fails or
is potentially unsafe you fall back to the glob-to-regex path; reference the
matchPattern function, the pattern variable, and the sites where new
RegExp(pattern) is called and replace those usages accordingly.
- Around line 175-222: getIPX has a race: concurrent callers can each call
loadIPX() and createIPX(), so memoize the pending initialization by introducing
a module-scoped _ipxPromise alongside _ipx; in getIPX, if _ipx exists return it,
else if _ipxPromise exists await it, otherwise set _ipxPromise = (async () => {
const ipxModule = await loadIPX(); if (!ipxModule) return undefined; const
instance = ipxModule.createIPX({...}); _ipx = instance; _ipxPromise = undefined;
return instance; })(); then await and return the resolved instance instead of
calling createIPX multiple times; also update close() to clear both _ipx and
_ipxPromise so subsequent initialization can run cleanly (references: getIPX,
loadIPX, _ipx, _ipxPromise, createIPX, close).
In `@src/runners/vercel/runner.ts`:
- Around line 85-102: The image request branch creates a new Request with only
URL and headers which drops method/body from the original input; update the
logic around requestUrl handling so when constructing the Request passed to
this._imageHandler.handle you propagate the original request semantics (if input
is a Request use input.method, input.body and other relevant properties;
otherwise use init.method and init.body when present) and merge headers,
ensuring this._imageHandler and createVercelImageHandler usage stays the same;
locate symbols requestUrl, input, init, this._imageHandler, and
_imageHandler.handle to apply the change.
In `@test/vercel.test.ts`:
- Around line 267-280: Replace tests that rely on real DNS/remote fetch failures
by using an unroutable reserved TLD (e.g., hostnames under .invalid) or by
asserting validation behavior only; specifically update VercelEnvRunner test
cases (the "allows remote URL when domain matches" test and the other tests
referencing allowed.example.com, cdn.example.com, any.example.com, other.com) to
point their image URL to a .invalid host (e.g.,
https://allowed.example.invalid/img.png) so fetch() deterministically fails, or
change the assertions to check the validation result from the server (via
runner.fetch response status/body) without depending on downstream network
resolution. Ensure changes reference the VercelEnvRunner instantiation and the
runner.fetch(...) calls in those tests.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4feb6f24-b36e-4e89-8db7-4124470cf9cf
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (7)
AGENTS.mdpackage.jsonpnpm-workspace.yamlsrc/runners/vercel/image.tssrc/runners/vercel/runner.tstest/fixtures/app-image.mjstest/vercel.test.ts
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/runners/vercel/image.ts`:
- Around line 270-279: The handler currently only validates when
isRemoteUrl(sourceUrl) or sourceUrl.startsWith("/"), allowing unsupported shapes
(e.g., "foo.png", "data:", "file:", uppercase schemes) to fall through; update
the logic in src/runners/vercel/image.ts around the sourceUrl checks so that if
the URL is neither a recognized remote (isRemoteUrl) nor a local absolute path
(startsWith("/")), the function immediately returns a 400 Response('"url"
parameter is not allowed'); ensure this check runs before calling
validateRemoteUrl or validateLocalUrl so unsupported schemes/relative paths are
rejected early.
- Around line 311-320: The accept-header negotiation bypasses the allowed
formats list: when no explicit format param (f) is present the code
unconditionally sets modifiers.format to "avif" or "webp" based only on Accept;
update that branch to consult the configured allowed formats (images.formats /
config.formats) before picking a type. Concretely, inside the block that reads
const accept = request.headers.get("accept") and sets modifiers.format, filter
the candidate formats (["avif","webp",...]) by the configured images.formats
list and only choose the first match present in both the Accept header and the
config; if none match, do not override modifiers.format or fall back to a safe
default already allowed by the config.
- Around line 132-161: The fetchUnoptimized function currently proxies any
upstream response; after performing the fetch (in fetchUnoptimized) inspect the
response Content-Type via res.headers.get('content-type') and reject responses
that are not image/* with an appropriate error status (e.g., 415) instead of
returning the body, and specifically block SVG payloads unless
config?.dangerouslyAllowSVG === true by returning a forbidden error (e.g., 403)
when content-type contains "svg" and dangerouslyAllowSVG is not set; only apply
the vary/cache-control header adjustments and return the Response with res.body
when the content-type checks pass.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 396dc94f-2d73-4b17-a76d-70c8530c91d7
📒 Files selected for processing (2)
src/runners/vercel/image.tstest/vercel.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- test/vercel.test.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/runners/vercel/image.ts`:
- Around line 65-83: The matchPattern function currently compiles RegExp on
every request and will throw for malformed patterns; precompile and validate
patterns when loading the runner config and use those cached RegExp objects on
the hot path. Update the config parsing code to convert each pattern string into
a RegExp (or mark it invalid) and store it (e.g., compiledPatterns), log and
skip/disable any patterns that fail compilation, then change matchPattern (or
add a companion like matchCompiledPattern) to accept and test against a
precompiled RegExp rather than calling new RegExp(pattern) per request so the
image route no longer recompiles or crashes on bad patterns.
- Around line 349-353: The parsing of the public ?cache= param
(cacheOverride/cacheTTL) is computed but only used inside the ipx branch, so the
unoptimized fallback path ignores it; update the code so the cache override is
applied in both paths: compute cacheOverride and cacheTTL early (using
cacheOverride = Number.parseInt(...) and cacheTTL as currently defined) and then
use cacheTTL when setting cache-control / TTL in the unoptimized fallback
response as well as in the ipx branch, ensuring the same variable names
(cacheOverride, cacheTTL) are referenced in the fallback code that builds the
response.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 463c7629-a5ce-4828-a3ca-0f63cbf8c984
📒 Files selected for processing (1)
src/runners/vercel/image.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/runners/vercel/image.ts`:
- Around line 148-166: The fallback path that uses fetchUnoptimized() must apply
the same security and disposition headers as the optimized branch: when building
the Response in the fallback (the block that returns the non-optimized image
response), copy the optimized-branch logic to set content-security-policy from
config.contentSecurityPolicy (if present) and set content-disposition based on
config.contentDispositionType (e.g., "inline" or "attachment" and filename
handling) onto the Headers instance (the same headers variable) before returning
the Response; ensure you also preserve existing vary/cache-control handling
already present so the fallback mirrors the optimized branch.
- Around line 281-291: The code treats protocol-relative URLs like
"//cdn.example.com/a.jpg" as both local and remote; normalize or reject them
before the isLocal/isRemote checks so they are mutually exclusive. Update the
logic in the request handling flow (where isLocal = sourceUrl.startsWith("/")
and isRemote = isRemoteUrl(sourceUrl) are computed) to first detect
protocol-relative URLs and either (a) prepend a default protocol (e.g. "https:")
to produce normalizedSourceUrl, or (b) return a 400 rejecting protocol-relative
URLs; then use normalizedSourceUrl (not the original sourceUrl) for subsequent
branching, validateRemoteUrl(normalizedSourceUrl) /
validateLocalUrl(normalizedSourceUrl), and for the fallback/IPX fetch paths so
remote requests are never treated as local.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5b49428d-1f3f-40ac-afd1-cb150ef3b401
📒 Files selected for processing (1)
src/runners/vercel/image.ts
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/runners/vercel/image.ts`:
- Around line 181-183: The fallback response currently sets Vary to "Accept"
only if no Vary exists, which drops Accept when upstream sent a Vary; update the
logic in the fallback response handling (the headers variable in the image
optimization path, e.g., where the code currently checks if
(!headers.has("vary")) headers.set("vary", "Accept")) to always ensure "Accept"
is present by reading existing headers.get("vary"), appending "Accept" if not
already included, and then writing the combined value back to
headers.set("vary", combinedValue) so upstream Vary values are preserved while
guaranteeing Accept is included for correct cache-keying.
- Around line 169-180: The current guard (using headers, contentType and res.ok)
lets through responses with no or non-image Content-Type; change the logic so we
only forward bodies that are positively identified as images: after reading
headers and contentType, if there is no contentType or
/^image\//i.test(contentType) is false, return the 400 Response (rejecting
non-images and missing type); keep the existing SVG check using
config?.dangerouslyAllowSVG and /^image\/svg\+xml\b/i to forbid SVGs when
configured, but ensure that check runs only for responses that passed the
positive image check.
- Around line 159-167: The fetch calls in this file (notably the branches using
getAddress()/resolveWorkerUrl and the external fetches around the code paths
that call ipxHttpStorage) currently follow HTTP redirects and can bypass the
allowlist validation applied only to the original sourceUrl; update those
fetches to either set redirect: 'error' on their RequestInit or implement manual
redirect-following that on each 3xx response reads the Location header and
re-validates the target against the configured allowlist (domains,
remotePatterns, localPatterns) before issuing the next fetch; ensure this change
is applied to the fetch in the sourceUrl local branch (resolveWorkerUrl), the
external fetch branch, and any fetches used by ipxHttpStorage so no redirect hop
can escape the allowlist check.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a961a680-cee6-410d-ad3c-d1792a4d0506
📒 Files selected for processing (1)
src/runners/vercel/image.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/runners/vercel/image.ts`:
- Around line 294-305: The handler currently defaults quality to 75 and then
always enforces config.qualities, causing requests that omit q to fail when 75
isn't allowed; update the logic around q/quality so that if the incoming q
parameter is absent you either (A) derive the default from config.qualities
(e.g., set quality = config.qualities[0] when config?.qualities?.length) or (B)
skip the config.qualities inclusion check when q was not explicitly provided;
adjust the validation that uses config.qualities.includes(quality) accordingly
so it only runs when q was provided (or after you set the default from
config.qualities). Ensure you reference the existing q, quality and
config.qualities checks in the image handler when making this change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b63161c0-b8c7-4067-8a91-f0903a7665bf
📒 Files selected for processing (1)
src/runners/vercel/image.ts
8dfcda5 to
56c0496
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
src/runners/vercel/image.ts (1)
492-523: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueThe fallback path ignores the request method.
fetchUnoptimized()always issues aGETand returnsres.body, so aHEADrequest receives a body from the handler. The ipx path honorsHEADthrough ipx. Forwardrequest.methodto keep both paths equal.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/runners/vercel/image.ts` around lines 492 - 523, Update the fallback call to fetchUnoptimized in handle so it receives request.method and preserves HEAD semantics; ensure fetchUnoptimized uses that method when issuing the upstream request and does not return a response body for HEAD, matching the ipx path..agents/VERCEL.md (1)
71-71: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueFix the dangling word in this sentence.
"relied on rather:" reads as an unfinished clause.
✏️ Proposed fix
-Two ipx v4 defaults are relied on rather: `maxOutputDimension` (8192) clamps +Two ipx v4 defaults are relied on: `maxOutputDimension` (8192) clamps🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.agents/VERCEL.md at line 71, Fix the sentence in the ipx v4 defaults description by removing or replacing the dangling “rather” before the colon, while preserving the existing details about maxOutputDimension and SVG sanitization.test/vercel-image.test.ts (1)
443-458: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueThe warn-once assertion depends on test order.
loadIPX()sets_ipxLoadedonce per module instance. This test installs theconsole.warnspy, so it must be the first test in the suite that triggers a load. If a later refactor reorders the suite or adds an earlier test, the first warning happens before the spy exists and this assertion fails silently in intent. Install the spy inbeforeAllfor this suite, or reset modules per test, to remove the ordering dependency.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/vercel-image.test.ts` around lines 443 - 458, Update the vercel-image test setup around loadIPX and the “proxies the unoptimized source and warns once” test so the console.warn spy is installed in a suite-level beforeAll before any test can trigger loading. Keep the existing single-warning assertion and restore the spy in suite-level cleanup, eliminating dependence on test order.package.json (1)
63-63: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valuePin or re-evaluate the
ipxprerelease range.
^4.0.0-beta.1allows later4.0.0prereleases and future4.xstable releases, but lockfile resolution still fixesipxto4.0.0-beta.1. If lateripxversions can break the Vercel image API used here (createIPX,ipxHttpStorage,createIPXFetchHandler), narrow the dev dependency and peer dependency to a specific prerelease or stable version.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@package.json` at line 63, Re-evaluate the ipx version range in package.json and narrow both the dev dependency and peer dependency to the specific prerelease or stable version verified for the Vercel image API symbols createIPX, ipxHttpStorage, and createIPXFetchHandler. Keep the dependency and peer ranges consistent with the supported version.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.agents/VERCEL.md:
- Line 51: Update the `w` parameter description in `VERCEL.md` to state that it
must be a positive integer, excluding zero, while preserving the existing
bare-integer and parsing restrictions.
In `@src/runners/vercel/image.ts`:
- Around line 85-113: Update patternToRegExp to handle RegExp compilation
failures defensively: catch invalid-pattern errors, avoid caching them as valid
expressions, and return a non-matching result for matchPattern instead of
allowing the exception to escape parseImageRequest or handle. Preserve existing
matching and cache behavior for valid patterns.
- Around line 347-369: Update fetchUnoptimized so both fetch calls use redirect:
"error", preventing an allowlisted source or worker URL from following an
unvalidated Location to another host. Keep the existing URL resolution,
readiness handling, and upstream error response unchanged.
---
Nitpick comments:
In @.agents/VERCEL.md:
- Line 71: Fix the sentence in the ipx v4 defaults description by removing or
replacing the dangling “rather” before the colon, while preserving the existing
details about maxOutputDimension and SVG sanitization.
In `@package.json`:
- Line 63: Re-evaluate the ipx version range in package.json and narrow both the
dev dependency and peer dependency to the specific prerelease or stable version
verified for the Vercel image API symbols createIPX, ipxHttpStorage, and
createIPXFetchHandler. Keep the dependency and peer ranges consistent with the
supported version.
In `@src/runners/vercel/image.ts`:
- Around line 492-523: Update the fallback call to fetchUnoptimized in handle so
it receives request.method and preserves HEAD semantics; ensure fetchUnoptimized
uses that method when issuing the upstream request and does not return a
response body for HEAD, matching the ipx path.
In `@test/vercel-image.test.ts`:
- Around line 443-458: Update the vercel-image test setup around loadIPX and the
“proxies the unoptimized source and warns once” test so the console.warn spy is
installed in a suite-level beforeAll before any test can trigger loading. Keep
the existing single-warning assertion and restore the spy in suite-level
cleanup, eliminating dependence on test order.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 78e979d7-71bf-41d4-81fa-e33f140d6f43
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (9)
.agents/VERCEL.mdAGENTS.mdpackage.jsonsrc/index.tssrc/runners/vercel/image.tssrc/runners/vercel/runner.tstest/fixtures/app-image.mjstest/vercel-image.test.tstest/vercel.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- test/fixtures/app-image.mjs
- AGENTS.md
- src/runners/vercel/runner.ts
| async function fetchUnoptimized( | ||
| sourceUrl: string, | ||
| getAddress: () => WorkerAddress | undefined, | ||
| config: VercelImageConfig | undefined, | ||
| maxAge: number, | ||
| ): Promise<Response> { | ||
| let res: Response; | ||
| try { | ||
| if (sourceUrl.startsWith("/")) { | ||
| const address = getAddress(); | ||
| if (!address) { | ||
| return new Response("Runner not ready", { status: 503 }); | ||
| } | ||
| res = await fetch(resolveWorkerUrl(address, sourceUrl)); | ||
| } else { | ||
| res = await fetch(sourceUrl); | ||
| } | ||
| } catch { | ||
| // Connection refused, DNS failure, aborted upstream | ||
| return new Response('"url" parameter is valid but upstream response is invalid', { | ||
| status: 502, | ||
| }); | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
The unoptimized fallback follows redirects, so the allowlist only gates the first hop.
fetch(sourceUrl) and fetch(resolveWorkerUrl(...)) use the default redirect: "follow". An allowlisted remote host can redirect to any host, including an internal address, and the response is then served under the app origin. The ipx path re-validates each hop through ipxHttpStorage({ domains }), so the two paths do not agree. blockPrivateIPs also has no effect here.
Set redirect: "error" on both calls, or follow redirects manually and re-validate each Location with validateRemoteUrl().
🛡️ Proposed fix
- res = await fetch(resolveWorkerUrl(address, sourceUrl));
+ res = await fetch(resolveWorkerUrl(address, sourceUrl), { redirect: "error" });
} else {
- res = await fetch(sourceUrl);
+ res = await fetch(sourceUrl, { redirect: "error" });
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/runners/vercel/image.ts` around lines 347 - 369, Update fetchUnoptimized
so both fetch calls use redirect: "error", preventing an allowlisted source or
worker URL from following an unvalidated Location to another host. Keep the
existing URL resolution, readiness handling, and upstream error response
unchanged.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
src/runners/vercel/image.ts (2)
364-378: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winConsider an upper bound for
wwhensizesis not configured.
wis only bounded whenconfig.sizesis set. With the default config,w=100000reaches ipx and sharp, which allocates a resize buffer proportional to the requested width. A few such requests can exhaust memory in the dev process.Vercel's endpoint rejects a
woutside the configuredsizes. Adding a hard ceiling keeps the default path safe without changing the configured behavior.♻️ Proposed cap
+// Vercel's largest documented device size; also the ceiling `sizes` entries fall under. +const MAX_WIDTH = 3840; +const width = Number.parseInt(w, 10); if (width <= 0) { return badRequest('"w" must be a positive integer'); } + if (width > MAX_WIDTH) { + return badRequest(`"w" must be less than or equal to ${MAX_WIDTH}`); + } if (config?.sizes?.length && !config.sizes.includes(width)) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/runners/vercel/image.ts` around lines 364 - 378, Update the width validation around the parsed width in the image handler to enforce a hard maximum when config.sizes is not configured, while preserving the existing config.sizes membership validation when sizes are provided. Reject values above the cap with the existing positive-integer validation response or an equivalent badRequest response.
473-501: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winPropagate an abort signal and a timeout to the fallback fetches.
Neither
fetch()call receives a signal or a deadline. If the upstream accepts the connection and then stalls,handle()never settles and the request hangs. A client abort also does not cancel the upstream fetch, so the socket stays checked out.
handle()already hasrequest.signal(the runner forwards it insrc/runners/vercel/runner.ts). Pass it intofetchUnoptimized()and combine it with a timeout. The same applies to theworkerStoragefetches at Lines 564 and 581.♻️ Proposed signal plumbing
+const UPSTREAM_TIMEOUT = 30_000; + async function fetchUnoptimized( sourceUrl: string, isLocal: boolean, getAddress: () => WorkerAddress | undefined, config: VercelImageConfig | undefined, maxAge: number, + signal?: AbortSignal | null, ): Promise<Response> { + const timeout = AbortSignal.timeout(UPSTREAM_TIMEOUT); + const abort = signal ? AbortSignal.any([signal, timeout]) : timeout; let res: Response; try { if (isLocal) { const address = getAddress(); if (!address) { return new Response("Runner not ready", { status: 503 }); } - res = await fetch(resolveWorkerUrl(address, sourceUrl)); + res = await fetch(resolveWorkerUrl(address, sourceUrl), { signal: abort }); } else { - res = await fetch(sourceUrl, { redirect: "error" }); + res = await fetch(sourceUrl, { redirect: "error", signal: abort }); } } catch {Then update the call site at Line 651 to pass
request.signal.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/runners/vercel/image.ts` around lines 473 - 501, Update handle() and fetchUnoptimized() to propagate request.signal into both fallback fetches, combining it with the existing timeout mechanism so stalled upstreams terminate and client aborts cancel sockets. Apply the same signal-and-timeout handling to the workerStorage fetches in handle(), and update the fetchUnoptimized call site to pass request.signal..agents/VERCEL.md (1)
92-92: 🚀 Performance & Scalability | 🔵 TrivialBound concurrent image encodes before production use.
The documented lack of a sharp concurrency limit allows many requests to allocate image-processing work simultaneously. Add a bounded queue or semaphore, or enforce an upstream concurrency limit before exposing this route to untrusted traffic.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.agents/VERCEL.md at line 92, Implement a bounded queue or semaphore to limit concurrent sharp image encode operations in the image processing route implementation. Once the concurrency control is in place, update the known gaps documentation to remove or replace the statement about no cap on concurrent sharp encodes, as this constraint will then be addressed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.agents/VERCEL.md:
- Line 76: Qualify the statement about reachable 400 responses in the
documentation to refer specifically to “IPX-originated 400s,” preserving the
existing explanation of IPX_INVALID_IMAGE and IPX_INVALID_SVG without implying
that endpoint validation cannot also return 400.
- Line 82: The documentation in the URL validation section lists the
remotePatterns matching fields (protocol, hostname, port, pathname) but omits
explanation of the search parameter that is included in the PR objectives. Add
documentation that clarifies how the search parameter in remotePatterns is
matched against query strings, and explicitly describe the behavior when search
is empty or missing from the configuration. If the search parameter is not
actually implemented, remove it from the PR objectives and align the schema and
tests accordingly instead of documenting it.
---
Nitpick comments:
In @.agents/VERCEL.md:
- Line 92: Implement a bounded queue or semaphore to limit concurrent sharp
image encode operations in the image processing route implementation. Once the
concurrency control is in place, update the known gaps documentation to remove
or replace the statement about no cap on concurrent sharp encodes, as this
constraint will then be addressed.
In `@src/runners/vercel/image.ts`:
- Around line 364-378: Update the width validation around the parsed width in
the image handler to enforce a hard maximum when config.sizes is not configured,
while preserving the existing config.sizes membership validation when sizes are
provided. Reject values above the cap with the existing positive-integer
validation response or an equivalent badRequest response.
- Around line 473-501: Update handle() and fetchUnoptimized() to propagate
request.signal into both fallback fetches, combining it with the existing
timeout mechanism so stalled upstreams terminate and client aborts cancel
sockets. Apply the same signal-and-timeout handling to the workerStorage fetches
in handle(), and update the fetchUnoptimized call site to pass request.signal.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 73523f32-893e-4f6b-918f-659da0da9a92
📒 Files selected for processing (4)
.agents/VERCEL.mdsrc/runners/vercel/image.tstest/vercel-image.test.tstest/vercel.test.ts
|
|
||
| ipx owns `content-type`, `etag` (weak, from the source `mtime` + modifiers when available, content-hashed otherwise), `if-none-match`/`if-modified-since` → `304`, `last-modified`, `content-security-policy: default-src 'none'` and `x-content-type-options: nosniff`. The handler then overrides `cache-control` (Vercel semantics, `minimumCacheTTL`), ensures `Vary: Accept`, applies the configured CSP/`content-disposition`, and buffers the body to set `content-length` (undici does not derive it from a `Uint8Array` body). | ||
|
|
||
| `content-disposition` is built by `contentDisposition()`: quotes and backslashes are stripped from the filename (they would terminate or escape the quoted value) and anything outside printable ASCII is replaced with `_`, with the real name carried in RFC 5987 `filename*` — appended only when it differs, so the common case stays byte-identical to what Vercel sends. The non-ASCII half is not cosmetic: `Headers.set()` throws a `TypeError` on any value above U+00FF, so a source such as `/日本.png` previously took the whole request down with an unhandled rejection rather than returning a `Response`. The filename is percent-decoded first, since a local source is normalized (and therefore encoded) by then. **ipx error bodies are re-stated, statuses are kept.** ipx answers failures with an `HTTPError` JSON body naming its own `IPX_*` code and the resolved source path (`{"status":404,"statusText":"IPX_RESOURCE_NOT_FOUND","message":"Resource not found: /sample.jpg"}`). The statuses are already right — better than a blanket 500 — so `IPX_ERROR_MESSAGES` maps each one onto the plain-text message the rest of the endpoint uses, keeping the status: `404`/`502` → `"url" parameter is valid but upstream response is invalid`, `403` (forbidden host/IP) → `"url" parameter is not allowed`, `400` → `"url" parameter is valid but upstream is not an image`, anything else → `Image optimization failed`. That keeps the ipx and unoptimized-fallback paths answering alike and stops ipx internals reaching the client. The only reachable `400`s are undecodable sources (`IPX_INVALID_IMAGE`, `IPX_INVALID_SVG`) — the modifiers ipx receives are just width/quality/format, all validated by `parseImageRequest()` first. A throw escaping ipx's own error handling is not expected, so it is logged once via `console.warn` before the body is normalized the same way. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Qualify the 400 status statement.
“The only reachable 400s” conflicts with the documented 400 responses for missing or disallowed URLs, blocked SVG, and other request validation failures. Change this to “The only IPX-originated 400s” if that is the intended scope.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.agents/VERCEL.md at line 76, Qualify the statement about reachable 400
responses in the documentation to refer specifically to “IPX-originated 400s,”
preserving the existing explanation of IPX_INVALID_IMAGE and IPX_INVALID_SVG
without implying that endpoint validation cannot also return 400.
|
|
||
| **Local sources are normalized before they are validated**, and the normalized form is what becomes the fetch id (`new URL(sourceUrl, "http://localhost")` → `pathname + search`). Validating the raw query-param string while fetching it through a URL parser meant the two disagreed, and `localPatterns` could be walked out of: `/assets/../secret.png` lexically satisfies a `/assets/**` rule and then resolves to `/secret.png` on the worker (which the same config denies when asked for directly), and the hand-rolled `split("?")` hid everything past a second `?` from a `search` rule, so `/a.png?v=1?evil=2` passed `search: "?v=1"` and was then fetched with both query strings. The invariant is now that the allowlist sees exactly the path the worker is asked for. Percent-encoding is deliberately left intact rather than decoded, for the same reason (`/assets/..%2Fx` is forwarded as written, so whatever the worker does with it is what was checked) — a `localPatterns` glob therefore matches the encoded pathname, as it does in Next.js. | ||
|
|
||
| **URL validation:** Remote sources are **default-deny**, matching Vercel — with neither `domains` nor `remotePatterns` configured, every remote `url` is rejected with 400 `"url" parameter is not allowed`. Allowing them by default would both diverge from production and turn the dev server into an open image proxy. Configured remotes are matched against `domains` (exact hostname) and `remotePatterns` (protocol, hostname glob/regex, port, pathname glob/regex). Local paths are allowed unless narrowed by `localPatterns`. Compiled patterns are cached in a module-level `Map` since they are re-tested on every request. SVG sources are blocked by default (400) unless `dangerouslyAllowSVG` is true. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Document remotePatterns.search.
The PR objectives include search in remotePatterns. This section lists protocol, hostname, port, and pathname, but omits query-string matching. Document how search is matched and how empty or missing values behave. If it is unsupported, align the schema and tests instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.agents/VERCEL.md at line 82, The documentation in the URL validation
section lists the remotePatterns matching fields (protocol, hostname, port,
pathname) but omits explanation of the search parameter that is included in the
PR objectives. Add documentation that clarifies how the search parameter in
remotePatterns is matched against query strings, and explicitly describe the
behavior when search is empty or missing from the configuration. If the search
parameter is not actually implemented, remove it from the PR objectives and
align the schema and tests accordingly instead of documenting it.
Adds Vercel image optimization route handling to the Vercel env-runner preset, using IPX for image processing.
Usage
ipx is needed otherwise no image transformations are done.
Without
imagesconfig, local paths are allowed with sensible defaults (format negotiation viaAcceptheader, 60s cache TTL), but remote URLs are rejected, matching Vercel's default-deny behavior. ConfiguredomainsorremotePatternsto allow specific remote hosts.The
imagesoption accepts the same shape as theimagesproperty in.vercel/output/config.json, so framework presets can forward it directly:Supported query parameters
urlwqThese are the only params the real
/_vercel/imageendpoint honors, and every other query param is ignored to match it, so there is deliberately noh/fit/blur/cache, and no format-pinning param either.Config options (
VercelImageConfig)Matches the Build Output API
ImagesConfig. Every field is optional, but omitting one does not always widen what is allowed:sizes,localPatterns,qualitiesandformatsare unrestricted when unset, whiledomains/remotePatternsdeny every remote source until configured,dangerouslyAllowSVGblocks SVG, andblockPrivateIPsdefaults totrue.sizesnumber[]domainsstring[]remotePatternsRemotePattern[]protocol,hostname,port,pathname,searchlocalPatternsLocalPattern[]pathname,searchqualitiesnumber[]formatsstring[]minimumCacheTTLnumberdangerouslyAllowSVGbooleancontentSecurityPolicystringcontentDispositionTypestringblockPrivateIPsbooleantrue)Pattern fields (
hostname,pathname) accept both formats:"^cdn\\.example\\.com$","^/assets/.*$""**.example.com","/assets/**"Summary by CodeRabbit
Summary by CodeRabbit