Repository navigation
fix: locate structured-file findings at their own line, or by pointer - #222
Merged
Merged
Conversation
A component apart from the rules finds the line and column of a value from its path and checks that the text there holds that very value. JSON is read with the position of every value, so repeated keys, nested arrays and escaped strings are told apart. In TOML, the key and its value are checked in the section the path names, an element of an array of tables included. What it cannot place has no position, never a guessed line. Refs #207, #210
…inter Values of JSON and TOML configuration files were all located at their key's first occurrence, so findings of one key shared a line that was often not theirs. The configuration scan now places each value with the position component. A value whose line cannot be established gets a FileLocation, the file and the JSON pointer of the value, never a guessed line: reports show it without a line (JSON null and pointer, SARIF without region, text path only), an inline suppression does not apply to it, and the baseline recognises it by its pointer. Baselines are written in schema 2, whose entries carry the pointer; schema 1 files are still read and match exactly as they did. An old entry that cannot identify a finding with certainty no longer accounts for it, and the check then asks on standard error for the baseline to be recorded again. Closes #210
A package pinned in uv.lock or poetry.lock was located at the first line mentioning its name, often another package's dependency list, and the message named it as its own requirer. It is now located at the name of its own [[package]] entry, which the position component checks, or given a file location when the entry cannot be placed; the message states the pinned version, the lock file and the packages that require it. Closes #207
Review of the structured positions: a file location was fingerprinted by its pointer alone, so a new secret at the same place was baselined; it now carries a digest of its value, which the baseline compares, never the value itself. Multi-line TOML strings were tracked by counting triple quotes, so a delimiter in a comment, a one-line string or an escape put a value on a line inside a string; lines are now read by a small lexer that skips strings and comments and follows arrays and inline tables opened by a value, so an array element alone on its line is no header either. TOML lines are counted by line feeds, as the rest of the engine counts them, and each section's keys are indexed once. Refs #207, #210
…heir quotes Review of the structured positions: the baseline read a finding's line with splitlines, which also breaks on U+2028 and U+0085, while the parser and the position component count line feeds, so a separator above a secret recorded another line's text and could absorb a new secret. It now counts line feeds. A multi-line TOML string closed by four or five quotes left the lexer inverted, and a key written inside a later string could be located; the quotes before a closing delimiter now belong to the string. A FileLocation now requires its pointer and digest, which file_location builds from a string value. Refs #207, #210
… requirements Review of the baseline line fix: line numbers had no single rule. The baseline now counted line feeds while the .env, YAML, INI and requirements readers used splitlines and the parser also breaks on a lone carriage return, so a fingerprint or an inline suppression could refer to another line and a rotated secret could be absorbed. lines_of, in the source layer, counts lines as Python does (\n, \r\n, a lone \r; never U+2028 or a form feed), and every producer and consumer of a line number now uses it: the baseline, inline suppressions, the configuration and requirements readers, and the JSON and TOML positions. pyproject.toml requirements were placed at the first line containing their text, or line 1 by default; the position component now finds the element of an array a key holds, and a requirement it cannot place gets a file location. Refs #207, #210
This was referenced Oct 7, 2026
…array once Review of the array elements: an element written over several lines (a key in a multi-line string) was placed at the line it starts on, which does not hold it, so a changed value kept that line's text and was baselined. An element is now delimited by the end of its own value, and one that spans lines has no position: it gets a file location, whose digest of the value the baseline compares. Each array's elements were found again for every value, scanning the rest of the file each time; they are now read once per array over a single copy of the text (4,000 elements in 0.04 s instead of 40 s). Refs #207, #210
Review of the array elements: a bare scalar ended at its first space, so a datetime written with a space (1979-05-27 07:32:00) counted as two elements and shifted every later index. A bare scalar now ends at a separator or the end of its line, its trailing spaces trimmed. Closing a multi-line string counts its quotes in place instead of copying the rest of the text. Refs #207, #210
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #207, closes #210 (its line positions; the dependency maps were fixed in #216).
Findings in JSON, TOML and lock files were located by searching the text for the first line mentioning their key or name: the 33 HAR values of mitmproxy all pointed at the first
"headers"of their file, and werkzeug'svulnerable-dependencypointed at flask's dependency list (uv.lock:620). The message also named werkzeug as its own requirer.Design
coretrace_python.source.positions). The detectors still judge the valuesjsonandtomllibdecode; the component finds where a value is written from its structural path and checks that the text there holds that very value.jsondecodes them), so repeated keys in different objects, nested arrays and escaped strings are told apart; a key repeated in one object keeps its last value, asjsondecodes it.[table], or the element of an array of tables that its[[header]]opens, with its[sub.tables]; keys and values are read withtomllib. An element is never taken by its rank alone: its key must hold the value.FileLocation(coretrace_python.source): the file, the JSON pointer (RFC 6901) of a value whose line is not established, and a digest of the value (sha256:and 16 hex digits), never the value itself.SourceSpankeeps its invariant (line ≥ 1);Finding.spanisSourceSpan | FileLocation, andconfig_literalskeeps its(value, name, location, function)shape.line/column/end_*arenullwith apointerfield; SARIF has noregion, the pointer under the location'sproperties; text showspath: …without a line.uv.lock,poetry.lock) is located at thenameof its own[[package]]entry, checked by the component, or given a file location (/package/<i>/name).Requirementgainslocked, the version as the lock file writes it, and the message states the evidence:CVE-2026-27199: werkzeug 3.1.5, pinned in uv.lock, is in <3.1.6 (required by flask): …. Declared requirements keep their wording.Validation
tests/test_structured_positions.py(19): repeated keys, nested arrays, escaped strings, duplicate keys, mismatched values; TOML sections, arrays of tables with key and value checked, sub-tables of an element, quoted keys, values without a position; triple quotes in a comment, a one-line string or an escape, and quotes before a closing delimiter; an array element alone on its line; line feeds only (U+2028, CRLF).tests/test_file_locations.py(8): two values of one key at their own lines; a file location; JSON, SARIF and text reports; inline suppression only on verified lines; baseline by pointer in schema 2; a changed value at a file location is a new finding, and the baseline does not contain the value; a separator such as U+2028 above a secret does not make the baseline record another line.tests/test_baseline_schema_1.py(3): a new secret is never accounted for by an old entry (the scenario reproduced in review: it was absorbed before the fix); a repeated key is recognised only where its old entry was certain; Python findings match as before; the notice on standard error.tests/test_line_numbering.py(12): one rule of line numbering (lines_of); a rotated secret below a U+2028, U+0085, form feed or lone\rin.env, YAML, INI and Python files is new; a suppression on the next line does not apply; requirements files andpyproject.tomlrequirements (PEP 621 array elements, Poetry keys, a file location when unplaceable).tests/test_lock_positions.py(6):uv.lockandpoetry.lockpositions and message, a file location for an inline lock entry, the wording of a declared requirement.tests/test_baseline.pychanged, with prior approval: the fingerprint's fifth field (empty pointer) in two expectations, and the written schema (1 → 2). They still check the same behaviours (line text not number, fallback without the file, counts).test_packaging::test_the_version_has_one_source_of_truth, a stale local install).ruff check,ruff format --checkon new files,mypyandgit diff --checkpass.be75825, enterprise plugins atac35460,[email protected], no baseline: the same 75 results; 34 move. They are the 33 HAR values and werkzeug (uv.lock:620 → 1998), and their new positions are exactly the lines verified by hand inreports/mitmproxy-2026-10-02/triage.csv(34 of 34). Scan time unchanged (51 s, against 59 s onmain, on a loaded machine).Review
An independent review found no blocking defect and three to fix, all fixed and covered by tests: a file location was fingerprinted by its pointer alone, so a new secret at the same place was baselined (now a digest of the value is compared); triple quotes in a comment, a one-line string or an escape could put a value on a line inside a multi-line TOML string (now a lexer); TOML lines were counted by
splitlines, which also breaks on U+2028 (now line feeds only). Each section's keys are now indexed once: a flat section of 3,000 keys is read in 0.04 s instead of 13.5 s.A second review of those fixes found two more, fixed and covered by tests: the baseline read a finding's line with
splitlines, so a U+2028 or U+0085 above a secret recorded another line's text and could absorb a new secret (the baseline now counts line feeds, as the parser and the component do); a TOML multi-line string closed by four or five quotes inverted the lexer, so a key inside a later string could be located (the quotes before the closing delimiter now belong to the string).FileLocationnow requires its pointer and digest.A third review found that line numbers had no single rule: the baseline counted line feeds while the
.env, YAML, INI and requirements readers usedsplitlinesand the parser also breaks on a lone\r, so a fingerprint or an inline suppression could refer to another line, and a rotated secret could be absorbed; and thatpyproject.tomlrequirements kept guessed lines (first line containing their text, or line 1).lines_of(incoretrace_python.source) now counts lines as Python does,\n,\r\nor a lone\r, never U+2028 or a form feed, and every producer and consumer of a line number uses it; the position component finds the element of an array a key holds, sopyproject.tomlrequirements are located at their own element (PEP 621) or key (Poetry), or given a file location.A fourth review found that an array element written over several lines was placed at the line it starts on, which does not hold it, so a changed multi-line secret was baselined, and that array elements were found again for every value (40 s for 4,000 elements). An element is now delimited by the end of its own value; one that spans lines gets a file location; each array is read once (0.04 s). Two older defects it found, outside this diff, are tracked in #223 (a secret in a multi-line Python string is recognised by its first line only) and #224 (accepted secrets at a line are recorded in the baseline in plain text).
A fifth review confirmed both fixes (fuzzed against
tomllibon about 12,000 arrays: no returned position belongs to a multi-line element) and found that a datetime written with a space counted as two array elements; a bare scalar now ends at a separator or the end of its line. Closing a multi-line string no longer copies the rest of the text.Not covered
baseline: a secret in a multi-line Python string is recognised by its first line only #223, baseline: accepted secrets at a line are recorded in plain text #224: older baseline defects found in review (see above).
requirements.txtlines are numbered as Python counts them; pip splits them withstr.splitlines, so a form feed or U+2028 between two requirements is read as one malformed requirement here.JsonPositionsgives up on a document nested beyond its recursion limit or usingNaN/Infinity: every value of it then gets a file location (a safe degradation).Whether GitHub code scanning accepts a SARIF result whose location has no
regionis to be checked on an actual upload.YAML, INI,
.envand properties files are read line by line, their positions exact by construction; YAML nested keys are not given structural paths.