Skip to content

fix(normalizr): Use Object.keys() in deepClone to avoid inherited properties#3875

Merged
ntucker merged 2 commits intomasterfrom
fix/deepclone-own-properties
Apr 5, 2026
Merged

fix(normalizr): Use Object.keys() in deepClone to avoid inherited properties#3875
ntucker merged 2 commits intomasterfrom
fix/deepclone-own-properties

Conversation

@ntucker
Copy link
Copy Markdown
Collaborator

@ntucker ntucker commented Apr 5, 2026

Motivation

deepClone in the immutable normalize path uses for...in, which iterates both own and inherited enumerable properties. If Object.prototype were ever polluted (e.g., by a third-party library), those properties would be silently copied into cloned store state, leading to subtle data corruption.

Solution

Replace for (const key in obj) with for (const key of Object.keys(obj)) to restrict iteration to own enumerable properties only. This is a defensive hardening with no behavioral change under normal conditions.

Open questions

N/A

Made with Cursor


Note

Low Risk
Small defensive change limited to deepClone iteration semantics plus a changeset; low risk with behavior only differing when inherited enumerable properties exist (e.g., prototype pollution).

Overview
Hardens the immutable normalize path by updating deepClone to iterate via Object.keys() instead of for...in, so only own enumerable properties are cloned and inherited properties can’t leak into normalized state.

Adds a patch changeset for @data-client/normalizr documenting the fix.

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

…perties

for...in iterates inherited properties, which could copy polluted
Object.prototype entries. Object.keys() restricts to own enumerable
properties only.

Made-with: Cursor
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 5, 2026

🦋 Changeset detected

Latest commit: ad23e37

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

This PR includes changesets to release 11 packages
Name Type
@data-client/normalizr Patch
@data-client/core Patch
example-benchmark Patch
normalizr-github-example Patch
normalizr-redux-example Patch
normalizr-relationships Patch
@data-client/react Patch
@data-client/vue Patch
example-benchmark-react Patch
test-bundlesize Patch
coinbase-lite 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

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs-site Ignored Ignored Preview Apr 5, 2026 7:59pm

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 5, 2026

Size Change: 0 B

Total Size: 80.6 kB

ℹ️ View Unchanged
Filename Size
examples/test-bundlesize/dist/App.js 1.46 kB
examples/test-bundlesize/dist/polyfill.js 307 B
examples/test-bundlesize/dist/rdcClient.js 10.4 kB
examples/test-bundlesize/dist/rdcEndpoint.js 8 kB
examples/test-bundlesize/dist/react.js 59.7 kB
examples/test-bundlesize/dist/webpack-runtime.js 726 B

compressed-size-action

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Benchmark React

Details
Benchmark suite Current: ad23e37 Previous: be8f8d0 Ratio
data-client: getlist-100 169.54 ops/s (± 4.8%) 170.95 ops/s (± 5.4%) 1.01
data-client: getlist-500 45.66 ops/s (± 5.4%) 48.78 ops/s (± 5.6%) 1.07
data-client: update-entity 444.66 ops/s (± 6.5%) 500 ops/s (± 3.8%) 1.12
data-client: update-user 370.37 ops/s (± 5.3%) 500 ops/s (± 6.0%) 1.35
data-client: getlist-500-sorted 50.76 ops/s (± 2.7%) 49.26 ops/s (± 7.4%) 0.97
data-client: update-entity-sorted 370.37 ops/s (± 6.2%) 384.62 ops/s (± 6.5%) 1.04
data-client: update-entity-multi-view 377.49 ops/s (± 6.5%) 370.37 ops/s (± 5.1%) 0.98
data-client: list-detail-switch-10 13.55 ops/s (± 9.7%) 12.87 ops/s (± 6.1%) 0.95
data-client: update-user-10000 90.91 ops/s (± 0.7%) 104.17 ops/s (± 5.9%) 1.15
data-client: invalidate-and-resolve 50.51 ops/s (± 5.2%) 50.38 ops/s (± 5.9%) 1.00
data-client: unshift-item 303.03 ops/s (± 4.1%) 281.75 ops/s (± 5.3%) 0.93
data-client: delete-item 370.37 ops/s (± 5.4%) 400 ops/s (± 3.3%) 1.08
data-client: move-item 208.33 ops/s (± 8.8%) 232.56 ops/s (± 5.8%) 1.12

