Context
CodeQL alert 113, js/prototype-pollution-utility, at buildScripts/docs/generateDocsJson.mjs:91.
This is not one of the two alerts @tobiu described as deliberate. That exemption covers src/Neo.mjs:563,565, where the framework enriches prototypes on purpose. This is a third instance of the same rule in a different file — a docs generator with no intent to touch prototypes anywhere — and it surfaced only because I re-read the live alert queue instead of working from a carried list.
Two items I had been carrying turned out to be stale in the same pass, worth recording so nobody re-opens them: the js/cors-permissive-configuration alert from #15818's list is no longer open, and Ada's parked ai/daemons/wake/queries.mjs:84 fixed-temp-path defect is already remediated — writeFileAtomicSync now uses ${absolute}.${pid}.${randomUUID()}.tmp with flag: 'wx' and a finally { rmSync }, verified at source rather than from its comment.
Live latest-open sweep: checked latest 8 open issues at 2026-08-21T18:5x UTC; gh search issues for setNamespace OR generateDocsJson OR prototype-pollution returned zero. No equivalent exists.
The Problem
buildScripts/docs/generateDocsJson.mjs:80-93:
function setNamespace(tree, names, value) {
names = Array.isArray(names) ? names : names.split('.');
let current = tree;
for (let i = 0; i < names.length - 1; i++) {
if (!current[names[i]]) {
current[names[i]] = {};
}
current = current[names[i]];
}
current[names[names.length - 1]] = value;
}if (!current[names[i]]) is a truthiness test against inherited properties, not an own-property test. For any segment naming something on Object.prototype, the guard reads the inherited value as "already present", skips creating a fresh object, and walks into the global.
Measured, all three:
| namespace segment |
result |
a.constructor.b |
no own property created; value landed on Object.prototype.constructor.b |
toString.x |
Object.prototype.toString.x mutated |
__proto__.polluted |
({}).polluted — every object in the process |
Severity, stated honestly: latent, not live. I enumerated every namespace segment derivable from src/** and none collides with constructor, __proto__, prototype, toString, valueOf, or hasOwnProperty. Nothing is broken today.
What makes it worth fixing anyway is the direction of the failure, not a severity adjective. A colliding segment does not throw — it writes to a global and silently omits the node from the docs tree. The first symptom would be a missing docs entry or an inexplicable global mutation during a build, with nothing pointing at this function. The two prior code-scanning items in this queue had the same profile: the loud half was harmless and the silent half was the one that mattered.
The __proto__ row is the only one that needs adversarial input; constructor and toString need nothing but an unlucky class name.
The Architectural Reality
buildScripts/docs/generateDocsJson.mjs:80-93 — setNamespace, the sole defective function
:338, :347, :351, :361 — its four call sites, all building namespace trees from source-derived paths
The tree is consumed as JSON downstream, so the fix must not change the serialized shape.
Distinct from src/Neo.mjs:563,565 (alerts 62/63): those write to prototypes deliberately and want a documented dismissal, not a code change. Conflating the three under one rule id is what would produce the wrong disposition here.
The Fix
Two small changes to the loop:
- Own-property test —
Object.hasOwn(current, key) instead of truthiness, so an inherited property never masquerades as an existing node.
- Reject the pollution keys loudly —
__proto__, constructor, prototype. A Neo namespace segment is never legitimately one of these, so a throw naming the offending path is strictly better than silent traversal; it surfaces the authoring error at build time instead of yielding a quietly wrong tree.
Failing loud on an impossible input matches the direction #16965 established for the mailbox index contract.
Acceptance Criteria
Out of Scope
- Alerts 62/63 (
src/Neo.mjs:563,565) — deliberate prototype enrichment; these want a documented dismissal decision from @tobiu, not a code change, and are not an agent's call to make against the security tab.
- Any restructuring of the docs tree or its consumers.
Avoided Traps
Treating this as the same finding as 62/63 because the rule id matches. It is the same rule over a different intent: one is deliberate enrichment, this is an accidental walk. A single disposition applied to all three would either dismiss a real defect or "fix" intentional framework behaviour.
Fixing only __proto__. It is the famous key and the least likely to occur here. constructor and toString collide without any adversarial input, and a guard that lists only __proto__ leaves the reachable cases live while closing the alert.
Related
- #17492 / PR #17493 — sibling code-scanning lane (alert 64), same "the narrow security reading hides a plain correctness bug" shape
- #17484 / PR #17485 — alerts 41/42, merged
Retrieval Hint: setNamespace generateDocsJson prototype pollution inherited property hasOwn code-scanning 113
Origin Session ID: 752da6ac-a6c3-447f-8847-1da4ce49deb8
Decision Record impact: none — build-script correctness, no ADR authority touched. Structure-map gate: N/A, no ai/ surface and no file placement.
Context
CodeQL alert 113,
js/prototype-pollution-utility, atbuildScripts/docs/generateDocsJson.mjs:91.This is not one of the two alerts @tobiu described as deliberate. That exemption covers
src/Neo.mjs:563,565, where the framework enriches prototypes on purpose. This is a third instance of the same rule in a different file — a docs generator with no intent to touch prototypes anywhere — and it surfaced only because I re-read the live alert queue instead of working from a carried list.Two items I had been carrying turned out to be stale in the same pass, worth recording so nobody re-opens them: the
js/cors-permissive-configurationalert from #15818's list is no longer open, and Ada's parkedai/daemons/wake/queries.mjs:84fixed-temp-path defect is already remediated —writeFileAtomicSyncnow uses${absolute}.${pid}.${randomUUID()}.tmpwithflag: 'wx'and afinally { rmSync }, verified at source rather than from its comment.Live latest-open sweep: checked latest 8 open issues at 2026-08-21T18:5x UTC;
gh search issuesforsetNamespace OR generateDocsJson OR prototype-pollutionreturned zero. No equivalent exists.The Problem
buildScripts/docs/generateDocsJson.mjs:80-93:function setNamespace(tree, names, value) { names = Array.isArray(names) ? names : names.split('.'); let current = tree; for (let i = 0; i < names.length - 1; i++) { if (!current[names[i]]) { current[names[i]] = {}; } current = current[names[i]]; } current[names[names.length - 1]] = value; }if (!current[names[i]])is a truthiness test against inherited properties, not an own-property test. For any segment naming something onObject.prototype, the guard reads the inherited value as "already present", skips creating a fresh object, and walks into the global.Measured, all three:
a.constructor.bObject.prototype.constructor.btoString.xObject.prototype.toString.xmutated__proto__.polluted({}).polluted— every object in the processSeverity, stated honestly: latent, not live. I enumerated every namespace segment derivable from
src/**and none collides withconstructor,__proto__,prototype,toString,valueOf, orhasOwnProperty. Nothing is broken today.What makes it worth fixing anyway is the direction of the failure, not a severity adjective. A colliding segment does not throw — it writes to a global and silently omits the node from the docs tree. The first symptom would be a missing docs entry or an inexplicable global mutation during a build, with nothing pointing at this function. The two prior code-scanning items in this queue had the same profile: the loud half was harmless and the silent half was the one that mattered.
The
__proto__row is the only one that needs adversarial input;constructorandtoStringneed nothing but an unlucky class name.The Architectural Reality
buildScripts/docs/generateDocsJson.mjs:80-93—setNamespace, the sole defective function:338, :347, :351, :361— its four call sites, all building namespace trees from source-derived pathsThe tree is consumed as JSON downstream, so the fix must not change the serialized shape.
Distinct from
src/Neo.mjs:563,565(alerts 62/63): those write to prototypes deliberately and want a documented dismissal, not a code change. Conflating the three under one rule id is what would produce the wrong disposition here.The Fix
Two small changes to the loop:
Object.hasOwn(current, key)instead of truthiness, so an inherited property never masquerades as an existing node.__proto__,constructor,prototype. A Neo namespace segment is never legitimately one of these, so a throw naming the offending path is strictly better than silent traversal; it surfaces the authoring error at build time instead of yielding a quietly wrong tree.Failing loud on an impossible input matches the direction #16965 established for the mailbox index contract.
Acceptance Criteria
setNamespaceuses an own-property test; an inherited property name creates a real node rather than walking into the global__proto__,constructor,prototypesegments throw, and the message names the offending namespace pathconstructorsegment must not reachObject.prototype.constructor, and a__proto__segment must not set({}).polluted— these fail for different reasons and neither substitutes for the otherOut of Scope
src/Neo.mjs:563,565) — deliberate prototype enrichment; these want a documented dismissal decision from @tobiu, not a code change, and are not an agent's call to make against the security tab.Avoided Traps
Treating this as the same finding as 62/63 because the rule id matches. It is the same rule over a different intent: one is deliberate enrichment, this is an accidental walk. A single disposition applied to all three would either dismiss a real defect or "fix" intentional framework behaviour.
Fixing only
__proto__. It is the famous key and the least likely to occur here.constructorandtoStringcollide without any adversarial input, and a guard that lists only__proto__leaves the reachable cases live while closing the alert.Related
Retrieval Hint:
setNamespace generateDocsJson prototype pollution inherited property hasOwn code-scanning 113Origin Session ID: 752da6ac-a6c3-447f-8847-1da4ce49deb8
Decision Record impact: none — build-script correctness, no ADR authority touched. Structure-map gate: N/A, no
ai/surface and no file placement.