Publish curated ITWorx_MCP_Hub source from 2aa63ba527a98b38b4d28f2a1bb85b1b1dbe7f6d
This commit is contained in:
1 parent
49627c7a7c
commit
378ae83d30
22 files changed
+4055
-81
No files matched your search
+190
-22
@@ -12,9 +12,8 @@ Specifically fails on:
|
||||
grant);
|
||||
- a matching exception that has expired (expiryDate in the past) or is past
|
||||
its mandatory rescan date;
|
||||
- a matching exception whose recorded affectedImageDigests disagree with
|
||||
--image-digests for any service both documents name (the digest changed --
|
||||
the exception was reviewed against a build that no longer exists);
|
||||
- a matching exception without a valid image binding for each affected
|
||||
service, or whose binding differs from the current image inventory;
|
||||
- an exception missing any required field.
|
||||
|
||||
See docs/09-release/CVE_EXCEPTIONS.md for the full policy and
|
||||
@@ -25,6 +24,7 @@ context. This script has no "--force"/override flag by design.
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
@@ -36,6 +36,8 @@ if str(_ROOT_DIR) not in sys.path:
|
||||
|
||||
from scripts.packlib import load_json # noqa: E402
|
||||
|
||||
_SHA256_IMAGE_ID = re.compile(r"^sha256:[0-9a-f]{64}$")
|
||||
|
||||
REQUIRED_FIELDS = (
|
||||
"id",
|
||||
"cve",
|
||||
@@ -57,24 +59,134 @@ REQUIRED_FIELDS = (
|
||||
)
|
||||
|
||||
|
||||
def _findings(scan: dict[str, Any]) -> list[tuple[str, str, str]]:
|
||||
"""Return (package, cve, severity) for every CRITICAL/HIGH finding."""
|
||||
def _findings(scan: dict[str, Any]) -> list[tuple[dict[str, Any], str]]:
|
||||
"""Return (scan row, CVE) pairs for every CRITICAL/HIGH finding."""
|
||||
findings = []
|
||||
for row in scan.get("unresolvedCriticalOrHigh", []):
|
||||
for cve in row["cves"]:
|
||||
findings.append((row["package"], cve, row.get("severity", "UNKNOWN")))
|
||||
findings.append((row, cve))
|
||||
return findings
|
||||
|
||||
|
||||
def _current_image_inventory(
|
||||
image_digests: Any,
|
||||
) -> tuple[dict[str, dict[str, str]], list[str]]:
|
||||
"""Validate immutable current image bindings while retaining optional tags."""
|
||||
errors: list[str] = []
|
||||
if not isinstance(image_digests, dict) or not image_digests:
|
||||
return {}, ["IMAGE INVENTORY INVALID: --image-digests must contain a non-empty images map"]
|
||||
|
||||
inventory: dict[str, dict[str, str]] = {}
|
||||
for service, entry in image_digests.items():
|
||||
if not isinstance(service, str) or not service.strip():
|
||||
errors.append("IMAGE INVENTORY INVALID: every image service needs a non-empty name")
|
||||
continue
|
||||
if not isinstance(entry, dict):
|
||||
errors.append(f"IMAGE INVENTORY INVALID: {service} must be an image evidence object")
|
||||
continue
|
||||
|
||||
image_id = entry.get("imageId")
|
||||
if not isinstance(image_id, str) or not _SHA256_IMAGE_ID.fullmatch(image_id):
|
||||
errors.append(f"CURRENT IMAGE ID INVALID: {service} needs a sha256 imageId")
|
||||
continue
|
||||
|
||||
if "digest" in entry:
|
||||
digest = entry.get("digest")
|
||||
if not isinstance(digest, str) or not _SHA256_IMAGE_ID.fullmatch(digest):
|
||||
errors.append(f"CURRENT IMAGE DIGEST INVALID: {service} has a malformed digest")
|
||||
continue
|
||||
binding = digest
|
||||
else:
|
||||
# Older v1 inventory evidence records the immutable local image ID
|
||||
# instead of a registry manifest digest.
|
||||
binding = image_id
|
||||
|
||||
tag = entry.get("tag")
|
||||
if tag is not None and (not isinstance(tag, str) or not tag.strip()):
|
||||
errors.append(f"IMAGE INVENTORY INVALID: {service} has a malformed tag")
|
||||
continue
|
||||
inventory[service] = {"binding": binding, "imageId": image_id}
|
||||
if isinstance(tag, str):
|
||||
inventory[service]["tag"] = tag
|
||||
|
||||
if not inventory:
|
||||
errors.append("IMAGE INVENTORY INVALID: no valid current image bindings were supplied")
|
||||
return inventory, errors
|
||||
|
||||
|
||||
def _affected_services(
|
||||
row: dict[str, Any], inventory: dict[str, dict[str, str]]
|
||||
) -> tuple[list[str], list[str]]:
|
||||
"""Resolve scan image tags to services, or use the legacy full inventory."""
|
||||
if "affectedImages" not in row:
|
||||
# Legacy v1 scan rows had no affectedImages field. Requiring the
|
||||
# exception to cover the whole supplied inventory is the safe fallback.
|
||||
return list(inventory), []
|
||||
|
||||
affected_images = row.get("affectedImages")
|
||||
if (
|
||||
not isinstance(affected_images, list)
|
||||
or not affected_images
|
||||
or any(not isinstance(image, str) or not image.strip() for image in affected_images)
|
||||
or len(set(affected_images)) != len(affected_images)
|
||||
):
|
||||
return [], ["SCAN AFFECTED IMAGES INVALID: expected a non-empty list of unique image tags"]
|
||||
|
||||
services_by_tag: dict[str, list[str]] = {}
|
||||
for service, evidence in inventory.items():
|
||||
tag = evidence.get("tag")
|
||||
if tag:
|
||||
services_by_tag.setdefault(tag, []).append(service)
|
||||
|
||||
services: list[str] = []
|
||||
errors: list[str] = []
|
||||
for image in affected_images:
|
||||
matches = services_by_tag.get(image, [])
|
||||
if not matches:
|
||||
errors.append(
|
||||
f"AFFECTED IMAGE UNBOUND: scan image {image} has no current service binding"
|
||||
)
|
||||
elif len(matches) > 1:
|
||||
errors.append(f"AFFECTED IMAGE AMBIGUOUS: scan image {image} maps to multiple services")
|
||||
else:
|
||||
services.append(matches[0])
|
||||
return services, errors
|
||||
|
||||
|
||||
def check(
|
||||
scan: dict[str, Any],
|
||||
exceptions_doc: dict[str, Any],
|
||||
image_digests: dict[str, str] | None,
|
||||
image_digests: dict[str, Any] | None,
|
||||
today: date,
|
||||
) -> list[str]:
|
||||
errors: list[str] = []
|
||||
by_key: dict[tuple[str, str], dict[str, Any]] = {}
|
||||
image_inventory: dict[str, dict[str, str]] = {}
|
||||
if image_digests is not None:
|
||||
image_inventory, inventory_errors = _current_image_inventory(image_digests)
|
||||
errors.extend(inventory_errors)
|
||||
|
||||
by_key: dict[tuple[str, str, str], dict[str, Any]] = {}
|
||||
raw_by_key: dict[tuple[str, str, str], dict[str, Any]] = {}
|
||||
conflicting_keys: set[tuple[str, str, str]] = set()
|
||||
for exception in exceptions_doc.get("exceptions", []):
|
||||
if not isinstance(exception, dict):
|
||||
errors.append("exception entry must be an object")
|
||||
continue
|
||||
package = exception.get("package")
|
||||
cve = exception.get("cve")
|
||||
version = exception.get("installedVersion")
|
||||
key: tuple[str, str, str] | None = None
|
||||
if isinstance(package, str) and isinstance(cve, str) and isinstance(version, str):
|
||||
if package and cve and version:
|
||||
key = (package, cve, version)
|
||||
if key is not None and key in raw_by_key and raw_by_key[key] != exception:
|
||||
conflicting_keys.add(key)
|
||||
errors.append(
|
||||
f"CONFLICTING DUPLICATE: multiple exception entries disagree for {package}/{cve}"
|
||||
)
|
||||
elif key is not None:
|
||||
raw_by_key.setdefault(key, exception)
|
||||
|
||||
missing = [field for field in REQUIRED_FIELDS if not exception.get(field)]
|
||||
# riskOwner/approver/compensatingControls may be legitimately empty
|
||||
# only while approvalStatus is "pending" -- once "approved" they are
|
||||
@@ -87,12 +199,44 @@ def check(
|
||||
f"exception {exception.get('id', '<no id>')} is missing required fields: {missing}"
|
||||
)
|
||||
continue
|
||||
key = (exception["package"], exception["cve"])
|
||||
by_key[key] = exception
|
||||
if key is not None:
|
||||
by_key.setdefault(key, exception)
|
||||
|
||||
for package, cve, severity in _findings(scan):
|
||||
key = (package, cve)
|
||||
exception = by_key.get(key)
|
||||
for key in conflicting_keys:
|
||||
by_key.pop(key, None)
|
||||
|
||||
by_pair: dict[tuple[str, str], list[dict[str, Any]]] = {}
|
||||
for (package, cve, _version), exception in by_key.items():
|
||||
by_pair.setdefault((package, cve), []).append(exception)
|
||||
|
||||
for row, cve in _findings(scan):
|
||||
package = row["package"]
|
||||
severity = row.get("severity", "UNKNOWN")
|
||||
reviewed = by_pair.get((package, cve), [])
|
||||
if "installedVersion" in row:
|
||||
scanned_version = row.get("installedVersion")
|
||||
if not isinstance(scanned_version, str) or not scanned_version:
|
||||
errors.append(
|
||||
f"SCAN VERSION MISSING: {package}/{cve} has no valid installedVersion"
|
||||
)
|
||||
continue
|
||||
exception = by_key.get((package, cve, scanned_version))
|
||||
if exception is None and reviewed:
|
||||
errors.append(
|
||||
f"VERSION MISMATCH: {package}/{cve} scan found {scanned_version}, "
|
||||
"but no exception reviewed that exact version"
|
||||
)
|
||||
continue
|
||||
else:
|
||||
# Existing v1 evidence may omit the installed version. It remains
|
||||
# usable only when one unambiguous reviewed version exists.
|
||||
if len(reviewed) > 1:
|
||||
errors.append(
|
||||
f"AMBIGUOUS VERSION: {package}/{cve} legacy scan omits installedVersion "
|
||||
"and multiple versions were reviewed"
|
||||
)
|
||||
continue
|
||||
exception = reviewed[0] if reviewed else None
|
||||
if exception is None:
|
||||
errors.append(
|
||||
f"UNAPPROVED: {severity} finding {package}/{cve} has no exception recorded at all"
|
||||
@@ -128,11 +272,38 @@ def check(
|
||||
f"{rescan_due.isoformat()} has passed"
|
||||
)
|
||||
|
||||
if image_digests:
|
||||
if image_digests is not None:
|
||||
legacy_full_inventory = "affectedImages" not in row
|
||||
services, scope_errors = _affected_services(row, image_inventory)
|
||||
errors.extend(scope_errors)
|
||||
recorded = exception.get("affectedImageDigests", {})
|
||||
for service, current_digest in image_digests.items():
|
||||
if not isinstance(recorded, dict):
|
||||
errors.append(
|
||||
f"IMAGE BINDING INVALID: {package}/{cve} affectedImageDigests must be an object"
|
||||
)
|
||||
continue
|
||||
if legacy_full_inventory and set(recorded) != set(image_inventory):
|
||||
errors.append(
|
||||
f"IMAGE INVENTORY SCOPE CHANGED: {package}/{cve} uses a legacy scan "
|
||||
"without affectedImages and its reviewed service set does not exactly "
|
||||
"match the current full inventory"
|
||||
)
|
||||
for service in services:
|
||||
current_digest = image_inventory[service]["binding"]
|
||||
recorded_digest = recorded.get(service)
|
||||
if recorded_digest and recorded_digest != current_digest:
|
||||
if recorded_digest is None or recorded_digest == "":
|
||||
errors.append(
|
||||
f"IMAGE BINDING MISSING: {package}/{cve} has no valid reviewed digest "
|
||||
f"for {service}"
|
||||
)
|
||||
elif not isinstance(recorded_digest, str) or not _SHA256_IMAGE_ID.fullmatch(
|
||||
recorded_digest
|
||||
):
|
||||
errors.append(
|
||||
f"IMAGE BINDING INVALID: {package}/{cve} has a malformed reviewed digest "
|
||||
f"for {service}"
|
||||
)
|
||||
elif recorded_digest != current_digest:
|
||||
errors.append(
|
||||
f"DIGEST CHANGED: {package}/{cve} exception recorded "
|
||||
f"{service}={recorded_digest}, but the current build is "
|
||||
@@ -151,19 +322,16 @@ def main() -> int:
|
||||
"--image-digests",
|
||||
type=Path,
|
||||
help="evidence/<packet>/image-digests.json; when given, exceptions whose "
|
||||
"recorded digest for a named service disagrees with the current build fail.",
|
||||
"current image IDs or reviewed bindings are missing, malformed, or changed fail.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
scan = load_json(args.scan_summary)
|
||||
exceptions_doc = load_json(args.exceptions)
|
||||
image_digests: dict[str, str] | None = None
|
||||
image_digests: dict[str, Any] | None = None
|
||||
if args.image_digests:
|
||||
digest_doc = load_json(args.image_digests)
|
||||
image_digests = {
|
||||
service: (entry.get("digest") or entry.get("imageId"))
|
||||
for service, entry in digest_doc.get("images", {}).items()
|
||||
}
|
||||
image_digests = digest_doc.get("images", {})
|
||||
|
||||
errors = check(scan, exceptions_doc, image_digests, date.today())
|
||||
if errors:
|
||||
|
||||
Reference in new issue
Block a user