diff --git a/README.md b/README.md index 0bdb35a..c9bb541 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,8 @@ report_generator from-folder --artifacts ./sample [--path ] whose metadata is gone. The inputs are one SARIF file (`merged.sarif`, with one run per tool) and the vulnerability report -(`vdr.json`); either is enough on its own. `metadata.json` is optional, and without it the scan and -project pages are left out. The report has these parts: +(`vdr.json`); either is enough on its own. `metadata.json` is optional, and without it the project +page is left out. The report has these parts: 1. the cover and a table of contents, 2. a summary and the most critical issues across all sources, @@ -98,9 +98,8 @@ vectors by `persistent_storage.utils.cvss_utils`. ### What it cannot fill in A field the artifacts do not answer is written as ``, never guessed or quietly dropped. Today -that is the manufacturer (unless given with `--manufacturer`), the analysed lines of code, the language -breakdown, and the tools and profile of each pipeline stage — the metadata records that a stage ran -and how long it took, not what it ran. +that is the manufacturer (unless given with `--manufacturer`), the analysed lines of code and the language +breakdown. A measured zero survives: `0 filtered` is a reading, and a blank is a gap. diff --git a/report_generator/context.py b/report_generator/context.py index 040e12d..2fc90f2 100644 --- a/report_generator/context.py +++ b/report_generator/context.py @@ -79,9 +79,8 @@ def build_context( "sections": [_tool_section(name, report) for name, report in tools], } - # Without the pipeline's metadata these two pages would be nothing but gaps. + # Without the pipeline's metadata this page would be nothing but gaps. if metadata: - context["scan"] = _scan(sources, metadata) context["project"] = _project(metadata, project) critical = _critical(tools, sca) @@ -142,7 +141,7 @@ def _tool_name(run: dict[str, Any]) -> str: def _contents(context: dict[str, Any]) -> list[dict[str, str]]: """The table of contents: every top-level part of the report that is actually in it.""" - parts = [context[key] for key in ("scan", "project", "summary", "critical") if key in context] + parts = [context[key] for key in ("project", "summary", "critical") if key in context] return [{"id": part["id"], "title": part["title"]} for part in [*parts, *context["sections"]]] @@ -224,46 +223,6 @@ def _severity_ledger(tools: list[tuple[str, SarifReport]], sca: VdrReport | None return {"title": "True issues by severity", "rows": rows} -def _scan(sources: ReportSources, metadata: dict[str, Any]) -> dict[str, Any]: - """What the pipeline did, stage by stage.""" - stages = [stage for stage in metadata.get("stages") or [] if isinstance(stage, dict)] - failed = [stage["label"] for stage in stages if stage.get("status") not in ("success", None)] - - return { - "id": "scan", - "title": "CipherCell Scan Information", - "overview": [ - _fact("Scan started at", _moment(metadata.get("pipeline_start_iso"))), - _fact("Scan duration", _duration(metadata.get("pipeline_duration_seconds"))), - _fact("Scan completed at", _moment(metadata.get("pipeline_end_iso"))), - _fact( - "Pipeline Steps", ", ".join(_stated(stage.get("label") or stage.get("key")) for stage in stages) or None - ), - _fact("Errors Detected", ", ".join(failed) if failed else "None" if stages else None), - ], - "stages": [_stage(stage, sources) for stage in stages], - } - - -def _stage(stage: dict[str, Any], sources: ReportSources) -> dict[str, Any]: - """One stage of the pipeline. - - The tools a stage ran and the profile it ran them under are not recorded in the metadata, so - they are left unstated. What is recorded is when it ran, how it ended, and -- indirectly -- what - it produced, which is read off the names of the files the scan stored. - """ - return { - "title": stage.get("label") or stage.get("key") or UNKNOWN, - "rows": [ - _fact("Selected Tools", None), - _fact("Profile", None), - _fact("Duration", _duration(stage.get("duration_seconds"))), - _fact("Created Artifacts", ", ".join(sorted(sources.files)) or None, kind="mono"), - _fact("Errors Detected", "None" if stage.get("status") == "success" else stage.get("status")), - ], - } - - def _version(metadata: dict[str, Any]) -> str | None: """What version of the project was scanned: the stated one, else the commit the SBOM was uploaded for. @@ -772,24 +731,3 @@ def _percent(share: float | None) -> str | None: return None return f"{share * 100:.2f} %" if share < 0.01 else f"{share * 100:.1f} %" - - -def _moment(stated: Any) -> str | None: # noqa: ANN401 - """An ISO timestamp as the report writes it, or nothing where it cannot be read.""" - if not isinstance(stated, str): - return None - - try: - return datetime.fromisoformat(stated).strftime("%A, %B %d, %Y %H:%M:%S (%Z)").replace("()", "").strip() - except ValueError: - return None - - -def _duration(seconds: Any) -> str | None: # noqa: ANN401 - """A number of seconds as ``HH:MM:SS``.""" - if not isinstance(seconds, (int, float)) or isinstance(seconds, bool): - return None - - whole = int(seconds) - - return f"{whole // 3600:02d}:{whole % 3600 // 60:02d}:{whole % 60:02d}" diff --git a/report_generator/layout.py b/report_generator/layout.py index b8ed974..944370f 100644 --- a/report_generator/layout.py +++ b/report_generator/layout.py @@ -32,7 +32,6 @@ AUTHOR = "CipherCell" # shorter report rather than one full of empty tables. CONTEXT = { "cover": "scan_id, footer_date, logo, title, identity[], severity_ledger{title, rows[]}", - "scan": "title, overview[], stages[{title, rows[], table?}]", "project": "title, overview[], languages{title, rows[]}", "summary": "title, overview[], distributions[{title, rows[], legend}]", "critical": "title, table{columns[], rows[]}", diff --git a/report_generator/sample.py b/report_generator/sample.py index 35ea060..71fd21b 100644 --- a/report_generator/sample.py +++ b/report_generator/sample.py @@ -97,108 +97,6 @@ def _cover() -> dict[str, Any]: } -def _scan() -> dict[str, Any]: - """What the pipeline did, stage by stage, and what each stage was configured with.""" - return { - "title": "CipherCell Scan Information", - "overview": [ - _fact("Scan started at", "Wednesday, September 16, 2026 05:43:14 (UTC)"), - _fact("Scan duration", "07:30:38"), - _fact("Scan completed at", "Wednesday, September 16, 2026 13:13:52 (UTC)"), - _fact( - "Pipeline Steps", - "Preanalysis, SAST, SBOM Dependency Tracking, SBOM Reachability, AI FP Detection, AI Ranking", - ), - _fact("Errors Detected", "None (2 warnings, see SBOM Reachability)"), - ], - "stages": [ - { - "title": "Pre-Analysis Settings", - "rows": [ - _fact("Selected Tools", "Propylon v0.9.4"), - _fact("Profile", "deterministic"), - _fact("Errors Detected", "None"), - ], - }, - { - "title": "SAST Settings", - "rows": [ - _fact("Selected Tools", "ruff v0.14.4, bandit v1.8.6, cppcheck v2.18.0, semgrep v1.145.0"), - _fact("Language Coverage", "C (cppcheck, semgrep), Python (ruff, bandit, semgrep)"), - _fact("Rule Profile", "security — ruleset ciphercell-sec-2026.03, sha256 8f2b41c6…d97a"), - _fact("Deduplication Method", "Deterministic"), - _fact("Created Artifacts", "sast_combined.sarif", kind="mono"), - _fact("Errors Detected", "None"), - ], - }, - { - "title": "SBOM Dependency Tracking Settings", - "rows": [ - _fact("Selected Tools", "syft v1.33.0, cdxgen v11.10.0, deptrace v0.7.2"), - _fact("Profile", "Standard SBOM"), - _fact("SBOM Format", "CycloneDX 1.6"), - _fact("Advisory Sources", "NVD, OSV.dev, GitHub Advisory Database, Sonatype OSS Index"), - _fact("Created Artifacts", "sbom.cdx.json, sbom.vdr.json", kind="mono"), - _fact("Errors Detected", "None"), - ], - }, - { - "title": "SBOM Reachability", - "rows": [ - _fact("Selected Tools", "CodeQL v2.24.1, Joern v4.0.312"), - _fact("Profile", "Default Taint Analysis"), - _fact("Created Artifacts", "sbom_annotated.vdr.json", kind="mono"), - _fact("Errors Detected", "2 warnings"), - ], - "table": { - "columns": ["ID", "Stage", "Warning"], - "rows": [ - { - "cells": [ - "W-01", - "Joern, JavaScript", - "2 of 148 entry points could not be resolved (dynamic require() in " - "server/plugins/loader.js:41). The 6 findings depending on these entry points " - "were escalated and are reported conservatively as true issues.", - ] - }, - { - "cells": [ - "W-02", - "CodeQL, C", - "Database build skipped for vendored sources under third_party/; excluded by " - "scan scope, no reachability verdict is claimed for those paths.", - ] - }, - ], - }, - }, - { - "title": "AI FP Detection", - "rows": [ - _fact("Selected Tools", "Polygraph v1.6.2"), - _fact("Profile", "static and escalation agent"), - _fact("AI Model", "Qwen3-32B, digest sha256:4ab1…c70f"), - _fact("Inference", "On-premise, CipherCell inference cluster"), - _fact("Reasoning Effort", "Default (classification), Default (escalation)"), - _fact("Response Cache", "Enabled"), - _fact("Context Enrichment", "Static (50 lines before, 50 lines after)"), - _fact("Errors Detected", "None"), - ], - }, - { - "title": "Ranking", - "rows": [ - _fact("Selected Tools", "Prism v0.4.1"), - _fact("Effort", "Standard"), - _fact("Context Size", "Minimal"), - _fact("Errors Detected", "None"), - ], - }, - ], - } - - def _project() -> dict[str, Any]: """What was scanned, and what it is written in.""" return { @@ -517,7 +415,6 @@ def sample_context() -> dict[str, Any]: """A complete context, covering every section the template can draw.""" return { "cover": _cover(), - "scan": _scan(), "project": _project(), "summary": _summary(), "critical": _critical(), diff --git a/report_generator/template/report.html.j2 b/report_generator/template/report.html.j2 index 90e8ecd..f6d3b74 100644 --- a/report_generator/template/report.html.j2 +++ b/report_generator/template/report.html.j2 @@ -130,17 +130,6 @@ {% endif %} -{% if scan %} -

{{ scan.title }}

- {{ facts(scan.overview, wide=true) }} - - {% for stage in scan.stages %} -

{{ stage.title }}

- {{ facts(stage.rows, wide=true) }} - {% if stage.table %}{{ grid(stage.table) }}{% endif %} - {% endfor %} -{% endif %} - {% if project %}

{{ project.title }}

{{ facts(project.overview, wide=true) }}