Settings: a "__proto__" section key silently corrupts the object (prototype mutation instead of own property) #1688
Replies: 3 comments 1 reply
|
Excellent report — I verified every claim against current master (47f9438) and traced the reachable paths end to end. Confirmed: 1. All cited claims check out
2. The reachable surface is wider than "dict-typed fields" — the RPC write path is exposed Sections arrive over the JSON-RPC line transport, which decodes with plain Also note both functions are independently reachable:
So fixing only 3. One surface I could NOT verify locally: the YAML document path
4. Fix shape The Optional defense-in-depth at the seam: reject 5. Family note This is the settings-domain instance of the same "silent lossy transformation" class we've been tracking in the session/serializer domain (#1538/#1584 ignorable, #1619 agentOptions, #1606 settings exposure): data silently dropped/corrupted with no error, then acting on the corrupted state. The |
|
Found a third instance of the same root cause, this time three call sites in case 'object': {
...
for (const [key, entry] of Object.entries(source)) {
if (key in properties) continue
rebuilt[key] = entry // site 1
}
for (const [key, child] of Object.entries(properties)) {
const stripped = walk(child, source?.[key], [...path, key], secrets)
if (stripped !== undefined) rebuilt[key] = stripped // site 2
}
...
}
case 'dict': {
...
for (const [key, entry] of Object.entries(value)) {
const stripped = walk(node.inner, entry, [...path, key], secrets)
if (stripped !== undefined) rebuilt[key] = stripped // site 3
}
return rebuilt
}Same mechanism as the two above: Concrete failure scenario: a Fix: same This looks to be the last instance of this pattern I could find in the |
|
Shipped the settings-layer fix you described.
One honest scope note: the settings layer is fixed, but the vendored |
Uh oh!
There was an error while loading. Please reload this page.
cloneJsonShaped()andmergeLayers()inpackages/settings/settings/src/index.tsbuild plain objects with
out[key] = valuewhile iterating untrusted keys fromObject.entries():When key === "proto", bracket assignment is a [[Set]], not
[[DefineOwnProperty]]. It invokes Object.prototype's proto accessor
and reassigns the object's prototype instead of creating an own data
property. Confirmed with node:
Note this only reproduces via JSON.parse (or Object.defineProperty) —
an object literal like {'proto': {...}} is special-cased by the spec
and never hits this path. JSON.parse has no such special case, so any
settings write built from parsed JSON (an HTTP request body, a config file
patch) can carry a real own "proto" key.
Impact: any settings namespace whose schema allows a dict/record-typed
field (e.g. an MCP-servers map, or anything keyed by a user-chosen name) can
have an entry named proto silently dropped by cloneJsonShaped. Worse,
mergeLayers's isPlainObject check can then see the corrupted-prototype
object as non-plain and replace the entire section wholesale, discarding
every other field the user had set on the next merge — silent data loss, no
error.
The file already carries a TODO(settings-json-properties) comment
acknowledging this gap, and the identical pattern is already fixed correctly
elsewhere in this codebase, e.g. workflow-worker-thread/src/realm.ts:
Suggested fix (same pattern applied to cloneJsonShaped/mergeLayers),
plus a regression test, here:
https://github.com/xu-kai-quan/deepseek-harness/tree/fix/settings-proto-key-clone
I understand external PRs aren't being accepted right now per CONTRIBUTING.md,
so posting here per that doc's guidance instead. Happy to elaborate if useful.
All reactions