Vitest: Code injection via unauthenticated manual mock registration
### Summary `@vitest/mocker`'s `interceptorPlugin` accepts mock registrations on Vite's dev-server WebSocket with no authentication, and interpolates the mock's `url` into generated module source without escaping. A client that can reach the dev server can make it serve arbitrary JavaScript in place of any project module, which then runs in the developer's browser on the dev-server origin. Same handler and same unauthenticated caller as GHSA-82fw-gwwq-j7x9; that fix (`8ff9b9a9efca7c6cfd5243569440de8d7a33aec4`, PR #10972) hardened only the `redirect` branch and left the `manual` branch reaching the unescaped generator. ### Patches Fixed in `@vitest/mocker` 5.0.3, released on 2026-09-30 (PR #11377), and backported to 4.1.12. `createManualModuleSource` now serializes the module URL and the export names with `JSON.stringify`, so a registered mock cannot change the generated code. Projects that call `mockerPlugin()` or `interceptorPlugin()` directly need `@vitest/mocker` 4.1.12 or 5.0.3. ### Details `packages/mocker/src/utils.ts:6-9` builds module source by string interpolation: ```ts export function createManualModuleSource(moduleUrl: string, exports: string[], globalAccessor = '"__vitest_mocker__"'): string { const source = ` const __factoryModule__ = await globalThis[${globalAccessor}].getFactoryModule("${moduleUrl}"); ` ``` `moduleUrl` lands inside a double-quoted JavaScript string with no escaping or validation. `packages/mocker/src/node/interceptorPlugin.ts:66-93` feeds it s
- Products
- @vitest/mocker
- Affected versions
- Not specified
- Fixed versions
- 4.1.12, 5.0.3
- CVEs
- CVE-2026-112949
- Confirmed fixed CVEs
- Not specified
- Updated in this feed
- 31 Oct 2026, 16:08 UTC · Revision 1
Source evidence
Vitest: Code injection via unauthenticated manual mock registration
core-osv · Original sourcePublished 21 Oct 2026, 19:54 UTC
### Summary `@vitest/mocker`'s `interceptorPlugin` accepts mock registrations on Vite's dev-server WebSocket with no authentication, and interpolates the mock's `url` into generated module source without escaping. A client that can reach the dev server can make it serve arbitrary JavaScript in place of any project module, which then runs in the developer's browser on the dev-server origin. Same handler and same unauthenticated caller as GHSA-82fw-gwwq-j7x9; that fix (`8ff9b9a9efca7c6cfd5243569440de8d7a33aec4`, PR #10972) hardened only the `redirect` branch and left the `manual` branch reaching the unescaped generator. ### Patches Fixed in `@vitest/mocker` 5.0.3, released on 2026-09-30 (PR #11377), and backported to 4.1.12. `createManualModuleSource` now serializes the module URL and the export names with `JSON.stringify`, so a registered mock cannot change the generated code. Projects that call `mockerPlugin()` or `interceptorPlugin()` directly need `@vitest/mocker` 4.1.12 or 5.0.3. ### Details `packages/mocker/src/utils.ts:6-9` builds module source by string interpolation: ```ts export function createManualModuleSource(moduleUrl: string, exports: string[], globalAccessor = '"__vitest_mocker__"'): string { const source = ` const __factoryModule__ = await globalThis[${globalAccessor}].getFactoryModule("${moduleUrl}"); ` ``` `moduleUrl` lands inside a double-quoted JavaScript string with no escaping or validation. `packages/mocker/src/node/interceptorPlugin.ts:66-93` feeds it s
osv:GHSA-qxg6-89mg-rqv8
@vitest/mocker
Fixed versions: 4.1.12
@vitest/mocker
Fixed versions: 5.0.3
Complete source record
{
"id": "ec85b2279047b6e4a0cf98d0d47f201f39436f34294e9c0d0bc03bd6fe53e264",
"url": "https://osv.dev/vulnerability/GHSA-qxg6-89mg-rqv8",
"method": "structured",
"payload": {
"kind": "advisory",
"title": "Vitest: Code injection via unauthenticated manual mock registration",
"summary": "### Summary `@vitest/mocker`'s `interceptorPlugin` accepts mock registrations on Vite's dev-server WebSocket with no authentication, and interpolates the mock's `url` into generated module source without escaping. A client that can reach the dev server can make it serve arbitrary JavaScript in place of any project module, which then runs in the developer's browser on the dev-server origin. Same handler and same unauthenticated caller as GHSA-82fw-gwwq-j7x9; that fix (`8ff9b9a9efca7c6cfd5243569440de8d7a33aec4`, PR #10972) hardened only the `redirect` branch and left the `manual` branch reaching the unescaped generator. ### Patches Fixed in `@vitest/mocker` 5.0.3, released on 2026-09-30 (PR #11377), and backported to 4.1.12. `createManualModuleSource` now serializes the module URL and the export names with `JSON.stringify`, so a registered mock cannot change the generated code. Projects that call `mockerPlugin()` or `interceptorPlugin()` directly need `@vitest/mocker` 4.1.12 or 5.0.3. ### Details `packages/mocker/src/utils.ts:6-9` builds module source by string interpolation: ```ts export function createManualModuleSource(moduleUrl: string, exports: string[], globalAccessor = '\"__vitest_mocker__\"'): string { const source = ` const __factoryModule__ = await globalThis[${globalAccessor}].getFactoryModule(\"${moduleUrl}\"); ` ``` `moduleUrl` lands inside a double-quoted JavaScript string with no escaping or validation. `packages/mocker/src/node/interceptorPlugin.ts:66-93` feeds it s",
"evidence": "osv:GHSA-qxg6-89mg-rqv8",
"products": [
{
"name": {
"value": "@vitest/mocker",
"evidence": "/affected/0/package/name"
},
"prerequisites": [],
"fixed_versions": [
{
"value": "4.1.12",
"evidence": "/affected/0/ranges"
}
],
"affected_versions": []
},
{
"name": {
"value": "@vitest/mocker",
"evidence": "/affected/1/package/name"
},
"prerequisites": [],
"fixed_versions": [
{
"value": "5.0.3",
"evidence": "/affected/1/ranges"
}
],
"affected_versions": []
}
],
"severity": "medium",
"structured": {
"id": "GHSA-qxg6-89mg-rqv8",
"aliases": [
"CVE-2026-112949",
"GHSA-qxg6-89mg-rqv8"
],
"metrics": [
{
"type": "CVSS_V3",
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N"
}
],
"affected": [
{
"ranges": [
{
"type": "SEMVER",
"events": [
{
"introduced": "2.1.0-beta.7"
},
{
"fixed": "4.1.12"
}
]
}
],
"package": {
"name": "@vitest/mocker",
"purl": "pkg:npm/%40vitest/mocker",
"ecosystem": "npm"
},
"database_specific": {
"source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-qxg6-89mg-rqv8/GHSA-qxg6-89mg-rqv8.json"
}
},
{
"ranges": [
{
"type": "SEMVER",
"events": [
{
"introduced": "5.0.0-beta.1"
},
{
"fixed": "5.0.3"
}
]
}
],
"package": {
"name": "@vitest/mocker",
"purl": "pkg:npm/%40vitest/mocker",
"ecosystem": "npm"
},
"database_specific": {
"source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-qxg6-89mg-rqv8/GHSA-qxg6-89mg-rqv8.json"
}
}
],
"modified": "2026-10-21T20:01:39.911013327Z",
"provider": "osv",
"references": [
"https://github.com/vitest-dev/vitest/security/advisories/GHSA-qxg6-89mg-rqv8",
"https://nvd.nist.gov/vuln/detail/CVE-2026-112949",
"https://github.com/vitest-dev/vitest/commit/15cc006aa9f8683dc5f7e895d011fa7dc751ce35",
"https://github.com/vitest-dev/vitest/commit/b825313243dc55757e8d7c0f9e7a792135e13473",
"https://github.com/vitest-dev/vitest",
"https://github.com/vitest-dev/vitest/releases/tag/v4.1.12",
"https://github.com/vitest-dev/vitest/releases/tag/v5.0.3"
],
"provider_metadata": {
"cwe_ids": [
"CWE-116",
"CWE-306",
"CWE-94"
],
"severity": "MODERATE",
"github_reviewed": true,
"nvd_published_at": "2026-10-20T16:17:05Z",
"github_reviewed_at": "2026-10-21T19:54:26Z"
}
},
"related_cves": [
{
"value": "CVE-2026-112949",
"evidence": "/aliases"
}
],
"security_relevant": true,
"severity_evidence": "/severity/0/score"
},
"source_id": "core-osv",
"document_id": 399306,
"published_at": "2026-10-21T19:54:26+00:00"
}