Skip to content

Add nosql taint type and mark MongoDB sinks - #11944

Merged
danog merged 10 commits into
vimeo:masterfrom
danog:add-nosql-taint-type-master
Sep 15, 2026
Merged

danog merged 10 commits into
vimeo:masterfrom
danog:add-nosql-taint-type-master

Conversation

@danog

@danog danog commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a new nosql taint type (part of the input taint class) and marks the appropriate MongoDB driver parameters as sinks, so user-controlled data flowing into a MongoDB query/command/filter is reported.

This is the master (bitmask taint design) counterpart of #11943 (which targets 6.x).

Changes

  • New taint bit TaintKind::INPUT_NOSQL, added to ALL_INPUT and TAINT_NAMES (USER_SECRET/SYSTEM_SECRET bits shifted up to keep ALL_INPUT contiguous; BUILTIN_TAINT_COUNT bumped to 20).
  • New TaintedNosql issue (shortcode 369), wired into TaintFlowGraph and config.xsd.
  • NoSQL taint only applies to values that can hold an array/object. A plain string can never be a NoSQL query, so getTaintsToRemove() strips nosql from string-typed values — casting user input to string acts as a natural escape (new TaintKind::ARRAY_ONLY, mirroring the existing NUMERIC_ONLY/BOOL_ONLY design for sleep).
  • Marked MongoDB driver sinks in stubs/extensions/mongodb.phpstub:
    • MongoDB\Driver\Query::__construct $filter
    • MongoDB\Driver\Command::__construct $document
    • MongoDB\Driver\BulkWrite::insert/update/delete filter and document params
    • Added a minimal MongoDB\Driver\Manager so the documented example is runnable.
  • Documentation page TaintedNosql.md with concrete injection examples and safe alternatives (cast to string, @psalm-taint-escape nosql), plus positive/negative test coverage in TaintTest.

Test plan

  • vendor/bin/phpunit tests/TaintTest.php
  • vendor/bin/phpunit tests/DocumentationTest.php

🤖 Generated with Claude Code

danog and others added 2 commits September 14, 2026 14:21
Add a new `nosql` taint type (part of the input taint class) and the
corresponding `TaintedNosql` issue. Mark the appropriate MongoDB driver
parameters as `nosql` sinks in the extension stub:

- MongoDB\Driver\Query::__construct $filter
- MongoDB\Driver\Command::__construct $document
- MongoDB\Driver\BulkWrite::insert/update/delete filter and document params

Co-Authored-By: Claude Opus 4.8 
A plain string can never be a NoSQL query, so the nosql taint is now
stripped from string-typed values (via getTaintsToRemove) - casting user
input to string escapes it. Add concrete injection examples, an escape
function example (@psalm-taint-escape nosql) and a minimal MongoDB\Driver\Manager
stub so the documentation example is runnable.

Co-Authored-By: Claude Opus 4.8 

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread src/Psalm/Type/UnionTrait.php
Addresses review feedback: casting an inner value to string inside an
array filter (e.g. new Query(["username" => (string) $_GET["username"]]))
now actually removes the nosql taint, matching the documented safe pattern.
Casting to string is escapes any array-only taint via getTaintsToRemove().

Also makes the sanitize_mongo_filter example a concrete sanitizer that
forces filter values to scalars.

Co-Authored-By: Claude Opus 4.8 
@danog danog added the release:feature The PR will be included in 'Features' section of the release notes label Sep 14, 2026
@danog

danog commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

danog and others added 3 commits September 14, 2026 15:05
The previous attempt to strip nosql on every string cast rerouted all
string-cast taint flows through an extra graph node, changing every taint
graph / SARIF snapshot (and adding noise to unrelated html/sql flows).

Instead rely on the existing sink-boundary stripping (getTaintsToRemove):
a string-typed argument reaching a nosql sink is not reported. For array
filters, the documented escape is a sanitizer annotated @psalm-taint-escape
nosql. Docs and tests updated to match this actual behavior.

Co-Authored-By: Claude Opus 4.8 
…hots

Restoring the string-cast nosql escaping (per review), but scoped to the
taint flow graph only so the variable-use graph - and therefore unused
variable/reference analysis and Psalm self-analysis - is unaffected.

Casting to string now inserts a pass-through "string-cast" node on the taint
graph that drops array-only taints (nosql). Updated the affected taint-path
snapshots: TaintTest path strings, the taint graph dump fixture, and the SARIF
report fixture.

Co-Authored-By: Claude Opus 4.8 
@danog

danog commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Adding the string-cast pass-through node only to the taint graph while
rerouting the value's parent_nodes broke variable-use tracking (the chain
pointed at a node absent from the variable-use graph), causing false unused
variable reports and a PossiblyNullReference in self-analysis.

Add it to the active data-flow graph instead: the flow chain stays intact in
both modes, and removed_taints only takes effect for taint analysis.

Co-Authored-By: Claude Opus 4.8 
@danog

danog commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

(int)/(float)/(bool) casts now route their value through a taint
pass-through node (like (string) already did), removing every taint
that cannot survive the target scalar while preserving those a scalar
can still carry (e.g. sleep on numerics). This runs consistently in
taint-only, variable-use-only and combined analysis modes, fixing both
a false negative (sleep((int) $_GET[...]) was silently untainted) and a
false positive (a value cast to a scalar inside an array no longer
flags nosql/sql).

Union::getTaintsToRemove() drops its isSingle() guard so literal unions
such as int(0)|int(1) produced by casting a bool are handled too;
isInt()/isString() already require every atomic member to match.

Add TaintTest coverage for taints correctly stripped by each numeric
cast and for taints that must survive (sleep through int/float, sql
through string), and document that any scalar cast escapes nosql.

Co-Authored-By: Claude Opus 4.8 
@danog

danog commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

bugbot review

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

…e-master

master's new CustomTaintCacheTest used 'nosql'/'nosql-json' as sample
*custom* taint names, but this branch promotes 'nosql' to a builtin
TaintKind, so getOrRegisterTaint('nosql') now returns the builtin bit
instead of a freshly-assigned custom bit. Rename the sample custom
taints to 'graphql'/'graphql-json' (deliberately not builtin) so the
test keeps exercising custom-taint registration/caching.

Co-Authored-By: Claude Opus 4.8 
@danog

danog commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

bugbot review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit c1c4b86. Configure here.

@danog
danog merged commit c47e97e into vimeo:master Sep 15, 2026
8 of 10 checks passed
danog added a commit to danog/psalm that referenced this pull request Sep 22, 2026
Add nosql taint type and mark MongoDB sinks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release:feature The PR will be included in 'Features' section of the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant