Skip to content

feat: match curated entry points by suffix for untypeable receivers - #200

Merged
SizzleUnrlsd merged 7 commits into
mainfrom
feat/advisory-entry-suffixes
Sep 30, 2026
Merged

SizzleUnrlsd merged 7 commits into
mainfrom
feat/advisory-entry-suffixes

Conversation

@SizzleUnrlsd

Copy link
Copy Markdown
Contributor

Closes #197. Stacked on #199 (feat/argument-present-conditions); merge that one first.

A curated entry point names its symbol canonically (python.django.db.models.QuerySet.annotate), but a project reaches it through a manager on its own model, Item.objects.annotate(...), which the engine derives as the project symbol python.app.models.Item.objects.annotate — the entry point never matched, and the five Django ORM advisories the keyword_name condition of #193 was built for could not be curated against real projects. The bundled Django models meet the same shape for sinks and solve it with SuffixSink("objects.raw").

  • AdvisoryEntryPoint.suffixes: each suffix (at least two dot-separated names, so a bare annotate is rejected) matches a derived symbol on a dot boundary — verbatim the SuffixSink rule — and only symbols of the project's own modules, enforced in one place (SuffixIndex.matches); a queryset chain is declared explicitly (objects.filter.annotate). Another package's objects.annotate never matches.
  • affected_entries resolves exact then suffix matches for the three finding paths: the reachable-vulnerability detector (calls, reads, template filters), the vulnerable-dependency exclusions (so a contradicted condition through a suffix feeds ruled_out and VEX not_affected), and correlate(). The finding keeps the advisory's symbol as entry_point and the derived one as symbol.
  • advisory_suffix_sinks gives the taint engine a SuffixSink(suffix, ADVISORY) per curated suffix; in the workers, _Batch.project carries the project's modules so the index is identical serial and parallel (checked by hand under jobs=2).
  • ModelTable.sink now merges an exact sink with every matching suffix sink instead of returning the first: the shadowing found during Taint the keys of a **mapping expansion as the keyword names of the call #193's design (an advisory-only sink on a symbol ending objects.extra silently dropped Django's SQL kind) is fixed, with the SQL positions kept.
  • No cache-key change: the advisory repr already carries the suffixes, and the project-prefix test can only flip for a module when the prefix module enters or leaves its transitive import closure, which module_keys already keys on.

Documented residual risk: a project symbol genuinely ending with a declared suffix (a project module named objects) matches; the vulnerable range and the conditions still gate it.

Tests: tests/test_entry_point_suffixes.py (18 cases, failing before the change: the issue's reproduction with keyword_name composing, the declared chain, the undeclared chain and a non-project receiver as negatives, the ruled-out and pending paths, the JSON round trip with six invalid suffixes, the ModelTable/SuffixIndex unit behaviour); on the stacked branch: full suite 1613 passed, 2 skipped; regression corpus 19 passed, snapshots unchanged; ruff check . and mypy clean.

Add Condition.present: the argument being given at all, whatever its value, is the condition, exclusive with values and default. A call giving it, positionally, by keyword or through a folded ** literal, meets it even when the value is unknown; a call surely without it is ruled out (reason absent); *args or an unreadable **mapping leaves it pending review. Closes #195.
Cover issue #197 before the implementation: the reproduction (Item.objects.annotate through a manager the engine cannot type), a declared queryset chain, an undeclared chain and a non-project receiver that must not match, ruled-out and pending conditions through the suffix, the advisories.json round trip with validation, and the ModelTable and SuffixIndex unit behaviour.
An advisory entry point may declare suffixes: a derived symbol of the project's own modules ending with a dot plus the suffix reaches the entry point, as a SuffixSink matches a sink. Suffixes load, dump and validate in advisories.json (at least two dot-separated names); SuffixIndex and affected_entries resolve exact and suffix matches for correlate(), the reachable-vulnerability detector and the vulnerable-dependency exclusions; advisory_suffix_sinks feeds the taint engine, and findings keep the advisory's symbol as entry_point and the derived one as symbol. ModelTable.sink now merges an exact sink with every matching suffix sink instead of returning the first, so an advisory sink on a derived symbol composes with objects.extra's SQL kind. No cache-key change: the advisory repr already carries the suffixes, and the project-prefix test can only flip for a module when the prefix module enters or leaves its transitive import closure, which module_keys already keys on. Closes #197.
…try-suffixes

# Conflicts:
#	CHANGELOG.md
#	docs/plugins.md
@SizzleUnrlsd SizzleUnrlsd added the enhancement New feature or request label Sep 30, 2026
@SizzleUnrlsd SizzleUnrlsd self-assigned this Sep 30, 2026
@SizzleUnrlsd
SizzleUnrlsd merged commit c9906e1 into main Sep 30, 2026
10 checks passed
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.

Match curated entry points by suffix for receivers the engine cannot type

1 participant