Skip to content

feat: report structured attacker input reaching NoSQL sinks - #160

Merged
SizzleUnrlsd merged 6 commits into
mainfrom
feat/nosql-injection
Sep 25, 2026
Merged

SizzleUnrlsd merged 6 commits into
mainfrom
feat/nosql-injection

Conversation

@SizzleUnrlsd

Copy link
Copy Markdown
Contributor

Summary

nosql-injection reports attacker input that may hold a structure reaching a NOSQL sink. The typical case is a MongoDB filter, where a JSON body giving {"$ne": null} matches any value. A string cannot hold an operator, so only input that may hold a structure carries NOSQL. That is the decision recorded in #158.

Closes #158.

Stacked on #156 and #159. Merge them first; this PR then shows only its own two commits.

What carries NOSQL

Input NOSQL
JSON (request.json, get_json()), raw bodies (data, get_data(), stream, bottle's body), files, tornado's request object and WebSocket messages, sys.stdin, HTTP responses, process output yes
Flask args, form, values, cookies, headers and paths; bottle's equivalents; tornado get_*argument(s), get_cookie, path_args, path_kwargs; the URL parameters of Flask, bottle and tornado routes; argv, click and Typer parameters, the environment, input() no: TEXT_KINDS
An entry-point parameter annotated with scalars: str, int, float, bool, bytes, UUID, dates, Decimal, Literal[...], or containers of them; also through Optional, Union, | None and Annotated no: FastAPI validates it
Django and aiohttp request objects yes, as a whole (a limit)

TEXT_KINDS is ALL without NOSQL. It is exported for model plugins.

One origin per flow

Problem. The parameters of one entry point can now carry different kinds: a scalar one has no NOSQL. They therefore got distinct Source objects for the same origin, and report emitted one flow per Source. The regression snapshots caught it: healthchecks' ping_body(request, code: UUID, n: int) gave two identical xss findings at line 814.

Fix. report now emits one flow per origin (symbol and label). As before, the flow's kinds come from the taint. test_parameters_of_one_entry_point_remain_one_origin covers the case, and the snapshots are unchanged.

Changed expectation

test_source_kinds_reflect_who_controls_them: os.environ, sys.argv and input() are text, so they no longer carry NOSQL. Their COMMAND and PATH restrictions are checked as before, and subprocess.check_output is unchanged.

Limits

They are documented in docs/usage.md.

  • Django and aiohttp taint the request object as a whole, so request.GET["name"] still counts. A test locks this behaviour.
  • The keys of a dict are not followed, so a string concatenated into $where is not reported.
  • JSON decoded from a text field (json.loads(request.args["filter"])) is not reported, and neither is a whole query mapping used as a filter.

The engine ships no NOSQL sink. The pymongo models (CoreTrace-Entreprise/coretrace-python-analyzer-plugins#3) will declare them, using #157 for the database and collection names.

Tests

tests/test_nosql_injection.py covers:

  • structured and text Flask input;
  • 13 FastAPI annotations;
  • standard input, argv and the environment;
  • Django, including the documented limit;
  • one origin per flow.

The count of bundled plugins goes from 36 to 37.

Local results:

  • 1174 tests passed;
  • 19 regression snapshots passed, unchanged;
  • ruff check and mypy report nothing.

A module-level name denotes what its value denotes in a function body: an alias, an attribute, an item or a call of an import or of an earlier module-level name, resolved in statement order.
…odel

Symbol resolution reads the Members models the engine provides as the semantic.members input, so SymbolAnalysis now depends on MembersAnalysis besides scopes and imports.
Text sources (form fields, headers, URL parameters, argv, the environment, input()) no longer carry NOSQL, so the operator source kinds are now expressed with TEXT_KINDS.
Only input that may hold a structure carries NOSQL: text sources carry TEXT_KINDS, and an entry-point parameter annotated with scalars loses NOSQL. A flow names each origin once, whatever kinds the parameters of one entry point carry.
@SizzleUnrlsd SizzleUnrlsd added the enhancement New feature or request label Sep 25, 2026
@SizzleUnrlsd SizzleUnrlsd self-assigned this Sep 25, 2026
@SizzleUnrlsd
SizzleUnrlsd merged commit 93f5c8f into main Sep 25, 2026
10 checks passed
@SizzleUnrlsd
SizzleUnrlsd deleted the feat/nosql-injection branch September 25, 2026 23:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Detection: a nosql-injection rule for structured attacker input

1 participant