support/scripts/cve-check: fix vulnerability timestamp to RFC 3339

Normalize vulnerability timestamps to RFC 3339 format with explicit UTC
timezone suffix for CycloneDX 1.6 compliance.
This fixes validation errors in sbom-utility and makes the generated
SBOM with vulnerabilities compatible with DependencyTrack VEX parsers.

The NVD JSON data feeds provide timestamps in ISO 8601 format without timezone
information (e.g., "1999-01-01T05:00:00.000"), but CycloneDX 1.6 requires
RFC 3339 format with explicit timezone designation (e.g.,
"1999-01-01T05:00:00.000Z").

Add nvd_datetime_to_rfc3339() helper function to convert timestamps before
serialization.

Validation results:

Before fix:
  $ sbom-utility validate -i cve/cve_report_current.json
  [INFO] BOM valid against JSON schema: 'false'
  [INFO] (234) schema errors detected.

  Error example:
  {
    "type": "format",
    "field": "vulnerabilities.0.updated",
    "context": "(root).vulnerabilities.0.updated",
    "description": "Does not match format 'date-time'",
    "value": "2025-04-03T01:03:51.193"
  }

After fix:
  $ sbom-utility validate -i cve/cve_report_update.json
  [INFO] BOM valid against JSON schema: 'true'

Tested-with: sbom-utility v0.18.1
Co-authored-by: Fabien Lehoussel <fabien.lehoussel@smile.fr>
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
This commit is contained in:
Thomas Perale
2026-05-29 17:06:27 +02:00
committed by Thomas Petazzoni
parent 33c26bb898
commit e46783d3a0

View File

@@ -12,10 +12,10 @@
from collections import defaultdict
from pathlib import Path
from typing import TypedDict
from datetime import datetime, timezone
import argparse
import sys
import json
import cve as cvecheck
@@ -35,6 +35,24 @@ locally.
brpath = Path(__file__).parent.parent.parent
def datetime_to_rfc3339(dt_string):
"""Normalize datetime string to RFC 3339 format with Z suffix.
NVD dates are already in ISO format, just need to add the Z suffix.
Input: "1999-01-01T05:00:00.000"
Output: "1999-01-01T05:00:00.000Z"
"""
dt = datetime.fromisoformat(dt_string.replace('Z', '+00:00'))
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
else:
dt = dt.astimezone(timezone.utc)
return dt.isoformat().replace('+00:00', 'Z')
def cve_api_get_lang_from_list(values, lang="en") -> (str | None):
for x in values:
if x.get("lang") == lang:
@@ -134,10 +152,10 @@ def nvd_cve_to_cdx_vulnerability(nvd_cve):
"url": f"https://nvd.nist.gov/vuln/detail/{nvd_cve['id']}"
},
**({
"published": nvd_cve["published"],
"published": datetime_to_rfc3339(nvd_cve["published"]),
} if "published" in nvd_cve else {}),
**({
"updated": nvd_cve["lastModified"],
"updated": datetime_to_rfc3339(nvd_cve["lastModified"]),
} if "lastModified" in nvd_cve else {}),
**({
"cwes": nvd_cve_weaknesses_to_cdx(nvd_cve["weaknesses"]),