Skip to content

fix: Handle flag keys that are names of built-in object members - #2170

Merged
kinyoklion merged 2 commits into
mainfrom
rlamb/prototype-named-flag-keys
Nov 5, 2026
Merged

kinyoklion merged 2 commits into
mainfrom
rlamb/prototype-named-flag-keys

Conversation

@kinyoklion

@kinyoklion kinyoklion commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

Summary

Flag keys such as constructor, toString or __proto__ are ordinary keys, but some code kept them in plain objects:

  • The event summarizer threw when it summarized an evaluation of a flag whose key is a built-in object member, so variation() failed for that flag in the server and client SDKs. It now uses maps, and builds the summary on a null-prototype object so that a flag named __proto__ stays in the summary.
  • allFlagsState() dropped a flag named __proto__ from allValues() and toJSON(), and getFlagValue() could return an inherited property for a key that is not in the state. The state still serializes to plain objects, which React server components require.
  • The in-memory store dropped a flag named __proto__ from all() and when it applied an update, and the FDv2 payload conversion dropped it unless it was the first item of its kind, so the flag was not found. Both now use null-prototype objects, as the override layer already does. The in-memory store's all() result now has no prototype, like the override layer's.

New unit tests cover each case. The node-server contract tests pass new server-side evaluation tests for these keys with FDv1 and FDv2.

Opened from termag


Note

Overview
Fixes incorrect handling when feature flag keys are JavaScript built-in names such as __proto__, constructor, or toString. Assigning those keys on normal objects can mutate the prototype or hide entries, which broke summarization, bootstrap state, the in-memory store, and FDv2 payload conversion.

Event summarization now keeps counters in Map instances and builds the summary features object with Object.create(null), so every flag key is summarized and survives JSON.stringify.

FlagsStateBuilder stores values and metadata via Object.defineProperty, uses hasOwnProperty for lookups, and still emits plain objects from toJSON() for frameworks like React server components.

In-memory feature store and FDv2 payload listener use null-prototype maps when listing, snapshotting, merging, and converting updates so flags like __proto__ are retained even when they are not the first item in a batch.

New unit tests cover these paths across EventSummarizer, FlagsStateBuilder, InMemoryFeatureStore, and createPayloadListenerFDv2.

Reviewed by Cursor Bugbot for commit b67ad28. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

@launchdarkly/js-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 32497 bytes
Compressed size limit: 32500
Uncompressed size: 153345 bytes

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

@launchdarkly/js-client-sdk size report
This is the brotli compressed size of the ESM build.
Compressed size: 34545 bytes
Compressed size limit: 35000
Uncompressed size: 123262 bytes

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

@launchdarkly/js-client-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 26066 bytes
Compressed size limit: 44000
Uncompressed size: 168326 bytes

@kinyoklion
kinyoklion force-pushed the rlamb/prototype-named-flag-keys branch from 551861e to e33ad89 Compare November 4, 2026 23:18
- The event summarizer threw when it summarized an evaluation of a flag whose
  key is a built-in object member, such as constructor, toString or
  __proto__. It now uses maps, and builds the summary on a null-prototype
  object.
- The all-flags state dropped a flag named __proto__ from allValues() and
  toJSON(), and getFlagValue() could return an inherited property for a key
  that is not in the state. The state still serializes to plain objects.
- The in-memory store dropped a flag named __proto__ from all() and when it
  applied an update, and the FDv2 payload conversion dropped it unless it was
  the first item of its kind. Both now use null-prototype objects.

Generated-by: termag
@kinyoklion
kinyoklion force-pushed the rlamb/prototype-named-flag-keys branch from e33ad89 to 4f67776 Compare November 4, 2026 23:23
@kinyoklion
kinyoklion marked this pull request as ready for review November 4, 2026 23:51
@kinyoklion
kinyoklion requested a review from a team as a code owner November 4, 2026 23:51
@kinyoklion
kinyoklion merged commit 4a18699 into main Nov 5, 2026
68 checks passed
@kinyoklion
kinyoklion deleted the rlamb/prototype-named-flag-keys branch November 5, 2026 23:27
@github-actions github-actions Bot mentioned this pull request Nov 5, 2026
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