TanStack Start XSS (CVE-2026-102989): Are You Affected?

If you run npm audit on a TanStack Start app today, it will probably tell you that you have zero vulnerabilities. We checked on 2026-10-02, against a lockfile deliberately pinned to a version the advisory lists as affected. The audit came back clean.

It is wrong. On 2026-09-30 the TanStack team published GHSA-qx66-fv34-fjm8, tracked as CVE-2026-102989: an unauthenticated reflected cross-site scripting flaw in server-function responses, rated Critical at CVSS 9.3. The advisory just has not reached the databases your tooling reads yet.

TL;DR

TanStack Start versions from 1.143.12 up to the 2026-09-30 patches let an attacker craft a URL that returns attacker-controlled HTML from your own origin. Fixed in @tanstack/react-start 1.168.60, @tanstack/solid-start 1.168.57, @tanstack/vue-start 1.168.56 and @tanstack/start-server-core 1.169.39. Check your lockfile version by hand, because as of 2026-10-02 npm audit and OSV do not know about this yet.

What the bug actually is

TanStack Start lets you write a function that runs on the server and call it from the client, and the framework handles shipping the arguments across. According to the advisory, that transport passes request payload fields into internal middleware state rather than restricting them to the public input fields. An attacker who controls those fields can control the result produced on the error path, and the response handling then treats that result as a legitimate HTTP response.

The practical outcome: a crafted URL makes your app return HTML the attacker wrote, served from your domain.

That last part is what matters. Script running on your origin is inside your security boundary. It can read whatever your JavaScript can read, which includes any session token not in an HttpOnly cookie, and it can make requests your logged-in user is authorised to make.

No login required to trigger it. The advisory describes the flaw as unauthenticated and reflected. The attacker does not need an account on your app. They need a victim to open a link.

Two limits worth being precise about, because we would rather you not panic than over-read this.

Your app has to expose at least one server function, and the attacker needs a valid id for it. Those ids are embedded in the client bundle, so treat them as discoverable rather than secret, but an app that defines no server functions has no entry point here.

The crafted-link path also needs that function to accept GET. That sounds like a meaningful filter and mostly isn't: GET is the default when you don't set a method. There's a neat detail underneath that, which also explains the whole bug. The vulnerable response path only triggers when the request lacks the x-tsr-serverFn: true header. A real call from your own client always sets it. A browser following a link never can. That asymmetry is exactly why a normal RPC behaves and a pasted link does not.

Three vendor pages went up alongside the advisory: TanStack's own writeup, Netlify's, and Appwrite's. Read whichever matches your host. They cover upgrading well. What none of them mentions is that your scanner cannot see this yet, which is the section after next.

Check whether you're affected

This takes about two minutes and it is the only check we would trust right now.

1

Find the version you actually installed. Read the lockfile, not package.json: a range like ^1.1.0 tells you nothing about what got installed.

Find the installed version
grep -n "@tanstack/react-start" package-lock.json

# pnpm
grep -n "@tanstack/react-start" pnpm-lock.yaml

# yarn
grep -n "@tanstack/react-start" yarn.lock
2

Compare it against the affected ranges from the advisory. Everything from 1.143.12 up to the patched release is affected.

PackageAffectedPatched in
@tanstack/react-start>=1.143.12 <1.168.601.168.60
@tanstack/solid-start>=1.143.12 <1.168.571.168.57
@tanstack/vue-start>=1.143.12 <1.168.561.168.56
@tanstack/start-server-core>=1.143.12 <1.169.391.169.39

Note the fourth row. @tanstack/start-server-core can arrive as a transitive dependency, so it is worth grepping for even if you never installed it directly.

3

Upgrade. The patched versions went out the same day as the advisory: @tanstack/react-start@1.168.60 was published to npm on 2026-09-30 at 17:48 UTC.

Upgrade
npm install @tanstack/react-start@latest
npm ls @tanstack/start-server-core

The second command is the one people skip. It shows you what version of the server core your tree actually resolved to after the upgrade.

If you genuinely cannot ship an upgrade today, the advisory suggests an edge or WAF rule that blocks requests to the server-function path which lack the literal x-tsr-serverFn: true header, or which look like browser document navigations (Sec-Fetch-Mode: navigate, or an Accept of text/html). TanStack is explicit that this is a mitigation and not a complete fix, and that the header is client-controlled and is not authentication. Treat it as something that buys you an afternoon.

Why your scanner is quiet about this

Here is the part nobody is saying, and it is the reason we wrote this page.

An advisory has to travel. It gets published on GitHub, then ingested into the OSV database, then into the feed npm audit queries. That trip normally takes days. Until it completes, every tool built on those feeds reports nothing.

We measured where this one had got to on 2026-10-02:

