Skip to content

fix: strip prototype pollution keys from FormData JSON parsing#1850

Open
eddieran wants to merge 2 commits intoelysiajs:mainfrom
eddieran:fix/formdata-prototype-pollution
Open

fix: strip prototype pollution keys from FormData JSON parsing#1850
eddieran wants to merge 2 commits intoelysiajs:mainfrom
eddieran:fix/formdata-prototype-pollution

Conversation

@eddieran
Copy link
Copy Markdown

@eddieran eddieran commented Apr 12, 2026

Summary

Fixes #1848 — FormData auto-parse prototype pollution.

This replaces #1849 (which was auto-closed when the fork was deleted) and addresses the gap identified in the CodeRabbit review: the nested key path parsing code was calling JSON.parse on intermediate values without stripping dangerous keys.

Changes

  • Added a stripDangerousKeys function that recursively removes __proto__, constructor, and prototype keys from parsed objects
  • Applied stripDangerousKeys to all JSON.parse results in FormData processing:
    • normalizeFormValue in src/dynamic-handle.ts (single-value JSON parse and file+JSON mixed parse)
    • parseObjectString in src/dynamic-handle.ts (nested key path intermediate object parsing)
    • All three JSON.parse call sites in the inlined formData parser in src/adapter/web-standard/index.ts — including the nested key path JSON.parse(existing) that the review flagged
  • Added comprehensive tests covering __proto__, constructor, prototype stripping at top-level, nested, and deeply nested JSON, as well as nested key path (user.profile) and dangerous key path (constructor.prototype) scenarios

Test plan

  • All 8 new tests in test/core/formdata-prototype-pollution.test.ts pass
  • Existing test/core/formdata.test.ts tests continue to pass (no regressions)

Summary by CodeRabbit

  • Bug Fixes

    • Improved form-data and JSON parsing to strip unsafe prototype-related keys from incoming values, preventing prototype-pollution from crafted inputs.
  • Tests

    • Added end-to-end tests validating protection against prototype pollution across single fields, nested structures, arrays, dotted/bracket paths, and deep JSON payloads.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 553f5dc1-b50c-4c48-a75e-ea3ad1f89093

📥 Commits

Reviewing files that changed from the base of the PR and between e4feddd and 0c69621.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • test/core/formdata-prototype-pollution.test.ts
✅ Files skipped from review due to trivial changes (1)
  • test/core/formdata-prototype-pollution.test.ts

Walkthrough

FormData JSON parsing now strips prototype-pollution keys (__proto__, constructor, prototype) via recursive sanitization added to the WebStandard adapter and dynamic handler; tests were added to verify dangerous keys are removed and no global prototype pollution occurs.

Changes

Cohort / File(s) Summary
WebStandard adapter & dynamic handler
src/adapter/web-standard/index.ts, src/dynamic-handle.ts
Added a recursive stripDangerousKeys() sanitizer and applied it to all JSON-parsing paths for FormData values (single entries, array elements, nested/dotted/bracket assignments). Removes __proto__, constructor, and prototype from parsed objects.
Test coverage
test/core/formdata-prototype-pollution.test.ts
New Bun test asserting dangerous keys are stripped across varied FormData encodings and nested paths, and that Object.prototype is not polluted.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Adapter as WebStandard Adapter
  participant Handler as DynamicHandler
  participant Sanitizer as stripDangerousKeys
  participant App as Application Route

  Client->>Adapter: POST multipart/form-data
  Adapter->>Handler: normalized form fields (strings/JSON)
  Handler->>Sanitizer: JSON.parse(...) results
  Sanitizer-->>Handler: sanitized objects (dangerous keys removed)
  Handler->>App: constructed request body (safe)
  App-->>Client: response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

