TaintedNosql

Emitted when user-controlled input can be passed into a NoSQL query (e.g. a MongoDB filter or command).

Unlike SQL injection, NoSQL injection does not depend on string concatenation. It happens when an attacker controls the structure of a query document — typically by supplying an array/object instead of a scalar. For example, a request like ?username[$ne]= causes $_GET['username'] to be the array ['$ne' => ''], which turns an equality match into a "not equal" match and can bypass authentication.

Because of this, only values that can hold an array (or object) can carry the nosql taint — a scalar can never be a NoSQL query, so casting user input to string, int, float or bool removes the taint.

<?php

function getUser(MongoDB\Driver\Manager $manager): array {
    // $_GET["username"] may be an array such as ["$ne" => null]
    $filter = ["username" => $_GET["username"]];
    $query = new MongoDB\Driver\Query($filter);

    return $manager->executeQuery("db.users", $query)->toArray();
}

Safe alternatives

Cast user input to a scalar so it can only ever be a literal value, never a query operator:

<?php

function getUser(): MongoDB\Driver\Query {
    // (string) forces a literal match; the nosql taint is removed.
    // Casting to (int)/(float)/(bool) works the same way for numeric/boolean fields.
    return new MongoDB\Driver\Query(["username" => (string) $_GET["username"]]);
}

Or route the filter through a sanitizer annotated with @psalm-taint-escape nosql:

<?php

/**
 * Forces every filter value to a scalar so an attacker cannot inject query
 * operators such as ["$ne" => null] through array-valued input.
 *
 * @param array<string, mixed> $filter
 * @return array<string, string>
 * @psalm-taint-escape nosql
 */
function sanitize_mongo_filter(array $filter): array {
    $safe = [];
    foreach ($filter as $field => $value) {
        if (is_array($value)) {
            throw new InvalidArgumentException("Filter values must be scalar");
        }
        $safe[$field] = (string) $value;
    }
    return $safe;
}

function getUser(): MongoDB\Driver\Query {
    $filter = sanitize_mongo_filter(["username" => $_GET["username"]]);
    return new MongoDB\Driver\Query($filter);
}