Updates to new format #1
No files matched your search
@@ -39,8 +39,8 @@ report_generator from-folder --artifacts ./sample [--path <file-or-directory>]
|
||||
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 `<xxx>`, 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.
|
||||
|
||||
|
||||
@@ -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}"
|
||||
@@ -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[]}",
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -130,17 +130,6 @@
|
||||
</ol>
|
||||
{% endif %}
|
||||
|
||||
{% if scan %}
|
||||
<h1 id="{{ scan.id }}" style="break-before: page">{{ scan.title }}</h1>
|
||||
{{ facts(scan.overview, wide=true) }}
|
||||
|
||||
{% for stage in scan.stages %}
|
||||
<h3>{{ stage.title }}</h3>
|
||||
{{ facts(stage.rows, wide=true) }}
|
||||
{% if stage.table %}{{ grid(stage.table) }}{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if project %}
|
||||
<h1 id="{{ project.id }}" style="break-before: page">{{ project.title }}</h1>
|
||||
{{ facts(project.overview, wide=true) }}
|
||||
|
||||
Reference in new issue
Block a user