NestJS and Astro route, render, and image-fetch boundary checks¶
Sources: hourly offensive-security scan, 2026-07-18 GitHub Security Advisory updates. Primary entries: NestJS GHSA-6v32-fjc9-9qf6 / CVE-2026-54281, Astro GHSA-jrpj-wcv7-9fh9 / CVE-2026-54298, and @astrojs/netlify GHSA-529g-xq4f-cw38 / CVE-2026-54300.
This batch is durable for operators because it exposes three reusable framework-integration boundaries: middleware matching a different route representation than the application handler, attacker-controlled object keys becoming server-rendered HTML attribute names, and deployment adapters translating host/path allowlists into broader regular expressions.
Authorized validation only
Use disposable NestJS/Astro applications, synthetic users and records, owned image origins, and harmless DOM/callback markers. Do not bypass authentication on production routes, collect sessions or user data, target internal services, or deliver executable browser payloads to real users.
What changed¶
| Advisory | Component | Boundary | Operator value |
|---|---|---|---|
| GHSA-6v32-fjc9-9qf6 / CVE-2026-54281 | @nestjs/platform-fastify middleware registered with MiddlewareConsumer.forRoutes() | The default Fastify adapter can route /resource/ to an application handler while Nest middleware registered for /resource does not run | Add trailing-slash and canonical-path variants to authentication/authorization middleware matrices. |
| GHSA-jrpj-wcv7-9fh9 / CVE-2026-54298 | Astro spreadAttributes() / addAttribute() | Spread-object values are escaped, but untrusted object keys can cross into unvalidated HTML attribute names during SSR, SSG, or hybrid rendering | Trace both keys and values from API/CMS/URL data into spread props; prove the key-to-markup transition with inert attributes. |
| GHSA-529g-xq4f-cw38 / CVE-2026-54300 | @astrojs/netlify conversion of image.remotePatterns | Adapter-generated Netlify Image CDN regexes can make a single-subdomain wildcard include the apex host and make a single-segment path wildcard match deeper paths | Compare source-framework allowlist decisions with built deployment artifacts and the live CDN fetch decision. |
Affected ranges in the advisory records are @nestjs/platform-fastify <= 11.1.23, astro < 6.4.6, and @astrojs/netlify < 7.0.13. Confirm the deployed package and adapter versions before testing; do not infer exposure from a framework banner alone.
Replayable validation boundaries¶
NestJS trailing-slash middleware matrix¶
- Build a disposable NestJS application with the Fastify adapter, one synthetic protected resource, and middleware registered through
MiddlewareConsumer.forRoutes()for the normal CRUD route shape. - Make the middleware add a harmless response header or append a nonce to a lab-only log before it applies the synthetic authentication decision.
- Send equivalent requests to
/resource,/resource/,/resource/canary, and/resource/canary/. Keep method, headers, query, and body constant. - Record the application handler reached, middleware marker present or absent, authentication result, status, and response-body canary for each representation.
- Repeat against the fixed package and with route-level guards as negative controls. Distinguish a middleware omission from an ordinary router 404 or redirect.
Report this as request path representation -> framework middleware matcher -> application router -> protected handler. Strong evidence is a decision table showing that only a canonicalization variant reaches the same handler without the middleware marker.
Astro spread-key rendering checks¶
- Identify Astro templates that spread an object onto native HTML, for example
<div {...record}>, and trace whether an API, CMS field map, database JSON object, or URL-derived object can control its keys. - In a local SSR or build harness, supply a benign unusual key such as
data-skillz-canaryand confirm it becomes an attribute. This proves key control without script execution. - Use a quote-containing inert key that attempts to introduce only a second
data-*attribute. Compare source data, raw rendered HTML, and the browser-parsed DOM. - Test value-only control separately. A safely escaped value does not disprove attribute-name injection when the object key follows another path.
- Repeat on the fixed Astro release and with a static allowlist of permitted prop names.
Report this as untrusted object key -> spread props -> server-rendered attribute name -> browser DOM marker. Do not use cookie access, credential forms, keyloggers, or executable event handlers in wiki or customer-facing evidence.
Astro-to-Netlify image allowlist differential¶
- Create an Astro/Netlify lab with one owned apex origin, one owned single-level subdomain, and canary image paths at
/ok/a.svg,/ok/a/b.svg, and/nope/a.svg. - Configure
image.remotePatternswith a single-level host wildcard and a single-segment path wildcard. Record Astro's canonical helper decision for every host/path pair. - Build the deployment and inspect
.netlify/v1/config.json. Save the generatedimages.remote_imagesexpression as evidence; do not rely only on source configuration. - Request each encoded canary URL through the lab Netlify Image CDN route and correlate responses with owned-origin callback logs.
- Positive evidence is a mismatch such as apex-host or deeper-path rejection by Astro's matcher but acceptance by the generated CDN policy. Include patched-adapter output as a negative control.
Report this as framework allowlist -> adapter regex translation -> deployment CDN matcher -> owned origin fetched. Keep all hosts under researcher control; do not probe loopback, metadata, private networks, or third-party origins.
Operator checklist¶
- Did route testing hold method, body, query, and identity constant while varying only path representation?
- Did middleware evidence distinguish handler execution from redirects and 404s?
- Did spread-prop testing trace object keys separately from object values and use inert DOM markers?
- Did image testing compare source matcher, generated deployment config, live CDN decision, and callback logs?
- Did every proof remain inside disposable apps and owned origins?
July 20 follow-up: View Transition values crossing CSS and HTML contexts¶
GHSA-4g3v-8h47-v7g6 adds a separate Astro rendering boundary. In affected Astro releases before 7.1.0, server-generated View Transition styles can place string animation properties into a raw <style> element without making them safe for both CSS serialization and the HTML raw-text context. An application is not exposed merely because it uses View Transitions: attacker-controlled data must reach a property such as duration, easing, direction, delay, fillMode, or name in an SSR/on-demand render path.
Replayable transition-style check¶
- Find
transition:animateuse and trace every animation property back through URL parameters, CMS fields, API objects, database values, and frontmatter. Record whether the page is server-rendered, hybrid, or prerendered. - In a disposable Astro app, pass an ordinary canary string through one property and capture the raw response. Confirm which generated style declaration contains it before testing context boundaries.
- Use a non-executable delimiter canary containing a closing-style sequence followed only by a harmless custom element or
data-*marker. Compare the response bytes with the browser-parsed DOM; do not use script, event handlers, cookie access, or credential forms. - Hold route and template constant while testing numeric values, normal CSS time strings, delimiter canaries, and the same inputs on Astro
7.1.0or later. - Positive evidence is attacker-controlled data terminating the generated style element and creating the inert sibling marker. A value merely appearing inside a valid CSS declaration is not enough.
Report this as untrusted animation property -> CSS builder -> raw HTML style element -> browser parser context break. Keep it distinct from the earlier spread-key issue: one controls object keys serialized as HTML attributes; this one controls animation values serialized through CSS into an HTML raw-text element.
Not promoted from the same updated wave¶
GHSA-c43c-rf7g-5xpg (Katello cross-product content-existence disclosure) and GHSA-j4h6-gcj7-7v9v (an older Decidim meeting-embed XSS record) were marked processed without standalone guidance. The available details did not add a stronger reusable workflow beyond existing object-authorization and trusted-render boundary pages.
July 20 follow-up: decode-depth authorization mismatch¶
GHSA-vj59-8hwv-xxmv adds a second Astro route representation bug, narrowly affecting astro >=6.4.7,<6.4.8. An iterative path decoder stops after ten passes and returns a partially decoded pathname to middleware; a later rewrite/router decode can still resolve that value to the protected canonical route. The exploitable application pattern is path-based authorization followed by next(context.url) or an equivalent rewrite path—not every Astro deployment.
Add an encoding-depth matrix to the route workflow:
- Use a disposable protected route and make middleware emit the pathname it authorized plus a harmless decision nonce.
- Generate nested percent-encoding depths around one character in that route, from zero through at least one step beyond the framework cap. Generate these locally rather than copying a production path.
- For each request, record the raw request target, middleware pathname, final matched route, auth decision, and response canary. Hold identity, method, query, and body constant.
- Include direct canonical, malformed-escape, no-rewrite, and Astro
6.4.8+controls. Stop once the middleware and router disagree; do not retrieve protected data.
Positive evidence is raw nested encoding -> middleware sees a partially decoded non-protected path -> later rewrite performs another decode -> the protected canary handler runs. Distinguish this decode-depth issue from the trailing-slash NestJS case and from ordinary proxy normalization. Capture every representation at each hop so the report identifies where the extra decode occurs.
July 20 late follow-up: adapter regex, path, spread-key, RSS, and transition contexts¶
Six newly published Astro records extend the same representation-first workflow:
| Advisory | Boundary to add |
|---|---|
| GHSA-hp3v-mfqw-h74c | @astrojs/netlify failed to escape pathname regex metacharacters while generating the Netlify Image CDN allowlist. Add owned paths containing literal regex-significant characters and compare source matcher, generated config, and live fetch. |
| GHSA-r557-wffq-wvrc | @astrojs/node trailing-slash redirect logic did not classify backslash-prefixed paths like the final router. Add raw slash/backslash and encoded-separator variants to redirect-before-middleware/auth decision tables. |
| GHSA-f48w-9m4c-m7f5 | renderHTMLElement retained an unescaped spread-attribute-name path after the earlier Astro fix. Exercise framework/component rendering paths separately; a patched helper elsewhere is not a negative control for this sink. |
| GHSA-8j5q-mfj2-5q9q | @astrojs/rss fields crossed into generated XML without context-safe escaping. Compare source value, raw feed bytes, strict XML parse tree, and consuming browser/feed-reader DOM with inert elements only. |
| GHSA-7pw4-f3q4-r2p2 | transition:* directive values on hydrated islands crossed into generated HTML attributes. Keep this separate from raw <style> transition properties and prove only an inert parsed-DOM marker. |
| GHSA-8mv7-9c27-98vc | A composable astro/hono pipeline could omit or misorder middleware that enforces security.checkOrigin. Compare state-changing canary routes with middleware present, absent, and ordered around the handler. |
For the Node adapter case, use a lab route where redirect and authorization behavior are visible through nonces; test the raw request target through the same proxy topology as the deployment. Stop when a backslash representation makes the redirect layer and router disagree—do not retrieve protected content. For RSS, use a disposable feed and harmless namespaced/custom-element markers; XML text appearing verbatim is not impact unless it changes the parsed tree or the actual consumer's DOM. For both spread-key variants, trace attacker control of the key, not merely the value.
Report each sink precisely: source path pattern -> generated CDN regex, raw separator form -> adapter redirect/auth order -> router, object key -> specific renderer -> HTML attribute grammar, feed field -> XML grammar -> consumer DOM, transition directive value -> island attribute -> browser DOM, or pipeline composition/order -> origin-check middleware absent -> inert state change accepted. For the Hono integration, use fake sessions and a marker-only state transition from an owned foreign origin; distinguish browser request delivery from response readability. Do not merge these into a generic Astro XSS/auth-bypass claim.
August 7 follow-up: Vercel ISR path override¶
GHSA-x27w-589x-frm2 adds a route-authority regression in @astrojs/vercel >=10.0.3,<11.0.3, fixed in 11.0.3. With isr: true, the public /_isr function could take x_astro_path from the query string and render that route at the origin without the middleware secret. Edge path rules and split edgeMiddleware: true saw only /_isr, while the origin rendered the selected path. Classic middleware running at the origin, whole-deployment protection, and non-GET methods are separate controls; do not generalize this to every Astro deployment or to state changes.
Use a disposable Vercel-adapter fixture with one public marker page and one GET-only protected marker page:
- Patch the origin renderer to record the raw request path, edge-visible path,
x_astro_pathvalue, rewritten origin path, middleware invocation, and selected route, then return only a random marker. - Compare direct protected-route requests with
/_isrrequests selecting the public marker, protected marker, nonexistent route, encoded separators, duplicate selectors, and absolute-URL-shaped values. Do not place user, billing, session, or tenant data in either page. - Test edge path rules, split edge middleware, classic origin middleware, whole-deployment authentication, ISR disabled, and
@astrojs/vercel 11.0.3+independently. - Keep cache state visible, but do not require a cache hit: the reported boundary can produce a fresh origin render. Stop at the patched route recorder rather than returning protected content.
A bounded positive is external GET reaches public /_isr -> client-selected path bypasses the secret-bound path channel -> edge authorizes only /_isr -> denied origin renderer selects the protected canary route. Report route-selection authority and GET confidentiality separately from caching, middleware topology, or state-changing impact.
October 2 follow-up: absolute-form request target skips path-scoped Nest middleware on the Fastify adapter (GHSA-9c5c-9qcx-q35q)¶
Source: GHSA published 2026-09-30 14:41Z (no CVE assigned at scan time). @nestjs/platform-fastify >= 12.0.0, < 12.0.2 and < 11.2.4, fixed 12.0.2 / 11.2.4 (recommend 12.0.3 / 11.2.5). A request line using the absolute-form request target — GET http://host/path HTTP/1.1 instead of GET /path HTTP/1.1 — reaches the route handler without running the path-scoped Nest middleware bound via MiddlewareConsumer.forRoutes(...) or .exclude(...). Node's HTTP server accepts absolute-form targets natively, so no server misconfiguration is required; where that middleware performs authentication or authorization, the protected handler executes with those checks skipped. Exposure shrinks when a fronting reverse proxy rewrites the request target to origin-form — which most do — so this is primarily a direct-to-origin / passthrough-proxy finding.
This is the same page's founding invariant (middleware matches a different route representation than the handler) reached through the HTTP grammar instead of a path transform: the middleware matcher keys on the normalized path, the router still dispatches correctly, and the two representations diverge at the request-line layer. RFC 9110 allows five request-target forms (origin, absolute, authority, asterisk, CONNECT-authority) but most middleware matchers only ever consider origin-form.
Replayable validation boundaries:
- On an owned Fastify-adapter Nest app with one marker route protected only by path-scoped middleware, send the identical request as origin-form
GET /marker HTTP/1.1and absolute-formGET http://<host>/marker HTTP/1.1(raw socket orcurl --request-target). A positive is the middleware log/nonce absent while the handler responds. - Re-run the battery per form (authority-form, asterisk) and per proxy topology (raw origin vs. each fronting proxy in the deployment) — record which hop normalizes the target; that hop is the mitigation boundary.
- Pair with the sibling spellings already on this page (Node-adapter backslash form,
/_isrx_astro_path) when sweeping any framework: enumerate every route-representation channel the stack accepts — separator forms, encoding forms, request-target forms, header-carried paths — and test middleware/router agreement on each.
Report shape: raw request-line form -> middleware matcher representation -> router dispatch representation -> skipped guard -> marker handler response. Proofs stay at a marker handler on owned apps; never retrieve protected content or bypass guards on third-party hosts.