CVE-2025-67489
Description
@vitejs/plugin-rs provides React Server Components (RSC) support for Vite. Versions 0.5.5 and below are vulnerable to arbitrary remote code execution on the development server through unsafe dynamic imports in server function APIs (loadServerAction, decodeReply, decodeAction) when integrated into RSC applications that expose server function endpoints. Attackers with network access to the development server can read/modify files, exfiltrate sensitive data (source code, environment variables, credentials), or pivot to other internal services. While this affects development servers only, the risk increases when using vite --host to expose the server on all network interfaces. This issue is fixed in version 0.5.6.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
@vitejs/plugin-rscnpm | < 0.5.6 | 0.5.6 |
Affected products
1- Range: plugin-react-oxc@0.1.1, plugin-react-oxc@0.2.0, plugin-react-oxc@0.2.1, …
Patches
1fe634b58210dfix(rsc): validate reference id on dev (#1010)
5 files changed · +112 −2
packages/plugin-rsc/e2e/starter.test.ts+45 −0 modified@@ -7,6 +7,51 @@ import { x } from 'tinyexec' test.describe('dev-default', () => { const f = useFixture({ root: 'examples/starter', mode: 'dev' }) defineStarterTest(f) + + test('validate reference 1', async () => { + const requestUrl = f.url('_.rsc') + const formData = new FormData() + const payload = { + '0': [1, '$F1'], + '1': { id: '__invalid_reference__# ' }, + } + for (const [k, v] of Object.entries(payload)) { + formData.append(k, JSON.stringify(v)) + } + const response = await fetch(requestUrl, { + method: 'POST', + body: formData, + headers: { + 'x-rsc-action': '/src/action.tsx#updateServerCounter', + }, + }) + expect(f.proc().stderr()).toContain( + `invalid server reference '__invalid_reference__`, + ) + expect(response.status).toBe(500) + }) + + test('validate reference 2', async () => { + const requestUrl = f.url('_.rsc') + const formData = new FormData() + const payload = { + '0': [1], + } + for (const [k, v] of Object.entries(payload)) { + formData.append(k, JSON.stringify(v)) + } + const response = await fetch(requestUrl, { + method: 'POST', + body: formData, + headers: { + 'x-rsc-action': `__invalid_reference__# `, + }, + }) + expect(f.proc().stderr()).toContain( + `invalid server reference '__invalid_reference__'`, + ) + expect(response.status).toBe(500) + }) }) test.describe('build-default', () => {
packages/plugin-rsc/src/plugins/shared.ts+20 −0 modified@@ -27,3 +27,23 @@ export function parseIdQuery(id: string): { const query = Object.fromEntries(new URLSearchParams(rawQuery)) return { filename, query } } + +export type ReferenceValidationVirtual = { + id: string + type: 'server' | 'client' +} + +export function toReferenceValidationVirtual({ + id, + type, +}: ReferenceValidationVirtual) { + return `virtual:vite-rsc/reference-validation?type=${type}&id=${encodeURIComponent(id)}&lang.js` +} + +export function parseReferenceValidationVirtual( + id: string, +): ReferenceValidationVirtual | undefined { + if (id.startsWith('\0virtual:vite-rsc/reference-validation?')) { + return parseIdQuery(id).query as any + } +}
packages/plugin-rsc/src/plugin.ts+37 −1 modified@@ -54,7 +54,12 @@ import { createDebug } from '@hiogawa/utils' import { scanBuildStripPlugin } from './plugins/scan' import { validateImportPlugin } from './plugins/validate-import' import { vitePluginFindSourceMapURL } from './plugins/find-source-map-url' -import { parseCssVirtual, toCssVirtual, parseIdQuery } from './plugins/shared' +import { + parseCssVirtual, + toCssVirtual, + parseIdQuery, + parseReferenceValidationVirtual, +} from './plugins/shared' import { stripLiteral } from 'strip-literal' const isRolldownVite = 'rolldownVersion' in vite @@ -261,6 +266,37 @@ export function vitePluginRscMinimal( ...vitePluginUseClient(rscPluginOptions, manager), ...vitePluginUseServer(rscPluginOptions, manager), ...vitePluginDefineEncryptionKey(rscPluginOptions), + { + name: 'rsc:reference-validation', + apply: 'serve', + load: { + handler(id, _options) { + if (id.startsWith('\0virtual:vite-rsc/reference-validation?')) { + const parsed = parseReferenceValidationVirtual(id) + assert(parsed) + if (parsed.type === 'client') { + const meta = Object.values(manager.clientReferenceMetaMap).find( + (meta) => meta.referenceKey === parsed.id, + ) + if (meta) { + return `export {}` + } + } + if (parsed.type === 'server') { + const meta = Object.values(manager.serverReferenceMetaMap).find( + (meta) => meta.referenceKey === parsed.id, + ) + if (meta) { + return `export {}` + } + } + this.error( + `[vite-rsc] invalid ${parsed.type} reference '${parsed.id}'`, + ) + } + }, + }, + }, scanBuildStripPlugin({ manager }), ] }
packages/plugin-rsc/src/rsc.tsx+5 −0 modified@@ -1,5 +1,6 @@ import serverReferences from 'virtual:vite-rsc/server-references' import { setRequireModule } from './core/rsc' +import { toReferenceValidationVirtual } from './plugins/shared' export { createClientManifest, @@ -20,6 +21,10 @@ function initialize(): void { setRequireModule({ load: async (id) => { if (!import.meta.env.__vite_rsc_build__) { + await import( + /* @vite-ignore */ '/@id/__x00__' + + toReferenceValidationVirtual({ id, type: 'server' }) + ) return import(/* @vite-ignore */ id) } else { const import_ = serverReferences[id]
packages/plugin-rsc/src/ssr.tsx+5 −1 modified@@ -3,7 +3,7 @@ import * as clientReferences from 'virtual:vite-rsc/client-references' import * as ReactDOM from 'react-dom' import { setRequireModule } from './core/ssr' import type { ResolvedAssetDeps } from './plugin' -import { toCssVirtual } from './plugins/shared' +import { toCssVirtual, toReferenceValidationVirtual } from './plugins/shared' export { createServerConsumerManifest } from './core/ssr' @@ -15,6 +15,10 @@ function initialize(): void { setRequireModule({ load: async (id) => { if (!import.meta.env.__vite_rsc_build__) { + await import( + /* @vite-ignore */ '/@id/__x00__' + + toReferenceValidationVirtual({ id, type: 'client' }) + ) const mod = await import(/* @vite-ignore */ id) const modCss = await import( /* @vite-ignore */ '/@id/__x00__' + toCssVirtual({ id, type: 'ssr' })
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.