security(helpers): filter __proto__ and constructor in deepCopy#26
Merged
Conversation
Addresses Sapper M1 finding M5 (prototype pollution via JSON.parse input). JSON.parse treats __proto__ and constructor as literal own enumerable properties, unlike object literals. Passing such parsed data to deepCopy would assign copiedObject.__proto__ = attackerValue, which triggers the prototype setter and replaces the copy's prototype with an attacker- controlled object. A consumer checking inherited properties (e.g., `if (obj.admin)`) without Object.hasOwn could then be fooled. Fix: skip __proto__ and constructor keys during the key iteration. No legitimate deepCopy caller supplies plain-object data with a literal "__proto__" own property — that only arises from untrusted input. Tests: 3 new cases verify the prototype is preserved, constructor is dropped, and other safe keys in the same payload are still copied. Coverage + mutation score remain 100% on deep-copy.ts. Bumps fs-helpers to 0.1.1 (triggers publish workflow on merge). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Deploying fs-packages with
|
| Latest commit: |
4214a54
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://911647b2.fs-packages.pages.dev |
| Branch Preview URL: | https://security-deepcopy-prototype.fs-packages.pages.dev |
jasperboerhof
approved these changes
Apr 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes Sapper M1 finding M5 —
deepCopyprototype pollution (field report lives in the war-room meta-repo, not this one).The bug
JSON.parsetreats__proto__andconstructoras literal own enumerable keys, unlike object literals where__proto__is a getter/setter onObject.prototype. That means:```js
const payload = JSON.parse('{"proto": {"polluted": "yes"}}');
Object.keys(payload); // ["proto"] ← own enumerable
```
The previous
deepCopyiterated withObject.keysand assigned viacopiedObject[key] = .... Whenkey === "__proto__", that assignment triggered the prototype setter, replacing the copy's prototype with an attacker-controlled object. A consumer checking inherited properties (e.g.,if (obj.admin)) withoutObject.hasOwnwould then be fooled.The fix
One line added to the iteration:
```ts
for (const key of Object.keys(toCopy)) {
if (key === "proto" || key === "constructor") continue;
copiedObject[key] = deepCopy(...);
}
```
No legitimate caller supplies plain-object data with a literal `proto` own property — that only arises from untrusted input. `constructor` filtering is defense-in-depth (it never triggers prototype ops, but can shadow the real `constructor` on the copy, which is a known attack surface in some frameworks).
Tests
3 new cases in `prototype pollution resistance`:
All 8 local gates pass: format, lint, build, typecheck, lint:pkg, 100% coverage, 100% mutation score on `deep-copy.ts` (23 mutants killed).
Release
Bumps `@script-development/fs-helpers` to `0.1.1`. On merge the publish workflow fires (triggered by `**/package.json` change on `main`), ships the patch to npm.
Test plan
@script-development/fs-helpers@0.1.1present🤖 Generated with Claude Code