SvelteKit is a popular full-stack JavaScript framework, and Vercel is its most common deployment platform. What if we told you that all apps built using this combination were vulnerable to attackers reading responses from any route of other signed-in users?
Well, it’s true. This attack vector, called cache deception, is exactly what one of the AI agents found and reported to us while we were testing Aikido Attack. And while initially skeptical, we retraced its steps and found we could reproduce the vulnerability perfectly. We quickly notified Vercel, and the vulnerability has now been automatically fixed for all users.
Note: The issue can be viewed in Aikido's Intel database and has the reserved CVE number of: CVE-2026-27118. We also found a separate issue that allows for Denial of Service in an experimental feature on SvelteKit. This has also been disclosed and fixed.
Quick Summary
The __pathname query parameter in SvelteKit's Vercel adapter can override the path from anywhere. On Vercel, every file under /_app/immutable/ has different Cache-Control: headers so it can be cached. By prefixing this with a fake path and rewriting it to contain sensitive content, the response will be forcefully cached, allowing an attacker to retrieve it by visiting the same URL without cookies.
Proof of Concept URL:
https://example.vercel.app/_app/immutable/x?__pathname=/api/session
Discovery
We will tell this story from the perspective of the AI pentest agent that found the vulnerability, starting from the initial gadget to full exploitation, and we’ll talk about some interesting ideas along the way. Embrace your inner robot and let’s get researching!
If you read the SvelteKit source code, you’ll come across some “adapters”, which are the middleware between a hosting platform like Vercel and the framework SvelteKit. This makes it possible to apply some special rules to the request & response required for the platform’s internals. Vercel has implemented the following in serverless.js:
const DATA_SUFFIX = '/__data.json';
fetch(request) {
// If this is an ISR request, the requested pathname is encoded
// as a search parameter, so we need to extract it
const url = new URL(request.url);
let pathname = url.searchParams.get('__pathname');
if (pathname) {
// Optional routes' pathname replacements look like `/foo/$1/bar` which means we could end up with an url like /foo//bar
pathname = pathname.replace(/\/+/g, '/');
url.pathname = pathname + (url.pathname.endsWith(DATA_SUFFIX) ? DATA_SUFFIX : '');
url.searchParams.delete('__pathname');
request = new Request(url, request);
}
return server.respond(request, {
getClientAddress() {
return /** @type {string} */ (request.headers.get('x-forwarded-for'));
}
});
}
We can read “If this is an ISR request, the requested pathname is encoded as a search parameter.” The code takes this pathname variable (from the ?__pathname= query parameter) and rewrites the url.pathname with it. The request ignores the original url and just uses this __pathname instead.
But there don’t seem to be any checks regarding these Incremental Static Regeneration (ISR) requests, so do all requests support this parameter?
The answer is yes, and that’s exactly where the vulnerability lies! Any path can be overridden to any other path with just a query parameter. A simple test like /?__pathname=/404 would indeed show a 404 error instead of the homepage.

