dotenv: stop byte-trimming 0xA0 from UTF-8 .env values - #34001
Conversation
The parser's WHITESPACE_CHARS set included raw 0xA0 and was applied byte-at-a-time to UTF-8 input. 0xA0 is a continuation byte in UTF-8 (NBSP is C2 A0), so trimming it severed multi-byte sequences at value boundaries: A=xठ (U+0920 = E0 A4 A0) lost its final byte and became invalid UTF-8, and NBSP-padded values were left with a bare C2 lead byte. Node.js does not trim NBSP from unquoted values, so drop 0xA0 from the set entirely.
|
Warning Review limit reached
Next review available in: 11 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
Comment |
|
Updated 12:13 AM PT - Jul 12th, 2026
❌ @robobun, your commit a631cb7 has 1 failures in
🧪 To try this PR locally: bunx bun-pr 34001That installs a local version of the PR into your bun-34001 --bun |
There was a problem hiding this comment.
LGTM — one-byte constant fix with a clear root cause and Node-compat justification.
What was reviewed:
WHITESPACE_CHARSuses inskip_whitespaces,parse_key(:branch), andparse_value'sstrings::trim— removing0xA0only affects trailing/leading trim of unquoted values; leading-NBSP-before-key and:-whitespace paths already stopped on0xC2so no regression there.- Test covers both
util.parseEnvand.envfile loading, includes a 3-byte sequence ending in0xA0(U+0920), NBSP-wrapped/bare values, and a guard that ASCII-space trimming still works (D= x→x). - No CODEOWNERS entry for
src/dotenv/.
Extended reasoning...
Overview
Removes the raw byte 0xA0 from the WHITESPACE_CHARS constant in src/dotenv/env_loader.rs, adds a two-line comment explaining why the set must be ASCII-only, and adds a regression test in test/cli/run/env.test.ts. The constant is applied byte-at-a-time by skip_whitespaces() and strings::trim() over UTF-8 input; 0xA0 is a UTF-8 continuation byte (NBSP = C2 A0, U+0920 = E0 A4 A0), so trimming it as a standalone byte severed multi-byte sequences at value boundaries and produced U+FFFD.
Security risks
None. This narrows what gets stripped from .env values — previously-corrupted bytes are now preserved verbatim. No new parsing, no allocation changes, no untrusted-length arithmetic.
Level of scrutiny
Low. The functional change is a single byte removed from a 7-byte constant. I traced all three consumers of WHITESPACE_CHARS in the parser: skip_whitespaces() (leading whitespace before keys/values/quotes), the b':' key-separator branch, and the unquoted-value strings::trim. In the first two, a leading NBSP already stopped scanning at 0xC2 (not in the set), so behavior is unchanged there. The only observable effect is that unquoted values with UTF-8 sequences ending in 0xA0 at either boundary are no longer byte-truncated — which is the bug being fixed and matches Node.js's util.parseEnv.
Other factors
The test is well-constructed: it exercises both the in-process parseEnv path and the subprocess .env-file path, asserts exact values (not lengths), and includes D= x → "x" to guard that ASCII-space trimming is unchanged. It sits alongside the existing .env space edgecase test in the correct file. The PR description states the full env.test.ts suite and Node's test-util-parse-env.js still pass. No prior human reviews or outstanding comments on the PR.
There was a problem hiding this comment.
LGTM — removing raw 0xA0 from a byte-wise whitespace set is the correct fix for UTF-8 corruption, and it matches Node's parseEnv behavior.
What was reviewed:
- Confirmed
WHITESPACE_CHARSis applied byte-at-a-time inskip_whitespaces/parse_key/strings::trim, so 0xA0 (a UTF-8 continuation byte) was severing multi-byte sequences at value boundaries. - Test covers a 3-byte sequence ending in 0xA0, leading/trailing/bare NBSP, and confirms ASCII-space trimming is unchanged (
D= x→x). - No other 0xA0 byte-trimming sites in
src/dotenv/; no CODEOWNERS entry for this path.
Extended reasoning...
Overview
One-byte deletion from the WHITESPACE_CHARS constant in src/dotenv/env_loader.rs (dropping \\xA0), plus a two-line comment and a new test in test/cli/run/env.test.ts. The constant is used by skip_whitespaces(), the : separator check in parse_key(), and strings::trim() in parse_value() — all byte-at-a-time over UTF-8 input. Since 0xA0 is a UTF-8 continuation byte (NBSP = C2 A0, U+0920 = E0 A4 A0), trimming it as a lone byte splits multi-byte code points and yields invalid UTF-8 that surfaces as U+FFFD.
Security risks
None. This narrows the whitespace set (fewer bytes trimmed), so it cannot introduce new parse ambiguities; it only stops corrupting valid UTF-8. The constant is local to the dotenv parser and does not touch auth, crypto, or network paths.
Level of scrutiny
Low. The mechanism is unambiguous (byte-wise trim of a continuation byte over UTF-8), the fix is a single-byte constant edit, and Node.js compatibility was verified — Node does not trim NBSP from unquoted .env values, so preserving NBSP verbatim is the correct behavior for both correctness and compat. The added comment documents the ASCII-only invariant to prevent regression.
Other factors
The new test exercises both entry points (util.parseEnv and auto-loaded .env), asserts the positive case (ASCII spaces still trimmed) alongside the fix, and is placed in the existing env.test.ts next to related edge-case tests. Grep confirms no other 0xA0 byte-set usage in src/dotenv/. No CODEOWNERS covers this path and there are no outstanding reviewer comments.
|
This diff is ready. The new test and the full CI is red on tests unrelated to this change (both already being handled separately):
Neither touches the |
Reproduction
Same corruption on auto-loaded
.envfiles and--env-file.Cause
WHITESPACE_CHARSinsrc/dotenv/env_loader.rsincluded raw0xA0and was applied byte-at-a-time byskip_whitespaces()andstrings::trim()over UTF-8 input. In UTF-8,0xA0is a continuation byte: NBSP isC2 A0, U+0920 isE0 A4 A0, etc. Trimming it as a single byte severs multi-byte sequences at value boundaries, leaving invalid UTF-8 that surfaces as U+FFFD.Fix
Drop
0xA0from the whitespace set. Node.js does not trim NBSP from unquoted.envvalues (verified against v26.3.0), so this also bringsutil.parseEnvand.envloading in line with Node.Verification
New test in
test/cli/run/env.test.tscoversutil.parseEnvand.envfile loading with a 3-byte sequence ending in0xA0, NBSP-wrapped values, a bare NBSP value, and ASCII-space trimming (unchanged). Fails onmain, passes with this change; fullenv.test.tssuite andtest-util-parse-env.jsstill pass.no test proof · iteration 1 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/cli/run/env.test.ts