Skip to content

Xeol parser derives severity from the wall clock, so the same report parses differently over time #15651

Description

@devGregA

Summary

XeolParser derives a finding's severity from the wall clock at parse time, so parsing the same, unchanged report on two different days can produce findings with different severities. Parser output should be a pure function of its input; here it is a function of the input and the calendar.

Where

dojo/tools/xeol/parser.py, lines 66-83:

# Determine severity based on EOL date
severity = "Info"
eol_str = cycle.get("Eol", "")
try:
    eol_date = datetime.strptime(eol_str, "%Y-%m-%d")
    now = datetime.now()
    if eol_date < now:
        delta = now - eol_date
        if delta <= timedelta(weeks=2):
            severity = "Low"
        elif delta <= timedelta(weeks=4):
            severity = "Medium"
        elif delta <= timedelta(weeks=6):
            severity = "High"
        else:
            severity = "Critical"
except Exception:
    severity = "Info"

now is the only non-input in the expression, and it moves.

Impact

A component that reached end of life walks through four severities over the six weeks that follow, with no change whatsoever to the scan report:

Time since the component's EOL date Severity
not yet EOL Info
0 to 2 weeks Low
2 to 4 weeks Medium
4 to 6 weeks High
beyond 6 weeks Critical (terminal)

Two consequences:

  1. Severity churn on reimport. Re-importing an unchanged Xeol report re-grades existing findings. A user watching a dashboard sees EOL findings silently escalate with no upstream event behind the change, and any severity-based SLA or report shifts with it.
  2. Duplicate findings for anyone who hashes severity. severity is a legal entry in HASHCODE_FIELDS_PER_SCANNER and is used by a number of scan types today (Anchore, Aqua, Burp and others). Xeol is not in that dict, so it currently falls back to the legacy field set (title, cwe, line, file_path, description) and its hash_code is unaffected. But a user who customizes the hash fields for Xeol to include severity, which is a supported configuration, gets a hash that changes on its own schedule: reimport stops matching the stored findings, closes them as absent, and creates duplicates. Rehashing cannot repair that, because the stored rows do not contain the values the parser would write today.

Reproduction

unittests/scans/xeol/xeol_one_finding.json carries "Eol": "2026-07-02" for Perl. That sample crossed the six-week boundary at 2026-08-13T00:00 local time of whatever runs the parser:

parse on 2026-08-12 -> severity High
parse on 2026-08-14 -> severity Critical

Nothing about the file changed.

Note this is also a latent trap in the test corpus: any assertion on that file's severity is time-dependent. unittests/tools/test_xeol_parser.py happens not to assert severity for xeol_one_finding.json (it only asserts it for xeol_multiple_findings.json, whose EOL dates are in 2019 and therefore permanently Critical), which is why CI has not surfaced this.

Prior art

This is the same class of defect as the Govulncheck parser building its description from ", ".join(set(...)), where Python's per-process string hash randomization made the description, and therefore the hash_code, differ on every import of the same file. That was fixed by sorting the sets in dojo/tools/govulncheck/parser.py. The principle applied there is the one at stake here: a parser's output must be reproducible from its input alone.

Possible fixes

  1. Grade against a timestamp carried in the report rather than datetime.now(), so the result is fixed per file. The xeol JSON descriptor block does not appear to carry a scan timestamp today, so this may need an upstream tool change.
  2. Drop the time banding and assign a single severity to any past-EOL component (with Info retained for one not yet EOL). Deterministic, and simple to reason about. This does change severity for existing findings on the next import, so it is worth calling out in release notes.
  3. Keep the banding but compute it from data in the report, for example the EOL date relative to the artifact's release or latest-release date, both of which are already parsed into the description.

Option 2 is the smallest change that makes the parser deterministic. Happy to open a PR for whichever direction maintainers prefer, since this is a product decision about how EOL findings should be graded rather than a purely mechanical fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions