Skip to content

feat(sarif): describe the rules, give each result its confidence and related locations - #274

Merged
SizzleUnrlsd merged 25 commits into
mainfrom
feat/sarif-rule-descriptions-and-confidence
Oct 22, 2026
Merged

SizzleUnrlsd merged 25 commits into
mainfrom
feat/sarif-rule-descriptions-and-confidence

Conversation

@SizzleUnrlsd

@SizzleUnrlsd SizzleUnrlsd commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Closes #270. Closes #271.

In the SARIF log, each rule's only text was its id, the confidence the model rates every finding with was dropped, and a finding could not point at a second place. GitHub code scanning, an IDE or CoreTrace's merged log could show a finding's message, but not explain its rule, rank it, or link to where the problem happens.

Changes

  • Rule descriptions (SARIF: describe each rule instead of repeating its id #270):
    • Plugins: whoever defines a rule describes it. A plugin declares its rules in Plugin.rules, a mapping from rule id to RuleDescription (a title and what the rule detects). It is optional, so existing plugins load unchanged and their rules are listed by id alone.
    • Engine: the engine describes its own findings in engine.ENGINE_RULES: unsupported-syntax, syntax-error, ambiguous-module and exploitable-vulnerability.
    • Bundled plugins: each declares the rules it reports, and SecretDetector declares the three secret rules.
    • Path to the report: a check gathers these descriptions (engine.rule_descriptions). ProjectAnalysis and FileAnalysis carry them as rules, and the command line passes them to Report.rules.
    • Reporter: the SARIF reporter reads report.rules only. A SARIF rule then carries the title as shortDescription and the description as fullDescription.
  • Confidence (SARIF: write each finding's confidence, and the other places it involves #271): each SARIF result carries its finding's confidence (low, medium, high) as the confidence property, as CoreTrace's merged log spells it.
  • Related locations (SARIF: write each finding's confidence, and the other places it involves #271):
    • RelatedLocation (a span and a message) and Finding.related let a finding name other places. The SARIF reporter writes them as relatedLocations, numbered from 0.
    • The SARIF message links to each one with an embedded link [text](id), with brackets and backslashes escaped around them. The text and JSON reports keep the message as it is.
    • A taint flow that reaches its sink inside a project function points at the sink: sink python.builtins.eval, reached via relay, where relay is the call the flow goes through, at any depth. The through and sink_line metadata are kept.
    • The cache, which the parallel analysis also goes through, keeps the related locations. CACHE_FORMAT goes to 18, so caches miss once.

Compatibility

  • Additive for plugins.
  • The JSON report is unchanged.
  • In SARIF, properties is now present on every result, with the confidence beside injection, context and origin.
  • Three tests compared a result's properties exactly. They now expect the confidence beside the properties they check, and still nothing else, so the guarantee that no other metadata is exported holds.

Tests

Each new test was committed before its implementation and failed for the expected reason:

  • test_rule_descriptions:
    • a third-party plugin that declares its rule sees it described in the SARIF log, and one that declares nothing still loads and reports by id;
    • every rule the usage guide documents is described by the bundled plugins;
    • every rule id the code gives a finding, read from the sources (the engine's notes included), is described. This caught syntax-error and ambiguous-module.
  • test_reporters:
    • the reporter renders the descriptions the report gives;
    • a rule without one keeps its id alone;
    • the confidence is checked for each level;
    • the related locations are rendered, and the message links to them with escaping.
  • test_code_injection: the sink inside a project function is a related location, reached via the first call even two calls away; a sink in the function itself gives none.
  • Cache and parallel: test_cache and test_parallel caught that the cache dropped the related locations, before the codec kept them.

Results:

  • pytest: 2464 passed, 19 regression tests deselected (network).
  • mypy and ruff check . are clean.

Each SARIF rule repeats its id as its only text, so GitHub code scanning, an IDE or CoreTrace's merged log can show a finding's message but cannot explain its rule. Every rule the usage guide documents, and unsupported-syntax, must have a title other than its id and a description; a rule without one, from a third-party plugin, keeps its id alone.

Refs #270
coretrace_python.findings.rules gives a title and a description for each rule the usage guide documents, and for unsupported-syntax, for a report to explain the rule behind a finding. A rule a third-party plugin reports has none.

Refs #270
A SARIF rule now carries its title as shortDescription and what it detects as fullDescription. A rule the analyzer does not describe, from a third-party plugin, keeps its id alone instead of repeating it as a title.

Refs #270
The finding model rates each finding low, medium or high, but the SARIF log drops it. Each result must carry it as the confidence property, as CoreTrace's merged log spells it, beside the injection, context and origin properties.

Refs #271
Each SARIF result now carries its finding's confidence, low, medium or high, as the confidence property, so that a consumer can rank it; CoreTrace's merged log spells it the same way. The tests that compared a result's properties exactly now expect the confidence beside the injection, context and origin they check, and still nothing else.

Refs #271
A finding has a single span. RelatedLocation names another place, a source span and a short message, and Finding.related lists them, empty by default. Nothing fills them yet.

Refs #271
…lves

A finding's related locations, such as the sink a flow reaches inside a project function, must become the result's relatedLocations, each with its span and message, numbered from 0; a finding without any has none.

Refs #271
Each related location of a finding becomes an entry of the result's relatedLocations, numbered from 0, with its file and region and its message, so that GitHub code scanning shows it as a link next to the alert. The region of a span is now built in one place for both.

Refs #271
…st be a related location

A flow reaching eval inside a project function is reported at the call, its sink line only in metadata. The sink must be a related location of the finding, with a message saying the input reaches it there; a sink in the function itself needs none.

Refs #271
The cache, which the parallel analysis also goes through, encoded a finding without its related locations, so a second run or a parallel one would report them without the places they involve. They are now encoded with their span and message, and CACHE_FORMAT goes to 18 so that an older cache, which has none, is rebuilt.

Refs #271
…nction

A flow reaching its sink inside a project function is reported at the call, the sink's line only in metadata. The sink is now a related location of the finding, with the message 'the input reaches <sink> here, in <function>', so that the SARIF log links to it.

Refs #271
@SizzleUnrlsd SizzleUnrlsd added enhancement New feature or request priority: high Review first labels Oct 22, 2026
@SizzleUnrlsd SizzleUnrlsd self-assigned this Oct 22, 2026
relatedLocations is filled but the message never references them, so GitHub code scanning shows no link. In SARIF only, the message must end with an embedded link [text](id) to each related location, with literal brackets and backslashes escaped around them.

Refs #271
…as the sink's function

For run -> relay -> evaluate -> eval, the related location points into evaluate but said 'here, in relay': through names the first call the flow goes through, not where the sink is. It must say the sink is reached via relay.

Refs #271
A result with related locations now ends its message with an embedded link [text](id) to each, which GitHub code scanning renders as a link to the place; brackets and backslashes of the text around the links are escaped, as SARIF requires. A result without related locations, and the text and JSON reports, keep the message as it is.

Refs #271
The related location of a flow through project functions said 'the input reaches <sink> here, in <through>', but through is the first call the flow goes through: two calls away, it named a function the sink is not in. The message is now 'sink <sink>, reached via <through>', which holds at any depth and also reads as a link.

Refs #271
The SARIF reporter only looked up a central table, so a third-party plugin had no way to describe its rules: they came out as their id alone. A plugin declaring the description of its rule must see it in the SARIF log, and a plugin declaring nothing must still load and report by id.

Refs #270
Plugin.rules maps each rule id the plugin reports to its RuleDescription, a title and what it detects, for a report to explain the findings. It is optional and empty by default, so existing plugins load unchanged and their rules are listed by id alone. RuleDescription is exported from coretrace_python.findings.

Refs #270
The SARIF reporter looked up a central table, so only the rules that table knew were described. A check now gathers the descriptions its loaded plugins declare, ProjectAnalysis and FileAnalysis carry them as rules, engine.report and the command line pass them to the Report, and the SARIF reporter reads report.rules only. The central table still seeds the engine's descriptions until the bundled plugins declare their own. The check that every documented rule is described now runs through a check with the bundled plugins, and the reporter's own test gives the report its descriptions.

Refs #270
…rts it

The descriptions lived in one central table. Each bundled plugin now declares those of the rules it reports in its rules attribute, SecretDetector those of the three secret rules, and the engine its own two (unsupported-syntax, exploitable-vulnerability) in ENGINE_RULES; the central table is gone, and coretrace_python.findings.rules keeps RuleDescription only. The reports are unchanged.

Refs #270
…ne's notes included

The documented rules were described, but the engine's own notes, syntax-error and ambiguous-module, came out as their id alone. Every rule id the code gives a finding, read from the sources (a rule_id class attribute or argument, the first argument of Finding or of the engine's _note), must be described by the bundled plugins or the engine.

Refs #270
syntax-error, a file that could not be read or parsed, and ambiguous-module, a file whose import name another file shares, are notes the engine reports itself; ENGINE_RULES now describes them, beside unsupported-syntax and exploitable-vulnerability, so that no rule the code reports comes out as its id alone.

Refs #270
@SizzleUnrlsd
SizzleUnrlsd merged commit f7afd78 into main Oct 22, 2026
10 checks passed
@SizzleUnrlsd
SizzleUnrlsd deleted the feat/sarif-rule-descriptions-and-confidence branch October 22, 2026 15:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request priority: high Review first

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SARIF: write each finding's confidence, and the other places it involves SARIF: describe each rule instead of repeating its id

1 participant