Skip to content

[wrangler] Fix case-insensitive-env leaking stale duplicate keys on override - #14999

Open
mittalpk wants to merge 2 commits into
cloudflare:mainfrom
mittalpk:fix/case-insensitive-env-stale-keys
Open

[wrangler] Fix case-insensitive-env leaking stale duplicate keys on override#14999
mittalpk wants to merge 2 commits into
cloudflare:mainfrom
mittalpk:fix/case-insensitive-env-stale-keys

Conversation

@mittalpk

@mittalpk mittalpk commented Aug 3, 2026

Copy link
Copy Markdown

Fix case-insensitive-env.ts leaking stale, differently-cased duplicate keys.

On Windows, wrangler loads .env values through a case-insensitive Proxy wrapper (packages/wrangler/src/config/case-insensitive-env.ts) so lookups like env.PATH and env.Path resolve to the same value, and this object is assigned directly to process.env (packages/wrangler/src/config/dot-env.ts). When a key is set again under a different casing (e.g. .env.local overriding a value from .env with different casing), the previous casing's property was never removed from the underlying target object. get/has still resolved correctly through the internal tracking map, but anything that enumerates process.envObject.keys, for...in, JSON.stringify, object spread, or a spawned subprocess inheriting the environment — saw both the stale and current key.

The set, defineProperty, and deleteProperty traps now keep exactly one own-property per case-insensitive key, so enumeration always reflects only the current value. (deleteProperty had the same class of bug in the other direction: it deleted from the internal tracking map by canonical key but issued Reflect.deleteProperty using whatever casing the caller passed, so delete env.path after env.PATH = ... left an orphaned, untracked PATH property on the target — fixed the same way, by resolving the tracked casing first.)

Repro (before the fix):

const env = caseInsensitiveEnv();
env.PATH = "1";
env.Path = "2";
Object.keys(env); // ["PATH", "Path"] — should be ["Path"]
JSON.stringify(env); // {"PATH":"2","Path":"2"} — should be {"Path":"2"}

  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: internal bug fix, no user-facing API/behavior change beyond correcting the leak

Note

This is a contribution from an AI agent: Claude Code (Anthropic), running under direct human supervision and review.


Open in Devin Review

…verride

The case-insensitive Proxy used for .env loading on Windows tracked the
current casing for a key in a side Map, but its set/defineProperty/
deleteProperty traps never removed the old differently-cased property from
the underlying target object. get/has still resolved correctly through the
tracking map, but Object.keys/for...in/JSON.stringify/spread over the
resulting object (which is assigned directly to process.env) exposed both
the stale and current key.

Fix set/defineProperty to delete the previous casing's property before
writing the new one, and deleteProperty to remove the tracked casing
rather than whatever casing the caller happened to pass.
@changeset-bot

changeset-bot Bot commented Aug 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0fc6313

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
wrangler Patch
@cloudflare/vite-plugin Patch
@cloudflare/vitest-pool-workers Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@workers-devprod
workers-devprod requested review from a team and emily-shen and removed request for a team August 3, 2026 17:05
@workers-devprod

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • @cloudflare/wrangler
Show detailed file reviewers
  • .changeset/case-insensitive-env-stale-keys.md: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/case-insensitive-env.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/config/case-insensitive-env.ts: [@cloudflare/wrangler]

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Open in Devin Review


On Windows, `wrangler` loads `.env` values through a case-insensitive `Proxy` wrapper so lookups like `env.PATH` and `env.Path` resolve to the same value, and this object is assigned directly to `process.env`. When a key was set again under a different casing (e.g. a value in `.env.local` overriding one from `.env` with different casing), the previous casing was never removed from the underlying object. `env.PATH`/`env.Path` still returned the correct, latest value, but anything that enumerates `process.env` — `Object.keys`, `for...in`, `JSON.stringify`, object spread, or a spawned subprocess inheriting the environment — would see both the stale and current key.

The proxy's `set`, `defineProperty`, and `deleteProperty` traps now keep exactly one entry per case-insensitive key, so enumeration always reflects the current value.

@devin-ai-integration devin-ai-integration Bot Aug 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Release note explains internal machinery instead of user impact

The release note text (.changeset/case-insensitive-env-stale-keys.md:7) describes internal implementation machinery rather than the user-facing effect, so the published changelog reads like maintainer notes.
Impact: Users reading the changelog get internal details they cannot act on instead of a clear description of what changed for them.

Repository changeset rule about implementation details

REVIEW.md states: "Changesets should target users of the tools (e.g. Wrangler users) rather than maintainers. Avoid including implementation details ... Instead, focus on user-facing impact and benefits." The middle paragraph describes the case-insensitive Proxy wrapper, the assignment to process.env, and "the previous casing was never removed from the underlying object" — all internal mechanics. The last paragraph (line 9) already conveys the user-facing impact and could stand alone.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Per review feedback: changesets should describe what changed for users,
not implementation details like which Proxy traps were touched.
@emily-shen
emily-shen requested review from petebacondarwin and removed request for emily-shen August 5, 2026 16:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants