Skip to content

fix(db): prevent prototype pollution via select() alias paths (#1584)#1595

Open
kevin-dp wants to merge 6 commits into
TanStack:mainfrom
kevin-dp:fix/1584-select-alias-prototype-pollution
Open

fix(db): prevent prototype pollution via select() alias paths (#1584)#1595
kevin-dp wants to merge 6 commits into
TanStack:mainfrom
kevin-dp:fix/1584-select-alias-prototype-pollution

Conversation

@kevin-dp

@kevin-dp kevin-dp commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Fixes #1584.

Summary

.select() alias keys are split on . and walked into the output object without sanitization. Aliases like __proto__.polluted or constructor.prototype.polluted therefore reach Object.prototype and pollute it for the rest of the process. Exploitable through any public entry point that compiles a select (e.g. queryOnce, createLiveQueryCollection).

Plan for this PR (intentional red → green)

To make the vulnerability + fix visible in CI history, this PR is structured as two commits:

  1. First commit — reproduction only. Adds tests/query/select-prototype-pollution.test.ts. CI is expected to fail on this commit, demonstrating the bug exists on main.
  2. Second commit — fix. Adds UnsafeAliasPathError and rejects unsafe alias path segments (__proto__, prototype, constructor) in the select compiler. CI should turn green.

The fix commit will be pushed after CI on commit 1 has reported the expected failure.

Fix details (commit 2, pushed after red is observed)

  • New UnsafeAliasPathError extends QueryCompilationError in packages/db/src/errors.ts.
  • New assertSafeAliasSegments helper in packages/db/src/query/compiler/select.ts, invoked from:
    • addFromObject (validates each non-spread key at compile time, including dotted keys)
    • processNonMergeOp (validates the split alias path at row-processing time)
    • processMerge (validates targetPath)

The thrown error matches the existing QueryCompilationError style (e.g. DistinctRequiresSelectError).

Summary by CodeRabbit

  • Bug Fixes / Security Hardening
    • Hardened .select() alias path handling to reject unsafe prototype-related segments (__proto__, prototype, constructor) and prevent prototype-pollution-style effects.
    • Added UnsafeAliasPathError to explicitly fail queries that attempt unsafe alias paths.
  • Tests
    • Added coverage to ensure unsafe alias targets are rejected and Object.prototype remains unmodified after failures.

Adds a failing test demonstrating that .select() alias paths like
`__proto__.polluted` or `constructor.prototype.polluted` are split
on '.' and walked into the result object without sanitization,
allowing prototype pollution through queryOnce().

This commit intentionally fails CI to demonstrate the vulnerability;
the next commit fixes it.
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 23813acd-617c-4452-9342-d2195a82701b

📥 Commits

Reviewing files that changed from the base of the PR and between 6cc63e3 and 5f3fe93.

📒 Files selected for processing (1)
  • .changeset/select-alias-prototype-pollution.md
✅ Files skipped from review due to trivial changes (1)
  • .changeset/select-alias-prototype-pollution.md

📝 Walkthrough

Walkthrough

Prototype pollution protection is added to the select() query mechanism. A new error class rejects unsafe alias segments, validation logic is injected at three compilation points, and a test suite verifies both rejection and Object.prototype integrity.

Changes

Prototype Pollution Guard for select() Aliases

Layer / File(s) Summary
Safety validation contract and helper
packages/db/src/errors.ts, packages/db/src/query/compiler/select.ts
UnsafeAliasPathError is added as a new exported query compilation error class. select.ts imports this error, defines an UNSAFE_ALIAS_SEGMENTS set containing __proto__, prototype, and constructor, and implements assertSafeAliasSegments() helper to validate and reject paths using those segments.
Validation enforcement at compilation points
packages/db/src/query/compiler/select.ts
processMerge, processNonMergeOp, and addFromObject each call assertSafeAliasSegments() to validate target paths and alias segments before writing to result objects, blocking prototype-pollution attempts at compilation time.
Prototype pollution rejection tests
packages/db/tests/query/select-prototype-pollution.test.ts
Vitest suite creates an in-memory users collection and invokes queryOnce().select(...) with __proto__.polluted and constructor.prototype.polluted aliases, asserting both queries throw UnsafeAliasPathError and confirming Object.prototype.polluted remains undefined after each failure.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

A bunny guards the prototype door,
With __proto__ blocked forevermore,
Safe aliases through the compiler's gate,
No pollution in this select state,
🐇✨ Prototype protection—secure and great!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: preventing prototype pollution via select() alias paths, with issue reference #1584.
Description check ✅ Passed The description comprehensively explains the vulnerability, fix strategy, implementation details, and includes a changeset entry covering all required template sections.
Linked Issues check ✅ Passed The code changes directly address all objectives from issue #1584: reject dangerous segments (proto, prototype, constructor), protect Object.prototype, secure public APIs, and preserve legitimate select() functionality.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing prototype pollution in select alias handling: new error class, validation logic in select compiler, tests, and changesets documentation.

✏️ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/db/tests/query/select-prototype-pollution.test.ts`:
- Around line 26-27: The test uses `({} as any).polluted` to check for prototype
pollution, which relies on unsafe `any` casts and makes the security assertion
ambiguous. Replace this pattern by creating a properly typed object literal
(without `any` casts) and explicitly check that the `polluted` property is NOT
an own property of Object.prototype using Object.prototype.hasOwnProperty or
Object.getOwnPropertyNames to make the security assertion clearer and more
explicit. Apply this change to all occurrences of the `({} as any).polluted`
pattern in the file (found at lines 26-27, 36-39, and 50-50).
- Around line 28-35: The test using `.rejects.toThrow()` without specifying the
expected error type or message is too permissive and will pass on any error, not
just the security-related one. Modify the `.rejects.toThrow()` calls in the
queryOnce test blocks to specify the expected error type or message that should
be thrown for unsafe alias paths (such as UnsafeAliasPathError or a specific
error message pattern). Apply this same fix to all similar test assertions in
the file that validate unsafe alias path handling.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: c0c210b7-5304-44f3-b8a8-47afe8f22ea6

📥 Commits

Reviewing files that changed from the base of the PR and between 4d1abde and 0dda114.

📒 Files selected for processing (1)
  • packages/db/tests/query/select-prototype-pollution.test.ts

Comment thread packages/db/tests/query/select-prototype-pollution.test.ts Outdated
Comment thread packages/db/tests/query/select-prototype-pollution.test.ts
@pkg-pr-new

pkg-pr-new Bot commented Jun 17, 2026

Copy link
Copy Markdown
More templates

@tanstack/angular-db

npm i https://pkg.pr.new/@tanstack/angular-db@1595

@tanstack/browser-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/browser-db-sqlite-persistence@1595

@tanstack/capacitor-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/capacitor-db-sqlite-persistence@1595

@tanstack/cloudflare-durable-objects-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/cloudflare-durable-objects-db-sqlite-persistence@1595

@tanstack/db

npm i https://pkg.pr.new/@tanstack/db@1595

@tanstack/db-ivm

npm i https://pkg.pr.new/@tanstack/db-ivm@1595

@tanstack/db-sqlite-persistence-core

npm i https://pkg.pr.new/@tanstack/db-sqlite-persistence-core@1595

@tanstack/electric-db-collection

npm i https://pkg.pr.new/@tanstack/electric-db-collection@1595

@tanstack/electron-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/electron-db-sqlite-persistence@1595

@tanstack/expo-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/expo-db-sqlite-persistence@1595

@tanstack/node-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/node-db-sqlite-persistence@1595

@tanstack/offline-transactions

npm i https://pkg.pr.new/@tanstack/offline-transactions@1595

@tanstack/powersync-db-collection

npm i https://pkg.pr.new/@tanstack/powersync-db-collection@1595

@tanstack/query-db-collection

npm i https://pkg.pr.new/@tanstack/query-db-collection@1595

@tanstack/react-db

npm i https://pkg.pr.new/@tanstack/react-db@1595

@tanstack/react-native-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/react-native-db-sqlite-persistence@1595

@tanstack/rxdb-db-collection

npm i https://pkg.pr.new/@tanstack/rxdb-db-collection@1595

@tanstack/solid-db

npm i https://pkg.pr.new/@tanstack/solid-db@1595

@tanstack/svelte-db

npm i https://pkg.pr.new/@tanstack/svelte-db@1595

@tanstack/tauri-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/tauri-db-sqlite-persistence@1595

@tanstack/trailbase-db-collection

npm i https://pkg.pr.new/@tanstack/trailbase-db-collection@1595

@tanstack/vue-db

npm i https://pkg.pr.new/@tanstack/vue-db@1595

commit: 5f3fe93

kevin-dp and others added 5 commits June 17, 2026 16:39
Adds a new `UnsafeAliasPathError` (extends QueryCompilationError) and an
`assertSafeAliasSegments` helper invoked in three places in
packages/db/src/query/compiler/select.ts:

- `addFromObject` validates each non-spread key at compile time, including
  dotted keys, before recording any select operation.
- `processNonMergeOp` validates the split alias path before walking into
  the result object.
- `processMerge` validates `targetPath` for the same reason.

Segments matching `__proto__`, `prototype`, or `constructor` are rejected,
which prevents prototype pollution via aliases like `__proto__.polluted`
or `constructor.prototype.polluted` going through queryOnce() /
createLiveQueryCollection().

Fixes TanStack#1584
- Import UnsafeAliasPathError and assert that the rejection is exactly
  that error class instead of a permissive .rejects.toThrow().
- Drop the `({} as any).polluted` pattern in favour of
  Object.prototype.hasOwnProperty.call(Object.prototype, 'polluted'),
  which is type-safe and a more explicit assertion that
  Object.prototype itself was not mutated.
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.

Prototype Pollution via queryOnce().select() Alias Handling

1 participant