Stop prototype pollution at merge and authorization boundaries
Node.js 22+; no dependencies, service, credentials or network required.

Reference solution: not included here, so the exercise does not hand you the answer.
Attempt the repair first. Then download prototype-pollution-solution.zip from the project card
on https://softwaresecurity.com/labs/#project-prototype-pollution and extract it into this folder;
it adds reference.mjs without changing your files, and the reference commands below
need it.

Objective: observe an actual inherited-property write, then separate a narrow
profile schema, inert arbitrary-key data and server-owned authorization state.
Prerequisites: JavaScript objects, JSON parsing and own vs inherited properties.

1. Run node --test exercise.test.mjs. The normal name/theme cases pass. The
   supplied JSON __proto__/constructor.prototype cases actually mutate the local
   process's Object.prototype in the starter; the harness restores it after each
   case. Do not run the starter inside an application process you care about.
2. Trace merge: target['__proto__'] initially resolves to an inherited prototype.
   target['constructor']['prototype'] is another route through inherited values.
   Recursion follows those objects and writes isAdmin there. A fresh ordinary
   object then appears to have isAdmin, despite having no own admin property.
   JSON.parse alone does not perform that mutation; the later merge does.
3. Replace the generic merge with the profile's real schema: displayName is a
   nonblank string of at most 80 UTF-16 code units; preferences.theme is light or
   dark. Reject other keys, wrong types, arrays and null. Construct a fresh
   result from selected fields. Do not recursively write arbitrary property
   paths. Use Object.create(null) for fresh records in this exercise.
4. Repair canAdmin: session must contain its own isAdmin property equal to the
   boolean true. Use Object.hasOwn, not session.hasOwnProperty, which may be
   absent or shadowed. This is server-created authenticated session state; never
   pass the submitted profile patch directly into an authorization decision.
5. Repair labelsDictionary. It intentionally accepts arbitrary string labels,
   including __proto__, as inert own data keys. A null-prototype dictionary
   avoids inherited setters/methods. Keep values bounded strings and never turn
   this dictionary into options, authority or another unsafe object merge.
6. Run all 18 tests. Reference: EXERCISE_MODULE=reference node --test exercise.test.mjs
   PowerShell: $env:EXERCISE_MODULE='reference'; node --test exercise.test.mjs
   Remove the variable before checking your repairs. Compare reference.mjs last.

Keep: a before/after diagram of the object lookup chain, the exact mutation point,
and test evidence that legitimate profile changes and real admin sessions work.
Complete when all 18 tests pass and you can explain why schema selection,
null-prototype data and own-property authorization solve different boundaries.

Scope: real JavaScript prototype behavior in one isolated test process. Inputs to
mergeProfile are JSON-parsed values; arbitrary JavaScript getters/proxies are not
part of this boundary. No claim is made about a particular third-party library.
Object.create(null) is not an input-validation or authorization substitute.
Disabling __proto__ alone does not remove constructor.prototype routes. Hardened
runtimes and maintained dependencies add defense but do not fix unsafe policy.

Sources (checked 9 October 2026):
https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
https://cheatsheetseries.owasp.org/cheatsheets/Prototype_Pollution_Prevention_Cheat_Sheet.html

Static analysis (optional)
semgrep/prototype-pollution.yaml holds Semgrep rules for the defect in this
project, and semgrep/prototype-pollution.mjs holds their test cases. With
Semgrep installed (python3 -m pip install semgrep), run from this folder:
   semgrep --test semgrep/
   semgrep scan --metrics=off --config semgrep/prototype-pollution.yaml exercise.mjs
The starter is reported at line 6 and line 23; the reference solution is not.
Checked with Semgrep 1.180.0. CodeQL 2.27.2 (codeql/javascript-queries) did not
report this merge with js/prototype-pollution-utility or
js/prototype-polluting-assignment when checked on 10 October 2026. Analyzers
model different code shapes, so keep the regression tests as the proof.

Dev container
.devcontainer/devcontainer.json opens this folder in VS Code Dev Containers or
GitHub Codespaces using the Dev Containers Node.js 24 image, pinned by digest.
Run the commands above in its terminal.