This may just sound like a strange feature. Why would it be dangerous? Well, it’s not, until caching gets involved.
Cache Poisoning?
If we can rewrite any path to point to any other resource, what happens if that poisoned resource gets cached? This was our first idea as well. If we check the source code of any SvelteKit app, you’ll see something like:
import("./_app/immutable/entry/start.CLO1Dlt2.js"),
import("./_app/immutable/entry/app.kQF6jJr8.js")
The path /_app/immutable/entry/start.CLO1Dlt2.js returns some JavaScript to execute, and looking at the response, Cache-Control: headers tell us it is indeed cached. This is expected for static resources:
Age: 618
Cache-Control: public, immutable, max-age=31536000
X-Vercel-Cache: HIT
If we could use our ?__pathname= parameter to rewrite the request to point to some attacker-controlled path like a file upload, and it would still be cached under the same plain /_app/immutable/entry/start.CLO1Dlt2.js path that every user loads, we’d have XSS on every user. Let’s see what happens when we add this query parameter to our request:
GET /_app/immutable/start.CLO1Dlt2.js?__pathname=/ HTTP/2
Host: example.vercel.app
In the response, we receive good and bad news from the headers:
Age: 935
Cache-Control: public, immutable, max-age=31536000
X-Vercel-Cache: HIT
The Age: has increased by however long it took to write the above paragraph, and the HIT signifies that with our query parameter added, the same cache entry is hit as the one everybody loads. But at the same time, that means the old JavaScript content is also returned, not the content on our rewritten path. If that’s the case, might we be able to flush the cache and then be the first to request it so our payload can be cached instead?
Unfortunately not. The Vercel platform is more complicated because of its serverless architecture, and it doesn’t actually even hit the adapter code for such a static resource. This is because static assets are cached and returned on a higher layer, so we will never be able to poison them.
In this case, Vercel itself is the only vulnerable platform, so we can’t find other exploitable scenarios. We hit a dead end.
Cache Deception!
On to the next problem with web caching: cache deception. In this lesser-known technique, an attacker redirects a victim to some sensitive page that they can retrieve with their cookies. If the cache stores this page without considering which user requested it, the attacker can later visit the same URL and see the victim's private data from the cache. The issue is that cache saves responses based only on the URL, ignoring that different users with different login cookies should see different content.
Applying this idea to our ?__pathname= gadget, we can make our URL look like it points to a static path, but actually points to sensitive content. The caching layer may trigger on that static-looking path and add explicit Cache-Control: headers and override the private response.
While some cache criteria are documented, our investigation showed more rules. One we’re already familiar with: paths starting with /_app/immutable/. It turns out not only are the expected static files cacheable under this prefix, but any 200 OK response. To avoid the already-generated assets, we can initially point to a fake asset. Then rewrite it to any sensitive path, let’s say /api/session:
https://example.vercel.app/_app/immutable/x?__pathname=/api/session
And there we have our final payload. Visiting this link as a logged-in victim will send a request like the following:
GET /_app/immutable/x?__pathname=/api/session HTTP/2
Host: example.vercel.app
Cookie: auth=...
SvelteKit’s Vercel adapter rewrites the pathname to /api/session, and gets handled by the app. The auth= cookie is verified, and their session token is returned:
HTTP/2 200 OK
Age: 0
Cache-Control: public, immutable, max-age=31536000
...
Server: Vercel
X-Vercel-Cache: MISS
Content-Length: 16
{"token":"1337"}While the Cache-Control: header would normally say max-age=0 for this endpoint, the caching layer of Vercel forcefully enables caching because of the /_app/immutable/ prefix.
Once the attacker knows a victim has been redirected, they can request the same URL themselves, without any cookies:
GET /_app/immutable/x?__pathname=/api/session HTTP/2
Host: example.vercel.appThey receive the same response as a HIT now, and can read the victim’s token.
HTTP/2 200 OK
Age: 22
Cache-Control: public, immutable, max-age=31536000
...
Server: Vercel
X-Vercel-Cache: HIT
Content-Length: 16
{"token":"1337"}
Using cache busters (unique dummy paths) and checking after redirecting, this could have been exploited on a large scale. Since this vulnerability is just part of the base Sveltekit, any SvelteKit website on Vercel that uses cookies for authentication would allow arbitrary responses to be retrieved.
The aftermath
We quickly reported the issue to Vercel, who came up with a fix to forcefully return 404 on any /_app/immutable/ path, and to strip the __pathname parameter. Since they control their whole platform, this resolves the issue automatically for all users, no manual patch required.
One big takeaway from this vulnerability is that caching is always tricky. Simple rules, like a prefix match, can be taken advantage of by unexpected platform features you didn’t even implement.
That’s why pentesting can be so useful. Issues like this would be hard to discover by code alone, but through a real deployment, hidden rules can be found and exploited. Aikido’s AI pentest can detect cache poisoning and cache deception attacks automatically, like it found this issue.
Key Takeaways
- On January 20, 2026, Aikido’s AI pentest system flagged something interesting in SvelteKit applications deployed on Vercel.
- We confirmed it was a cache deception vulnerability affecting default configurations.
- No misconfiguration required. Just SvelteKit + Vercel doing what they're supposed to do.
- Impact: Authenticated responses can be cached and exposed to other users.
- Any SvelteKit app running on Vercel with protected endpoints may be affected.
How to check if you're affected
Using Aikido:
If you are an Aikido user, check your central feed. You can see the issue in Aikido Intel here. Tip: Aikido rescans your repos nightly, though we recommend triggering a full rescan as well.
If you are not yet an Aikido user, set up an account and connect your repos. Our proprietary malware coverage is included in the free plan (no credit card required).
Aikido's AI pentest and web security scanning detect cache deception flows and unsafe rewrite behavior automatically.
Fix status
We disclosed this to Vercel on January 21, 2026.
Timeline
- January 20, 2026: Aikido Security identified the vulnerability, built working PoC
- January 21, 2026: Responsible disclosure to Vercel
- January 23, 2026: Report triaged
- February 9, 2026: Vercel confirms the report and starts working on a fix
- February 19, 2026: Vercel has fixed the vulnerability for all users, and advisory is published (GHSA-9pq4-5hcf-288c). The issue can be viewed in Aikido's Intel database and has the reserved CVE number of: CVE-2026-27118.
We also found a separate issue that allows for Denial of Service in an experimental feature on SvelteKit. This has also been disclosed and fixed.

