Skip to content

wrapId and unwrapId only encode the first null byte #22690

Description

@SujalXplores

Describe the bug

wrapId and unwrapId in packages/vite/src/shared/utils.ts encode and decode null bytes using String.prototype.replace with a string pattern:

// wrapId
VALID_ID_PREFIX + id.replace('\0', NULL_BYTE_PLACEHOLDER)

// unwrapId
id.slice(VALID_ID_PREFIX.length).replace(NULL_BYTE_PLACEHOLDER, '\0')

String.prototype.replace with a string pattern replaces only the first occurrence. When an id contains more than one null byte, only the first is encoded, and only the first placeholder is decoded.

wrapId is meant to make an id URL-safe by encoding every \0 as __x00__ (see the NULL_BYTE_PLACEHOLDER doc comment in packages/vite/src/shared/constants.ts), but the wrapped id keeps a raw null byte after the first one, which is not valid in an import URL. unwrapId is asymmetric for the same reason.

This affects real ids. A virtual module wrapped by another plugin, for example a CommonJS proxy around a virtual module, produces an id such as \0commonjs-proxy:\0virtual that contains more than one null byte.

The same single-replace pattern is also used for the Lightning CSS filename in packages/vite/src/node/plugins/css.ts. The existing comment there notes that Lightning CSS treats a null byte as a string terminator, so a leftover null byte truncates the filename.

I have opened a PR that fixes all three call sites and adds tests: #22687.

Reproduction

https://gist.github.com/SujalXplores/7f6ff088efa07cd0699fbd1c81be9fde

Steps to reproduce

Save the gist as wrap-id-repro.mjs and run it:

node wrap-id-repro.mjs

Output on the current main (the \0 below is a raw null byte, not the __x00__ placeholder):

wrapped: "/@id/__x00__commonjs-proxy:\0virtual"
wrapped still contains a raw null byte: true

Expected output:

wrapped: "/@id/__x00__commonjs-proxy:__x00__virtual"
wrapped still contains a raw null byte: false

The same expectation is covered by the unit test added in #22687, which fails on main and passes with the fix.

System Info

Affected version: [email protected] (current main, commit d18e985) and earlier versions that use these helpers.
Platform-independent defect in pure string helpers: wrapId and unwrapId in src/shared/utils.ts, and the Lightning CSS filename encoder in src/node/plugins/css.ts. Not OS, Node, package manager, or browser specific.

Used Package Manager

pnpm

Logs

No response

Validations

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions