fix(cpg): one method with no sources must not cost the whole batch its answers

`reachableByFlows` on an empty source list is not a no-op. Joern answers it by
logging

    [INFO ] Attempting to determine flows from empty list of sources.

to STDOUT -- while the script is running, so the line lands between the fence
markers, ahead of the payload. `json.loads` then fails on the whole document
and the driver reports `query_failed` for every request in the batch. One
method with nothing to trace from silently cost the answers for all 205 of
the others.

Fixed at both ends, because either alone leaves the seam brittle:

  * taint.sc skips the flow query when there is no source to trace from, which
    is also the correct thing to do with the question.
  * `extract_payload` starts a decode at each line that opens a document and
    keeps the first that parses. Trimming to the outermost brackets is the
    obvious approach and the wrong one -- `[INFO ]` brings its own bracket.

Found while testing whether `$this` belongs in parameter sources. It does not
belong there on the argument -- the receiver is the object, not request data --
and excluding it cuts Paid Memberships Pro's order class from 16 entailed
findings to 6, all sourced from a named parameter, and makes the 2.9.7/2.9.8
pair separate cleanly.

It also loses CVE-2023-6559: MW WP Form's `_delete_files()` takes no
parameters at all, and the attacker-controlled path arrives as
`$this->attachments`, assigned by another method. So the trade is a VERIFIED
CVE against findings merely suspected of being false, and it is refused. The
receiver stays, with the measurement written next to it. The real defect is
field-sensitivity -- a field should be tainted where it is assigned -- and
deleting the receiver removes the symptom by removing the evidence.

841 tests pass; gates byte-identical.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
This commit is contained in:
Marius Ciepluch
2026-09-09 18:30:41 +02:00
co-authored by Claude Opus 5
parent e0442d146c
commit 45cf43e625
3 changed files with 66 additions and 5 deletions
+12
View File
@@ -368,3 +368,15 @@ def test_a_build_with_nothing_dropped_reports_no_unparsed_files() -> None:
completed = subprocess.CompletedProcess(args=[], returncode=0, stdout="Successfully wrote graph", stderr="")
assert _unparsed_files(completed) == ()
assert _unparsed_files(None) == ()
def test_a_joern_log_line_inside_the_fence_does_not_destroy_the_payload() -> None:
"""Joern logs to stdout while the script runs, so a log line can land ahead of the payload.
`[INFO ] Attempting to determine flows from empty list of sources.` did, and because the batch is one
JSON document the driver lost all 205 answers in it and reported `query_failed` for every one.
"""
from openultrasast.cpg.backend import BEGIN, END, extract_payload
noisy = f'{BEGIN}\n[INFO ] Attempting to determine flows from empty list of sources.\n{{"0": [], "1": [1]}}\n{END}\n'
assert extract_payload(noisy) == {"0": [], "1": [1]}