ねぇねぇ、危ないキーはもうナイよ〜♡ (`ー´)フゥ
JSONが来たらこっそり削除しちゃうんだよ〜♡
proto?ぶっ飛ばす〜 ザマァ見ろ〜 (≧▽≦)
サーバは無事、プロト汚染はアウト〜♡
よくやったね、でももっと早くしなさいよ〜♡

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding prototype pollution key stripping to FormData JSON parsing, which is the primary purpose of the PR.
Linked Issues check ✅ Passed The PR fully addresses the FormData prototype pollution fix from #1848 by implementing stripDangerousKeys across all JSON.parse sites in dynamic-handle.ts and web-standard adapter, with comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing FormData prototype pollution; query type confusion from #1848 is acknowledged but not implemented, which is appropriate for a focused PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/adapter/web-standard/index.ts (1)

61-69: Security logic is duplicated in two places — extract a shared source, baka~

stripDangerousKeys/dangerous-key rules here mirror src/dynamic-handle.ts (Line 40-56). Since this is security-sensitive, duplication risks drift in future patches. Please centralize the literal generation or shared sanitizer definition.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/adapter/web-standard/index.ts` around lines 61 - 69, The sanitizer logic
is duplicated: the inline function stripDangerousKeys and its dangerousKeys set
here mirror the implementation in src/dynamic-handle.ts; centralize them by
extracting the shared sanitizer (e.g., export a sanitize/stripDangerousKeys
function and the dangerousKeys definition from src/dynamic-handle.ts) and import
and use that single exported symbol in this file (index.ts) instead of
redefining the function/string template, ensuring both places call the same
implementation to avoid drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/core/formdata-prototype-pollution.test.ts`:
- Around line 24-29: The tests currently try to send an own "__proto__" property
but use a bare __proto__ literal which becomes a prototype setter and is omitted
by JSON.stringify/Object.entries, so update the test payloads (the calls to
formPost and any JSON.stringify in formdata-prototype-pollution.test.ts) to
actually include an own "__proto__" key — either build objects with computed
property names (e.g., { ['__proto__']: { polluted: true }, safe: 'value' }) or
pass raw JSON strings that include "__proto__" — apply this change to all
occurrences around the JSON.stringify calls used in formPost and related tests
so the sanitizer receives the intended payload.

---

Nitpick comments:
In `@src/adapter/web-standard/index.ts`:
- Around line 61-69: The sanitizer logic is duplicated: the inline function
stripDangerousKeys and its dangerousKeys set here mirror the implementation in
src/dynamic-handle.ts; centralize them by extracting the shared sanitizer (e.g.,
export a sanitize/stripDangerousKeys function and the dangerousKeys definition
from src/dynamic-handle.ts) and import and use that single exported symbol in
this file (index.ts) instead of redefining the function/string template,
ensuring both places call the same implementation to avoid drift.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8165c26b-4d7a-4592-bfd8-4a688bd4d35f

📥 Commits

Reviewing files that changed from the base of the PR and between 56310be and e4feddd.

📒 Files selected for processing (3)
  • src/adapter/web-standard/index.ts
  • src/dynamic-handle.ts
  • test/core/formdata-prototype-pollution.test.ts

Comment thread test/core/formdata-prototype-pollution.test.ts
In JS object literals, `__proto__` acts as a prototype setter rather
than an own enumerable property, so `JSON.stringify({ __proto__: ... })`
drops it and `FormData` never sees it either. The tests therefore
weren't actually exercising the sanitization path. Build the JSON
strings and FormData instances manually so the dangerous keys show up
in the parsed input. Caught by @coderabbitai in elysiajs#1850.
@eddieran
Copy link
Copy Markdown
Author

Thanks @coderabbitai — valid catch. In an object literal, __proto__ is treated as a prototype setter (not an own enumerable property), so JSON.stringify({ __proto__: {...} }) drops it entirely and the sanitizer was never actually seeing the dangerous key on the wire. I rewrote the payloads to raw JSON strings (and built the top-level FormData by hand for the same reason). All 8 tests still pass.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2026

Tip

For best results, initiate chat on the files or code changes.

@eddieran: Fuun, so you actually did understand what I was pointing out ♡ Of course you had to rewrite the payloads — using { __proto__: {...} } as an object literal is basically telling JavaScript "please silently eat this key and change the prototype, kthxbye" (ᗒᗜᗕ)՞ JSON.stringify then just… pretends it was never there, and your sanitizer gets to sit around doing absolutely nothing~ how embarrassing that would've been, nee~?

Raw JSON strings like '{"__proto__":{"polluted":true}}' actually keep the key on the wire where it belongs, so now the sanitizer is actually being tested~ ♡ Good boyyy, you figured it ouuut (≧◡≦)


✏️ Learnings added
Learnt from: eddieran
URL: https://github.com/elysiajs/elysia/pull/1850

Timestamp: 2026-04-16T22:18:14.673Z
Learning: In JavaScript, using `__proto__` as a key in an object literal (e.g., `{ __proto__: { polluted: true } }`) acts as a prototype setter rather than creating an own enumerable property. As a result, `JSON.stringify()` silently drops the key entirely. When writing tests for prototype-pollution sanitizers in Elysia (or any JS/TS project), prototype-pollution payloads must be constructed as raw JSON strings (e.g., `'{"__proto__":{"polluted":true}}'`) or via `FormData.append` with a raw string value — never via object literals with `__proto__` keys — to ensure the dangerous key actually appears on the wire and reaches the sanitizer.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security] Query type confusion and FormData auto-parse prototype pollution

1 participant