What we found on 2026-10-02
  • api.osv.dev returns Vulnerability not found for both GHSA-qx66-fv34-fjm8 and CVE-2026-102989.
  • Querying OSV for @tanstack/react-start version 1.168.0, which is inside the affected range, returns zero vulnerabilities.
  • npm's bulk advisory endpoint returns an empty object {} for the same version.
  • A real npm audit against a lockfile pinning that version prints found 0 vulnerabilities.

So on the day a Critical advisory lands, the tool most people reach for to answer "am I affected" answers no. The same gap means Dependabot, OSV-scanner and anything else reading those feeds is equally blind today.

This finding has an expiry date and we want to be loud about it. Every one of those measurements is a snapshot of 2026-10-02. Once GitHub ingests the advisory, probably within days, the scanners start working and this section stops being true. Do not read it later as "scanners never catch this" and skip a check that by then works. Re-run it yourself; the point is the method, not our result.

The lesson does outlast the window: in the first days of any advisory, a clean scan means your scanner hasn't heard yet, not that you're fine.

This cuts both ways in CI. If a dependency-scanning step gates your deploys, it will happily wave through the vulnerable version this week. Nothing is broken; the feed is just empty.

Upgrading production is not the whole fix

Netlify's advisory on 2026-09-30 flags something the others don't: publicly available deploy previews and branch deploys may remain vulnerable until they are automatically deleted, and they suggest deleting them manually.

That is Netlify describing Netlify, and it is the only host we found saying it out loud. We're repeating it here because the reasoning is not Netlify-specific: a preview is an immutable build of old code on its own URL. But treat the sourced claim as Netlify's and check your own host rather than assuming.

Think about what a preview deploy is. It's a complete running copy of your app, built from whatever the code looked like on that branch, sitting on its own public URL. Patching main and redeploying production does not touch any of them.

For our audience this is the sharp edge. If you have been building with an AI assistant, you may have opened a lot of pull requests, and each one probably produced a preview URL that still resolves. Those URLs are frequently public by default and get indexed.

1

Open your hosting dashboard and list deploys that are still live: Netlify under Deploys, Vercel under the project's Deployments tab.

2

Delete the previews and branch deploys built before you upgraded, or set them to require authentication. Vercel made Deployment Protection free on every plan in September 2026, so locking previews no longer costs anything.

3

Redeploy production from the patched commit, then confirm the running build picked up the new version rather than a cached dependency layer.

The part worth keeping after this patch

Nothing in the advisory suggests this is being exploited. No vendor has reported attacks in the wild, and we are not going to imply otherwise.

What's durable here is the shape of the problem. A framework you did not choose explicitly, pulled in as a transitive dependency, had a Critical flaw, and the automated system you rely on to tell you about exactly that did not know for several days. Picking the framework was a decision someone made; being told about its vulnerabilities on time is not something you can assume.

Our scanner checks the things that survive a dependency bump: whether session cookies are HttpOnly so an XSS cannot read them, whether a Content Security Policy is in place to limit what injected script can do, and whether your preview environments are reachable by anyone who finds the URL. Those controls are what decides how bad an XSS is on the day one lands, and there will be another one.

How do I know if my app uses TanStack Start?

Search your lockfile, not package.json. Run grep -n "@tanstack/react-start" package-lock.json, or the yarn.lock or pnpm-lock.yaml equivalent. The lockfile records the version actually installed.

Why does npm audit say I have zero vulnerabilities?

As of 2026-10-02 this advisory had not reached the OSV database or npm's advisory feed. We pinned a vulnerable version, ran npm audit, and it reported found 0 vulnerabilities. A clean audit right now is not evidence that you are safe. Check the version number yourself.

I upgraded production. Am I done?

Not necessarily. Netlify's advisory notes that publicly available deploy previews and branch deploys may stay vulnerable until they are deleted, because they were built from the old version and keep serving on their own URLs. Those URLs are usually public and often indexed.

Does this need my users to be logged in?

No. The advisory describes it as unauthenticated and reflected, which means an attacker crafts a URL and gets attacker-controlled HTML back from your origin. Being signed in is not a precondition for the flaw, though a signed-in victim is what makes it worth an attacker's time.

Is this the same as the TanStack npm attack earlier this year?

No. CVE-2026-45321 was the May 2026 supply-chain compromise where malicious versions were published to npm. This is a code flaw in TanStack's own server-function handling, and it is separate again from GHSA-9m65-766c-r333, a seroval deserialization issue fixed in 1.167.30.

Check What an XSS Could Reach

We scan for missing HttpOnly cookies, absent CSP, and exposed preview environments.

Vulnerability Guides

TanStack Start XSS (CVE-2026-102989): Are You Affected?