Ten years ago, most of the security-relevant code in a web app was written by the team that shipped it. Today a large share of it lives in the framework, the SDK and the renderer. Middleware decides who gets through. Server components fetch data before the browser sees a byte. A headless-CMS SDK decides which URLs the server will call on your behalf. That code is shared across thousands of deployments, which is why a flaw in it is worth more than a flaw in any single app.
I test a lot of server-rendered stacks and headless-CMS front ends, and this year I was the credited finder of a server-side request forgery (SSRF) issue in one such SDK. This post is how I approach framework-level bugs: where the logic lives now, two public cases that make the lessons concrete, and a repeatable way to test middleware and server-side fetchers — generic placeholders and public-record facts only.
Where the logic went
To find these bugs you first have to notice that the interesting decisions moved. In a modern stack, four layers do work that used to sit in your controllers:
- Middleware runs before your route handlers — where people put authentication, redirects and header rewriting. Because it sits in front of everything, a bug here is an authorization bug for the whole app.
- Server components / server-side rendering (SSR) run your rendering code on the server. They often fetch data, images or layout fragments before responding, using URLs that may be derived from the request.
- SDKs from a CMS or component vendor ship pre-written fetchers, parsers and renderers. You import them and trust them.
- Headless-CMS renderers turn a content model into pages, frequently resolving references (images, embeds, linked pages) by making their own HTTP requests.
The common thread: the server now makes outbound requests and trust decisions on the user's behalf, using data that may be user-influenced. That is the soil SSRF and authorization bypasses grow in.
Case study: the Next.js middleware bypass (CVE-2025-29927)
The clearest public lesson on middleware is CVE-2025-29927, disclosed in March 2025. Next.js used an internal header, x-middleware-subrequest, to tell its own middleware "this is an internal subrequest, do not recurse." The problem: that header was trusted when it arrived from outside. An attacker who set it on an inbound request could make the framework skip middleware execution entirely — and if your authorization check lived only there, it was skipped too.
GET /admin/dashboard HTTP/1.1
Host: example.com
x-middleware-subrequest: middleware
The specific values and matching logic are in the public advisory and I will not reproduce exploit detail. The lesson for a tester outlives this one CVE:
People assume that because middleware runs "in front of everything," it is a reliable gate. Actually, middleware is reliable only for requests that are routed through it — and a framework that trusts a request-controlled signal to decide whether to run middleware has handed the attacker the key.
The durable takeaway: authorization belongs next to the resource, not only at the perimeter. Middleware is a fine place for a cheap first check and a bad place for the only check. Defense in depth means the route handler or data layer re-verifies the caller, so skipping the outer gate buys nothing.
When I test a stack with middleware-based auth, I ask one question first: if I could make the middleware not run, what would still stop me? If the honest answer is "nothing," that is the finding, independent of any particular header trick.
SSRF in SSR renderers
SSRF is server-side request forgery: you get the server to make a request to a destination you choose, abusing the server's network position — its access to internal services, cloud metadata endpoints and localhost that you, as an external client, cannot reach. It is catalogued as CWE-918 and sits in the OWASP API Security Top 10 2023 as API7.
SSR renderers are a natural home for it. A renderer that resolves an image URL, a layout fragment, an oEmbed, or a "preview this link" feature is, by design, a thing that takes a URL and fetches it. If any part of that URL is request-controlled and the destination is not constrained, you have SSRF.
A public example of the class is CVE-2026-92931, an SSRF in the @progress/sitefinity-nextjs-sdk npm package (the Next.js Renderer SDK for Progress Sitefinity). The CVE record, published 2026-10-05, describes it plainly: "Remote attackers may be able to make server-side requests to attacker-controlled hosts, potentially exposing sensitive information." It carries CVSS 3.1 score 8.8 (High), vector AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H. Affected versions are 15.1.8326 through 15.4.8637; the fix is 15.4.8638. It was reported through the vendor's Bugcrowd vulnerability disclosure program, and Progress, a CVE Numbering Authority, assigned the ID. I am citing only the public record; proof-of-concept specifics are withheld.
I lead with that record rather than a payload because the shape is what repeats across renderers: a server-side component dereferences a URL that traces back to the request, and the destination is not restricted to hosts you actually intend to call.
How I test SSR fetchers
The method is the same whether the fetcher is yours or an SDK's. Stand up a listener you control — any host that logs inbound connections — then try to make the server reach it.
1. Find the sinks. Look for request-controlled values that end up in a server-side fetch: image URLs, ?url= / ?next= / ?redirect= parameters, webhook or callback fields, "import from URL", link-preview or oEmbed features, PDF/screenshot generators, and any CMS field that stores a URL the renderer later resolves.
2. Point it at yourself first. The cleanest proof is a DNS or HTTP hit on infrastructure you own. No internal data is touched; you have simply shown the server will call an arbitrary external host.
POST /api/preview HTTP/1.1
Host: example.com
Content-Type: application/json
{ "imageUrl": "http://listener.example.net/ssrf-canary" }
If listener.example.net records the request, the server fetches attacker-chosen URLs — often reportable on its own.
3. Manipulate scheme, host and port. Once a hit lands, probe what the fetcher allows: dangerous schemes (file://, gopher://) if the client follows them; internal hosts (localhost, 127.0.0.1, ::1) including decimal/octal/IPv6 encodings of loopback that weak filters miss; and internal services on non-standard ports.
4. Try cloud metadata. On cloud hosts, the link-local endpoint 169.254.169.254 can expose instance identity and, on misconfigured setups, credentials. A read here turns an SSRF into something far more serious, so I report it explicitly.
GET /api/fetch?url=http://169.254.169.254/latest/meta-data/ HTTP/1.1
Host: example.com
5. Defeat naive filters. Two tricks separate a real filter from a decorative one. Redirects: point the fetcher at a URL on your host that returns a 302 to an internal address — a filter that validates only the first URL then follows the redirect is bypassed. DNS rebinding: a hostname that resolves to a public IP at validation time and an internal IP at fetch time, defeating check-then-use filters because the two lookups see different answers.
The misconception worth naming: blocking 127.0.0.1 and localhost is not SSRF protection. The internal address space is enormous, encodings are many, and redirects and rebinding sidestep string checks entirely. Deny-lists lose; allow-lists win.
Headers, caches and poisoning
SSR interacts badly with caches when the server reflects request headers into responses. Two things to check:
- Host header handling: if the app builds absolute URLs (password-reset links, canonical tags, SSR fetches) from an untrusted
HostorX-Forwarded-Host, you can steer where the server points. Confirm it uses a configured base URL, not the inbound header. - Cache key correctness: if an unkeyed header influences the response body, a poisoned response can be served to other users (web cache poisoning). Vary a header, check whether it changes the body, and whether that change persists for a second, clean request.
Dependency hygiene for frontend SDKs
Framework CVEs are patched upstream, so your exposure is mostly a function of how fast you adopt fixes. Treat your SDKs like the privileged server code they are: pin versions, commit your lockfile, watch the CVE feed for every server-side SDK, and prioritise a renderer SDK's security release the way you would a backend CVE — it is backend code.
npm ls @progress/sitefinity-nextjs-sdk # what am I running?
npm ls next
npm audit # what does the ecosystem flag?
Tester's checklist
- [ ] Map every place the server makes an outbound request; trace each URL back to the request.
- [ ] If auth lives in middleware, confirm a second check exists at the resource.
- [ ] Prove SSRF with a listener you own before touching anything internal.
- [ ] Test scheme, host (loopback encodings), port, redirects and DNS rebinding against any fetcher.
- [ ] Check cloud metadata reachability explicitly and report it as its own impact.
- [ ] Verify absolute URLs come from configured base URLs, not the inbound
Hostheader. - [ ] Confirm every response-influencing header is part of the cache key.
- [ ] Run
npm lsandnpm audit; pin versions; track advisories for server-side SDKs.
Fixes / defence
For middleware auth: keep a check in middleware for speed, but re-authorize at the route handler and data layer. Never let a request-influenced signal decide whether security code runs. Upgrade promptly when the framework patches a bypass.
For SSRF in fetchers: validate destinations against an allow-list of permitted hosts, resolve the hostname once and connect to that pinned IP (closing the rebinding gap), block link-local and private ranges after resolution, constrain redirects, and restrict schemes to what you need. Give the outbound client the least network reach it can do its job with — a renderer that only needs your CDN should not be able to reach 169.254.169.254 at all.
For host/cache issues: configure an explicit canonical base URL, ignore untrusted forwarding headers unless a trusted proxy sets them, and ensure every header that varies the response is in the cache key.
Takeaways
- The logic — and the bugs — moved into frameworks, SDKs and renderers. Test those layers as first-class attack surface, not trusted glue.
- Perimeter authorization is a convenience, not a guarantee. Put the real check next to the resource so skipping the gate buys nothing (the lesson of CVE-2025-29927).
- Anything that turns a request-influenced value into a server-side fetch is a candidate for SSRF. Prove it safely against your own listener, then measure how far inward it reaches.
- Deny-lists of
localhostare theatre; allow-list destinations and pin resolved addresses. And pin, audit and patch server-side frontend SDK versions like the backend code they are (CVE-2026-92931).
Learn more
- OWASP API Security Top 10 2023 — API7:2023 Server Side Request Forgery (owasp.org)
- MITRE CWE-918: Server-Side Request Forgery (cwe.mitre.org)
- OWASP Web Security Testing Guide — Testing for SSRF (owasp.org)
- cve.org — look up CVE-2025-29927 and CVE-2026-92931 for the primary records
Related: Four CVEs in Progress Software Products · BOLA, Mass Assignment and the API Bugs That Still Pay in 2026. Need this tested on your product? Book a free 30-minute scoping call.



