Skip to content

feat: relate two calls on one receiver for sequence advisory conditions - #201

Merged
SizzleUnrlsd merged 11 commits into
mainfrom
feat/sequence-conditions
Sep 30, 2026
Merged

SizzleUnrlsd merged 11 commits into
mainfrom
feat/sequence-conditions

Conversation

@SizzleUnrlsd

Copy link
Copy Markdown
Contributor

Closes #194. Stacked on #200 (itself on #199); merge #199 then #200 first.

Some advisories describe a flaw that only exists across two calls on one object: requests CVE-2024-35195 (Session.get(url, verify=False) then Session.get(other) reusing the unverified connection) and tornado CVE-2026-91992 (two fetches on one CurlAsyncHTTPClient reusing per-request credentials) — both held out of the curated data because the engine reasoned per call.

  • A sequence condition on a curated entry point names the method a prior call on the same receiver must have made (exact canonical name or .-suffix, the SuffixSink rule), with an optional argument constraint read exactly as an argument condition (argument, position, values, default).
  • The call graph records the receiver's other calls. For a receiver whose whole lifetime is visible — bound once to the result of a call or a with (through Await), denoting a symbol, read only to call external methods, at most 12 calls — each CallSite carries the complete set of the other external calls on it (PriorCall: symbol, location, arguments, and whether it dominates the site). Anything else — a receiver returned, passed to another function, mutated through an attribute (s.verify = False), merged by a Phi, a parameter, a module-level instance — stays unfollowed (None). The cache carries the field; its format becomes 13.
  • Decision: met by a dominating satisfied prior, with the prior's line in conditions_met; contradicted only when the followed receiver shows no call that could satisfy the constraint — the entry call's own arguments included, since across the iterations of a loop a call is its own prior (for u in urls: s.get(u, verify=False) reuses the pool it poisoned at iteration 1: pending, never not_affected); pending everywhere else — unfollowed receiver, undecided or non-dominating matching call (a textually later disabling call may run first in a loop), a flow reaching the sink through a summary. ruled_out reads Session.get(no prior Session.get on the receiver) and feeds VEX not_affected for the single-call shape.
  • Deliberately deferred, documented: cross-function receivers through summaries, multi-method conditions (post also poisons the pool a get condition names), the attribute form of the requests CVE.

Tests: tests/test_sequence_conditions.py (29 cases, RED in two stages: the missing PriorCall import, then 17 behaviour failures): the reproduction, single-call and different-receiver shapes, reversed order, parameter/branch/escape/**kwargs pendings, the loop self-prior soundness case, the curl method-only shape, advisory round trip with four rejections, cache codec round trip plus a second run served from cache with identical findings, and the check_conditions unit matrix. On the stacked branch (with #195, #197): full suite 1642 passed, 2 skipped; regression corpus 19 passed, snapshots unchanged; ruff check . and mypy clean; tests/test_scale.py and tests/test_parallel.py sane (0.1 s / 2.1 s).

Once released: curate the two advisories under the condition in the Enterprise data and lift the requests exclusion.

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
A sequence condition names the method a prior call on the same receiver must have made, with an optional argument constraint read as an argument condition. The call graph records, per call site, the other external calls on a receiver whose whole lifetime is visible (bound once to a call or a with, read only to call external methods, capped), each with whether it dominates the site; the cache carries them and its format becomes 13. The condition is met by a dominating satisfied prior, contradicted only when the followed receiver shows no call that could satisfy it, the entry call's own arguments included since a loop makes it its own prior, and pending everywhere else: an unfollowed receiver, an undecided or non-dominating prior, a flow through a summary. Closes #194.
…ions

# Conflicts:
#	CHANGELOG.md
#	docs/plugins.md
#	src/coretrace_python/bundled/dependency/vulnerable_dependency/vulnerable_dependency.py
#	src/coretrace_python/dependency/advisories.py
#	src/coretrace_python/dependency/correlation.py
#	src/coretrace_python/dependency/graph.py
#	src/coretrace_python/engine.py
@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 bccf254 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.

Relate two calls on one receiver for advisories whose flaw spans a call sequence

1 participant