Skip to content

Commit c12fc82

Browse files
deyaaeldeenCopilot
andcommitted
fix: verify module identity in cache protocol to prevent stale modules
The module runner's `urlToIdModuleMap` caches modules by URL. When a bare specifier (e.g., `@azure/core-lro`) is used as the URL, the first resolved version is cached and served to all subsequent importers, even if they should receive a different physical package. Additionally, the `{ cache: true }` protocol between the module runner and server had no identity verification. The server confirms a module was transformed without checking if the client's cached module matches the server's resolved module. Fix: - Server now includes the resolved module ID in `{ cache: true }` responses via the new optional `id` field on `CachedFetchResult`. - Client verifies that its cached module ID matches the server's. On mismatch, the client refetches the module without the cache flag. This prevents silent data corruption when multiple versions of a dependency exist in a monorepo with nested `node_modules`. Fixes #22079 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 6daa10f commit c12fc82

File tree

3 files changed

+28
-2
lines changed

3 files changed

+28
-2
lines changed

packages/vite/src/module-runner/runner.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
} from '../shared/moduleRunnerTransport'
99
import { createIsBuiltin } from '../shared/builtin'
1010
import type { EvaluatedModuleNode } from './evaluatedModules'
11-
import { EvaluatedModules } from './evaluatedModules'
11+
import { EvaluatedModules, normalizeModuleId } from './evaluatedModules'
1212
import type {
1313
ModuleEvaluator,
1414
ModuleRunnerContext,
@@ -309,6 +309,25 @@ export class ModuleRunner {
309309
`Module "${url}" was mistakenly invalidated during fetch phase.`,
310310
)
311311
}
312+
// Verify the server resolved to the same module we have cached.
313+
// When a bare specifier maps to different physical packages for
314+
// different importers (e.g. monorepos with duplicate deps), the
315+
// urlToIdModuleMap may hold a stale entry. If the server resolved
316+
// to a different module, refetch without the cache flag.
317+
if (
318+
fetchedModule.id &&
319+
cachedModule.id !== normalizeModuleId(fetchedModule.id)
320+
) {
321+
this.debug?.(
322+
'[module runner] cache identity mismatch for',
323+
url,
324+
'- cached:',
325+
cachedModule.id,
326+
'server:',
327+
fetchedModule.id,
328+
)
329+
return this.getModuleInformation(url, importer, undefined)
330+
}
312331
return cachedModule
313332
}
314333

packages/vite/src/node/ssr/fetchModule.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export async function fetchModule(
8585

8686
// if url is already cached, we can just confirm it's also cached on the server
8787
if (options.cached && cached) {
88-
return { cache: true }
88+
return { cache: true, id: mod.id }
8989
}
9090

9191
let result = await environment.transformRequest(url)

packages/vite/src/shared/invokeMethods.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ export interface CachedFetchResult {
1414
* it wasn't invalidated on the server side.
1515
*/
1616
cache: true
17+
/**
18+
* The resolved module ID on the server. The client uses this to verify
19+
* that its cached module matches what the server resolved. When a bare
20+
* specifier maps to different physical packages for different importers,
21+
* the client's cache may hold the wrong version.
22+
*/
23+
id?: string
1724
}
1825

1926
export interface ExternalFetchResult {

0 commit comments

Comments
 (0)