fix: preserve own keys through serialization - #133
Conversation
Both loader-data decoders wrote object keys with `result[key] = value`.
In browsers, assigning to `__proto__` sets the object's prototype
instead of creating an own property, so a loader returning
`{"__proto__": {"isAdmin": true}}` reached the client with the key
gone and `isAdmin` inherited. Keys are now written with
Object.defineProperty on the page-load, data-request, streamed, and
deferred decode paths.
The regression test decodes in a child `deno eval` that installs the
browser `__proto__` accessor, since Deno assigns it as an ordinary
property and the test process must not mutate globals.
Closes #132
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Security review at No findings. The fix closes juniper#132 on every decode path that builds an object from payload keys. Both new call sites are load-bearing: reverting either one turns its own path red, and no other path. The result keeps the default prototype, and normal data comes out the same as before. Severity of what this closes (for the record)
Decode paths examined (at
|
|
fix: preserve own keys before serialization — Extended 92abe27 to both preprocessing functions. Both browser-accessor modes cover all four paths; all five focused assignment mutations fail at the intended assertion. Full check and test gates pass (34 suites, 388 steps). Raw deno doc reports the same 25 known diagnostics as main; the PR body records that outstanding raw-lint limitation. Commit 20a2355 is pushed. No merge performed. |
|
🎉 This PR is included in version 0.11.6 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Summary
Preserve untrusted own
__proto__keys throughout Juniper serialization. Browser-style assignment previously substituted the object's prototype in the decoders; the same assignment in the preprocessing functions could drop the key before encoding. All four object-copy functions now usedefineOwnValue.Changes
processValueandprocessValueForStreaming.isAdmin, and unaffectedObject.prototype. The accessor runs only in a child process; sanitizer settings are unchanged.Testing
deno task test --filter "decoding an own __proto__" _serialization.test.tsfailed atassertEquals(paths, ...)in the encode-with-accessor case. All four paths hadhasOwnProto: falseandownKeys: ["name"].defineOwnValuewith assignment in the helper,processValue,processValueForStreaming,restoreValue, andrestoreValueWithPendingPromises. Every run exited 1 atAssertionError: Values are not equal.Encoder mutations lost the own key; decoder mutations also producedprotoIsObjectPrototype: falseand inheritedisAdmin: true. Restored source passes the full suite.deno task check: passes, including doc-lint's nine entrypoints and their JSDoc examples.deno task test --parallel --reporter=dot: 34 passed (388 steps), zero failures. The sandboxed run could not read esbuild temporary projects; the host run passed without code or sanitizer changes.deno doc --lintover all nine export-map entrypoints exits 1 with 25 existing diagnostics, identical on main and this branch. The repository's doc-lint script explicitly allows those external-type/private-member diagnostics. This is an outstanding exception to the requested raw-lint-clean gate, not a claimed raw-lint pass.Follow-up
This is step 1 of the founder's tagged JSON decision. PR #131 will be rebased and widened after this PR merges. Merging and publication remain reserved for the founder.
Closes
Closes #132