Class: Scryer::ReportRenderer
- Inherits:
-
Object
- Object
- Scryer::ReportRenderer
- Defined in:
- lib/scryer/report_renderer.rb
Overview
Turns a Scanner::Result into the exact JSON shape documented in CONTRACT2.md (also the ingest POST body shape) and a self-contained HTML report (inline CSS, no external assets except optionally linking out — here, none at all, so it works offline too). The HTML report is laid out similarly to a Brakeman report: an overview, a summary of counts, the full list of checks that ran, a breakdown of warnings by type, and then every finding in detail.
Constant Summary collapse
- SEVERITY_ORDER =
%w[critical warning info].freeze
- SEVERITY_LABELS =
{ "critical" => "Critical", "warning" => "Warning", "info" => "Info" }.freeze
- CSV_HEADERS =
%w[kind identifier severity location message suggested_fix code_snippet url].freeze
- SARIF_LEVEL_BY_SEVERITY =
{ "critical" => "error", "warning" => "warning", "info" => "note" }.freeze
Instance Method Summary collapse
-
#as_csv ⇒ Object
Flat, one-row-per-finding CSV — security + performance findings plus any dependency findings, in that order — for dropping into a spreadsheet or importing into a ticketing tool.
- #as_hash ⇒ Object
- #as_html ⇒ Object
- #as_json ⇒ Object
-
#as_sarif ⇒ Object
SARIF 2.1.0 (docs.oasis-open.org/sarif/sarif/v2.1.0) — the format GitHub Code Scanning (and other CI security dashboards) natively ingest, turning findings into inline PR annotations and Security-tab entries instead of a report file nobody opens.
-
#initialize(result:, project_name:, release_label: nil, git_commit_sha: nil, git_branch: nil, dependency_findings: [], scanned_at: Time.now) ⇒ ReportRenderer
constructor
dependency_findingsis an optional array of Scryer::DependencyAudit:: Finding (insecure_sources + vulnerable_gems) — pass it to fold a bundler-audit-like dependency audit into the same report as the static scan, instead of the audit living in separate--audit-depsoutput.
Constructor Details
#initialize(result:, project_name:, release_label: nil, git_commit_sha: nil, git_branch: nil, dependency_findings: [], scanned_at: Time.now) ⇒ ReportRenderer
dependency_findings is an optional array of Scryer::DependencyAudit::
Finding (insecure_sources + vulnerable_gems) — pass it to fold a
bundler-audit-like dependency audit into the same report as the static
scan, instead of the audit living in separate --audit-deps output.
Defaults to empty so existing callers that only run the static scan are
unaffected.
23 24 25 26 27 28 29 30 31 32 |
# File 'lib/scryer/report_renderer.rb', line 23 def initialize(result:, project_name:, release_label: nil, git_commit_sha: nil, git_branch: nil, dependency_findings: [], scanned_at: Time.now) @result = result @project_name = project_name @release_label = release_label @git_commit_sha = git_commit_sha @git_branch = git_branch @dependency_findings = dependency_findings @scanned_at = scanned_at end |
Instance Method Details
#as_csv ⇒ Object
Flat, one-row-per-finding CSV — security + performance findings plus
any dependency findings, in that order — for dropping into a
spreadsheet or importing into a ticketing tool. Deliberately excludes
duplicate-code groups: they're nested member lists, not a single
actionable item, so they don't fit a flat "one row = one thing to
fix" table (see as_json for the full nested data). No csv stdlib
dependency — RFC4180-style quoting is small enough to hand-roll, same
reasoning as this gem's other hand-rolled parsers/writers.
65 66 67 68 69 70 71 72 73 74 |
# File 'lib/scryer/report_renderer.rb', line 65 def as_csv h = as_hash rows = [CSV_HEADERS] h["security_findings"].each { |f| rows << static_csv_row(f) } h["performance_findings"].each { |f| rows << static_csv_row(f) } h["style_findings"].each { |f| rows << static_csv_row(f) } h["dependency_findings"].each { |f| rows << dependency_csv_row(f) } rows.map { |row| row.map { |field| csv_field(field) }.join(",") }.join("\n") end |
#as_hash ⇒ Object
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/scryer/report_renderer.rb', line 34 def as_hash { "project_name" => @project_name, "scryer_version" => Scryer::VERSION, "ruby_version" => RUBY_VERSION, "scanned_at" => @scanned_at.utc.iso8601, "release_label" => @release_label, "git_commit_sha" => @git_commit_sha, "git_branch" => @git_branch, "files_scanned" => @result.files_scanned, "parse_errors" => @result.parse_errors.map { |pe| { "file" => pe[:file], "error" => pe[:error] } }, "security_findings" => @result.security_findings.map(&:to_h), "performance_findings" => @result.performance_findings.map(&:to_h), "style_findings" => @result.style_findings.map(&:to_h), "duplicate_groups" => @result.duplicate_groups.map { |g| duplicate_group_hash(g) }, "dependency_findings" => @dependency_findings.map(&:to_h) } end |
#as_html ⇒ Object
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/scryer/report_renderer.rb', line 88 def as_html h = as_hash security = h["security_findings"] performance = h["performance_findings"] style = h["style_findings"] all_findings = security + performance + style by_severity = all_findings.group_by { |f| f["severity"] } duplicate_groups = h["duplicate_groups"] dependency_findings = h["dependency_findings"] <<~HTML <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Scryer report — #{escape(@project_name)}</title> <style>#{CSS}</style> </head> <body> <h1>Scryer report</h1> <p class="meta"> #{escape(@project_name)} · #{escape(h["release_label"] || "no release label")} · #{escape(h["scanned_at"])} · #{h["files_scanned"]} files scanned #{h["parse_errors"].any? ? "· <span class=\"crit\">#{h["parse_errors"].size} parse error(s)</span>" : ""} </p> #{render_toc(h)} <section id="overview"> <h2>Overview</h2> #{render_overview_table(h)} </section> <section id="summary"> <h2>Summary</h2> #{render_summary_table(security, performance, style, duplicate_groups, dependency_findings)} </section> <section id="checks-performed"> <h2>Checks performed</h2> #{render_checks_performed} </section> <section id="warnings-by-type"> <h2>Warnings by type</h2> #{render_warnings_by_type(all_findings)} </section> <section id="findings"> <h2>Findings (#{all_findings.size}) #{("#findings")}</h2> #{render_severity_section("critical", by_severity["critical"] || [])} #{render_severity_section("warning", by_severity["warning"] || [])} #{render_severity_section("info", by_severity["info"] || [])} </section> <section id="duplicates"> <h2>Duplicate code groups (#{duplicate_groups.size}) #{("#duplicates")}</h2> #{render_duplicate_groups(duplicate_groups)} </section> <section id="dependency-audit"> <h2>Dependency audit (#{dependency_findings.size}) #{("#dependency-audit")}</h2> #{render_dependency_findings(dependency_findings)} </section> <section id="errors"> <h2>Files that couldn't be parsed (#{h["parse_errors"].size})</h2> #{render_parse_errors(h["parse_errors"])} </section> <p class="footer"> Generated by Scryer v#{h["scryer_version"]} (Ruby #{escape(h["ruby_version"])}) · heuristic static analysis, not full data-flow/taint analysis — review every finding in its surrounding context before acting on it. </p> <script>#{JS}</script> </body> </html> HTML end |
#as_json ⇒ Object
53 54 55 |
# File 'lib/scryer/report_renderer.rb', line 53 def as_json JSON.pretty_generate(as_hash) end |
#as_sarif ⇒ Object
SARIF 2.1.0 (docs.oasis-open.org/sarif/sarif/v2.1.0) — the format GitHub Code Scanning (and other CI security dashboards) natively ingest, turning findings into inline PR annotations and Security-tab entries instead of a report file nobody opens. Pure data mapping of what's already in as_hash — no new detection logic, and every finding behaves identically to how it does in the other formats.
84 85 86 |
# File 'lib/scryer/report_renderer.rb', line 84 def as_sarif JSON.pretty_generate(sarif_hash) end |