"""Wiz adapter — translate Wiz API results to ACDL PolicyCheckResult records. Wiz is a SaaS security platform with a REST API (issues, security graph queries). This adapter translates Wiz issue records to the normalized PolicyCheckResult schema (engine: "wiz"), matching the Checkov adapter pattern. D-052: stub + schema path. The adapter degrades gracefully when Wiz is not configured — it emits a single SKIPPED record (WIZ_NOT_CONFIGURED) so the confidence policy input stays non-empty. The pipeline invokes it optionally when WIZ_API_TOKEN is set. CLI: wiz_adapter.py """ import datetime import json import os import sys SEVERITY_MAP = { "CRITICAL": "critical", "HIGH": "high", "MEDIUM": "medium", "LOW": "low", "INFO": "info", } RESULT_MAP = { "OPEN": "fail", "RESOLVED": "pass", "IN_PROGRESS": "skipped", "DISMISSED": "skipped", } def _iso8601_now(): return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") def _to_pcr(wiz_issue, contract_id): severity_raw = wiz_issue.get("severity", "INFO") severity = SEVERITY_MAP.get(str(severity_raw).upper(), "info") status = wiz_issue.get("status", "OPEN") result = RESULT_MAP.get(str(status).upper(), "error") control = wiz_issue.get("control", {}) return { "contractId": contract_id, "evaluatedAt": _iso8601_now(), "engine": "wiz", "ruleId": wiz_issue.get("id", control.get("id", "WIZ_UNKNOWN")), "severity": severity, "result": result, "message": wiz_issue.get("title", control.get("name", "")), "evidence": { "resource": wiz_issue.get("entity", {}).get("id"), "resource_name": wiz_issue.get("entity", {}).get("name"), "cloud_platform": wiz_issue.get("entity", {}).get("cloudPlatform"), "subscription_id": wiz_issue.get("entity", {}).get("subscriptionId"), }, "resourceRef": wiz_issue.get("entity", {}).get("id", ""), } def _emit_not_configured(contract_id): return { "contractId": contract_id, "evaluatedAt": _iso8601_now(), "engine": "wiz", "ruleId": "WIZ_NOT_CONFIGURED", "severity": "info", "result": "skipped", "message": "Wiz adapter not configured (WIZ_API_TOKEN not set); degraded gracefully (D-052).", "evidence": {}, "resourceRef": "", } def adapt(wiz_json_path, contract_id): with open(wiz_json_path, "r", encoding="utf-8") as fh: data = json.load(fh) out = [] # Accept either a bare list of issues or an object with an "issues" key. if isinstance(data, list): issues = data else: issues = data.get("issues", []) if not isinstance(issues, list): issues = [] for issue in issues: out.append(_to_pcr(issue, contract_id)) if not out: out.append(_emit_not_configured(contract_id)) return out def is_configured(): return bool(os.environ.get("WIZ_API_TOKEN")) if __name__ == "__main__": if len(sys.argv) != 3: print("usage: wiz_adapter.py ", file=sys.stderr) sys.exit(2) print(json.dumps(adapt(sys.argv[1], sys.argv[2]), indent=2))