This comment was automatically generated by workflow using github-action-benchmark.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 5, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.10%. Comparing base (115c762) to head (ad23e37).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #3875   +/-   ##
=======================================
  Coverage   98.10%   98.10%           
=======================================
  Files         153      153           
  Lines        2899     2899           
  Branches      564      564           
=======================================
  Hits         2844     2844           
  Misses         11       11           
  Partials       44       44           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Benchmark

Details
Benchmark suite Current: ad23e37 Previous: 53c7091 Ratio
normalizeLong 434 ops/sec (±2.38%) 459 ops/sec (±0.91%) 1.06
normalizeLong Values 403 ops/sec (±0.20%) 407 ops/sec (±0.42%) 1.01
denormalizeLong 277 ops/sec (±2.71%) 275 ops/sec (±3.25%) 0.99
denormalizeLong Values 249 ops/sec (±2.39%) 242 ops/sec (±2.61%) 0.97
denormalizeLong donotcache 967 ops/sec (±0.19%) 1023 ops/sec (±0.15%) 1.06
denormalizeLong Values donotcache 710 ops/sec (±0.31%) 731 ops/sec (±0.44%) 1.03
denormalizeShort donotcache 500x 1569 ops/sec (±0.12%) 1562 ops/sec (±0.10%) 1.00
denormalizeShort 500x 825 ops/sec (±2.46%) 833 ops/sec (±2.01%) 1.01
denormalizeShort 500x withCache 6322 ops/sec (±0.23%) 5711 ops/sec (±0.18%) 0.90
queryShort 500x withCache 2719 ops/sec (±0.09%) 2666 ops/sec (±0.11%) 0.98
buildQueryKey All 53921 ops/sec (±0.51%) 54203 ops/sec (±1.19%) 1.01
query All withCache 6332 ops/sec (±0.18%) 6508 ops/sec (±0.22%) 1.03
denormalizeLong with mixin Entity 267 ops/sec (±2.18%) 276 ops/sec (±2.50%) 1.03
denormalizeLong withCache 6490 ops/sec (±0.17%) 8046 ops/sec (±0.22%) 1.24
denormalizeLong Values withCache 5003 ops/sec (±0.41%) 5066 ops/sec (±0.36%) 1.01
denormalizeLong All withCache 6246 ops/sec (±0.19%) 6175 ops/sec (±0.14%) 0.99
denormalizeLong Query-sorted withCache 6511 ops/sec (±0.13%) 6487 ops/sec (±0.18%) 1.00
denormalizeLongAndShort withEntityCacheOnly 1621 ops/sec (±0.23%) 1794 ops/sec (±0.18%) 1.11
denormalize bidirectional 50 5676 ops/sec (±2.04%) 5716 ops/sec (±1.95%) 1.01
denormalize bidirectional 50 donotcache 41117 ops/sec (±0.66%) 40068 ops/sec (±0.51%) 0.97
getResponse 4616 ops/sec (±0.60%) 4638 ops/sec (±0.56%) 1.00
getResponse (null) 10250207 ops/sec (±0.59%) 10565574 ops/sec (±1.77%) 1.03
getResponse (clear cache) 255 ops/sec (±2.07%) 261 ops/sec (±2.10%) 1.02
getSmallResponse 3346 ops/sec (±0.14%) 3295 ops/sec (±0.13%) 0.98
getSmallInferredResponse 2419 ops/sec (±0.39%) 2468 ops/sec (±0.09%) 1.02
getResponse Collection 4485 ops/sec (±0.38%) 4545 ops/sec (±0.59%) 1.01
get Collection 4461 ops/sec (±0.27%) 4617 ops/sec (±0.21%) 1.03
get Query-sorted 5299 ops/sec (±0.14%) 5261 ops/sec (±0.29%) 0.99
setLong 451 ops/sec (±0.24%) 468 ops/sec (±0.23%) 1.04
setLongWithMerge 242 ops/sec (±0.34%) 261 ops/sec (±0.23%) 1.08
setLongWithSimpleMerge 254 ops/sec (±0.48%) 276 ops/sec (±0.22%) 1.09
setSmallResponse 500x 925 ops/sec (±0.06%) 939 ops/sec (±0.15%) 1.02

This comment was automatically generated by workflow using github-action-benchmark.

@ntucker ntucker merged commit 467a5f6 into master Apr 5, 2026
27 checks passed
@ntucker ntucker deleted the fix/deepclone-own-properties branch April 5, 2026 20:06
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.

1 participant