fix(eol): resolve stale MS-lifecycle findings the rescan no longer matches

run_eol_for_packages only ever ADDED findings; _supersede_old_eol fires
only when a replacement is created. So after the Edge→Azure-Stack-Edge
name-match fix, the wrong 'Microsoft Edge = EOL' finding stayed open
forever (no replacement is produced for a now-unmatched product).

Add a reconcile scoped to the EOL-MS-LIFECYCLE- prefix (package path
only; OS-level endoflife.date findings use other slugs and are
untouched): resolve open MS-lifecycle findings the current run didn't
re-produce. Guarded on a non-empty package list so a failed inventory
read can't mass-close.
This commit is contained in:
2026-07-24 10:10:36 +02:00
parent 6f9f2ae10b
commit c3cd62c461
+44
View File
@@ -776,6 +776,7 @@ def run_eol_for_packages(db: "Session", asset, packages: list) -> int:
"""
count = 0
seen: set = set()
kept_msl: set = set() # MS-lifecycle EOL cve_ids still valid this run
for pkg in packages or []:
name = (pkg.get("name") or "").strip()
version = (pkg.get("version") or "").strip()
@@ -804,6 +805,8 @@ def run_eol_for_packages(db: "Session", asset, packages: list) -> int:
pass
if not actionable:
continue
if status and status.product_slug == "ms-lifecycle":
kept_msl.add(_pseudo_cve_id("ms-lifecycle", status.release_name or "unknown"))
try:
upsert_eol_vulnerability(
db, asset_id=asset.id, product_name=name,
@@ -812,4 +815,45 @@ def run_eol_for_packages(db: "Session", asset, packages: list) -> int:
count += 1
except Exception as e:
logger.warning("EOL-for-packages upsert failed (%s on asset %s): %s", name, asset.id, e)
# Guard on a non-empty inventory: an empty list is a transient/failed read,
# not proof the products are gone (same safety as the other reconciles).
if packages:
_resolve_stale_ms_lifecycle(db, asset.id, kept_msl)
return count
def _resolve_stale_ms_lifecycle(db: "Session", asset_id: int, kept: set) -> None:
"""Resolve open MS-lifecycle EOL findings the current run no longer produced.
Without this, fixing a bad name→product match (tester: 'Microsoft Edge'
browser mis-mapped to the 'Azure Stack Edge' listing) left the wrong finding
open forever, since _supersede_old_eol only fires when a REPLACEMENT is
created. Scoped to the EOL-MS-LIFECYCLE- prefix, which only the package path
produces — OS-level endoflife.date findings use other slugs and are
untouched. Only reconciles when at least one package was inventoried (empty
package list = nothing to conclude)."""
from app.models.vulnerability import Vulnerability, VulnerabilityStatus
stale = (
db.query(Vulnerability)
.filter(Vulnerability.asset_id == asset_id,
Vulnerability.status == VulnerabilityStatus.open,
Vulnerability.cve_id.like("EOL-MS-LIFECYCLE-%"))
.all()
)
for v in stale:
if v.cve_id in kept:
continue
old_status = v.status
v.status = VulnerabilityStatus.patched
v.patched_at = datetime.now()
try:
from app.routers.vulnerabilities import log_vulnerability_change
log_vulnerability_change(
db, None, v.id, old_status, v.status,
reason="MS lifecycle no longer matches this installed product "
"(re-evaluated — not end-of-life)",
cve_id=v.cve_id, source="eol_reconcile",
)
except Exception as e:
logger.warning("audit log for MS-lifecycle reconcile failed (vuln_id=%s): %s", v.id, e)