#!/usr/bin/env python3 """Render audit evidence (JSON) as the text report printed by the collectors. Usage: python3 tools/render_report.py audit-macos-host-20261007.json python3 tools/render_report.py synthetic/out/evidence.jsonl --index 3 Useful for synthetic records (which have no printed report) and for checking that collector output and evidence stay in sync. Standard library only. """ import argparse import json from datetime import datetime LINE = "=" * 60 SECTIONS = [("baseline", None), ("gdpr", "GDPR TECHNICAL CONTROLS"), ("aiact", "EU AI ACT CONTROLS"), ("org", "ORGANISATIONAL CONTROLS")] LAYOUT = { "windows": {"title": "WINDOWS ENDPOINT SECURITY BASELINE - AUDIT", "results": "COMPLIANCE RESULTS", "end": "END OF READ-ONLY AUDIT", "blank_before_sections": True, "fields": [("Hostname", "hostname"), ("Operating System", "os_name"), ("OS Version", "os_version"), ("Build", "build"), ("Serial Number", "serial_number"), ("Execution User", None), ("Audit Date", None), ("Audit Mode", None)]}, "linux": {"title": "LINUX ENDPOINT SECURITY BASELINE - AUDIT", "results": "LINUX ENDPOINT SECURITY BASELINE - RESULTS", "end": "END OF LINUX ENDPOINT SECURITY BASELINE AUDIT", "blank_before_sections": False, "fields": [("Hostname", "hostname"), ("Operating System", "os_name"), ("OS Version", "os_version"), ("Kernel", "kernel"), ("Serial Number", "serial_number"), ("Execution User", None), ("Audit Date", None), ("Audit Mode", None)]}, "macos": {"title": "macOS ENDPOINT SECURITY BASELINE - AUDIT", "results": "COMPLIANCE RESULTS", "end": "END OF AUDIT", "blank_before_sections": False, "fields": [("Hostname", "hostname"), ("Operating System", "os_name"), ("OS Version", "os_version"), ("Build", "build"), ("Serial Number", "serial_number"), ("Execution User", None), ("Audit Date", None), ("Audit Mode", None)]}, } def audit_date(record, os_family): started = datetime.fromisoformat(record["audit"]["started_at"]) text = started.strftime("%Y-%m-%d %H:%M:%S") # Linux/macOS collectors print the time zone abbreviation, which the ISO offset cannot recover exactly. return text if os_family == "windows" else f"{text} {record.get('_tz', started.strftime('%z'))}" def render(record): os_family = record["host"]["os_family"] layout = LAYOUT[os_family] out = [LINE, f" {layout['title']}", LINE] for label, key in layout["fields"]: if key: value = record["host"].get(key, "") elif label == "Execution User": value = record["audit"]["execution_user"] elif label == "Audit Date": value = audit_date(record, os_family) else: value = record["audit"]["mode"] out.append(f"{label:<16}: {value}") out.append(LINE) for section, title in SECTIONS: if layout["blank_before_sections"]: out.append("") out += [LINE, f" {title or layout['results']}", LINE] for result in record["results"]: if result["section"] == section: out += [result["name"], result["status"]] out += [LINE, f" {layout['end']}", LINE] return "\n".join(out) def main(): parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument("path") parser.add_argument("--index", type=int, default=0, help="record number in a .jsonl file (0-based)") args = parser.parse_args() with open(args.path, encoding="utf-8-sig") as handle: if args.path.endswith(".jsonl"): record = json.loads(handle.readlines()[args.index]) else: record = json.load(handle) print(render(record)) if __name__ == "__main__": main()