@sikkuumiii/defuse 0.0.0-stage → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/LICENSE +235 -0
  2. package/README.md +561 -2
  3. package/dist/src/cli.d.ts +20 -0
  4. package/dist/src/cli.js +619 -0
  5. package/dist/src/cli.js.map +1 -0
  6. package/dist/src/core/analyze.d.ts +178 -0
  7. package/dist/src/core/analyze.js +834 -0
  8. package/dist/src/core/analyze.js.map +1 -0
  9. package/dist/src/core/coverage.d.ts +87 -0
  10. package/dist/src/core/coverage.js +181 -0
  11. package/dist/src/core/coverage.js.map +1 -0
  12. package/dist/src/core/finding.d.ts +196 -0
  13. package/dist/src/core/finding.js +178 -0
  14. package/dist/src/core/finding.js.map +1 -0
  15. package/dist/src/core/licence.d.ts +50 -0
  16. package/dist/src/core/licence.js +56 -0
  17. package/dist/src/core/licence.js.map +1 -0
  18. package/dist/src/core/paths.d.ts +21 -0
  19. package/dist/src/core/paths.js +58 -0
  20. package/dist/src/core/paths.js.map +1 -0
  21. package/dist/src/core/score.d.ts +137 -0
  22. package/dist/src/core/score.js +232 -0
  23. package/dist/src/core/score.js.map +1 -0
  24. package/dist/src/dast/targets.d.ts +30 -0
  25. package/dist/src/dast/targets.js +79 -0
  26. package/dist/src/dast/targets.js.map +1 -0
  27. package/dist/src/dast/zap.d.ts +42 -0
  28. package/dist/src/dast/zap.js +184 -0
  29. package/dist/src/dast/zap.js.map +1 -0
  30. package/dist/src/engine/scan.d.ts +80 -0
  31. package/dist/src/engine/scan.js +129 -0
  32. package/dist/src/engine/scan.js.map +1 -0
  33. package/dist/src/engine/shapes.d.ts +47 -0
  34. package/dist/src/engine/shapes.js +328 -0
  35. package/dist/src/engine/shapes.js.map +1 -0
  36. package/dist/src/engine/walk.d.ts +60 -0
  37. package/dist/src/engine/walk.js +156 -0
  38. package/dist/src/engine/walk.js.map +1 -0
  39. package/dist/src/parse/astDump.d.ts +31 -0
  40. package/dist/src/parse/astDump.js +66 -0
  41. package/dist/src/parse/astDump.js.map +1 -0
  42. package/dist/src/parse/languages.d.ts +70 -0
  43. package/dist/src/parse/languages.js +174 -0
  44. package/dist/src/parse/languages.js.map +1 -0
  45. package/dist/src/parse/location.d.ts +17 -0
  46. package/dist/src/parse/location.js +38 -0
  47. package/dist/src/parse/location.js.map +1 -0
  48. package/dist/src/parse/node-grammars.d.ts +12 -0
  49. package/dist/src/parse/node-grammars.js +35 -0
  50. package/dist/src/parse/node-grammars.js.map +1 -0
  51. package/dist/src/parse/parser.d.ts +126 -0
  52. package/dist/src/parse/parser.js +202 -0
  53. package/dist/src/parse/parser.js.map +1 -0
  54. package/dist/src/parse/tree-store.d.ts +139 -0
  55. package/dist/src/parse/tree-store.js +236 -0
  56. package/dist/src/parse/tree-store.js.map +1 -0
  57. package/dist/src/platform/dependency-rule.d.ts +27 -0
  58. package/dist/src/platform/dependency-rule.js +21 -0
  59. package/dist/src/platform/dependency-rule.js.map +1 -0
  60. package/dist/src/platform/deps.d.ts +86 -0
  61. package/dist/src/platform/deps.js +304 -0
  62. package/dist/src/platform/deps.js.map +1 -0
  63. package/dist/src/platform/php-facts.d.ts +49 -0
  64. package/dist/src/platform/php-facts.js +67 -0
  65. package/dist/src/platform/php-facts.js.map +1 -0
  66. package/dist/src/platform/runtime-version.d.ts +78 -0
  67. package/dist/src/platform/runtime-version.js +573 -0
  68. package/dist/src/platform/runtime-version.js.map +1 -0
  69. package/dist/src/report/colors.d.ts +50 -0
  70. package/dist/src/report/colors.js +97 -0
  71. package/dist/src/report/colors.js.map +1 -0
  72. package/dist/src/report/coverage-table.d.ts +90 -0
  73. package/dist/src/report/coverage-table.js +291 -0
  74. package/dist/src/report/coverage-table.js.map +1 -0
  75. package/dist/src/report/html-report.d.ts +66 -0
  76. package/dist/src/report/html-report.js +489 -0
  77. package/dist/src/report/html-report.js.map +1 -0
  78. package/dist/src/report/human.d.ts +44 -0
  79. package/dist/src/report/human.js +575 -0
  80. package/dist/src/report/human.js.map +1 -0
  81. package/dist/src/report/json.d.ts +24 -0
  82. package/dist/src/report/json.js +178 -0
  83. package/dist/src/report/json.js.map +1 -0
  84. package/dist/src/report/mark.d.ts +17 -0
  85. package/dist/src/report/mark.js +39 -0
  86. package/dist/src/report/mark.js.map +1 -0
  87. package/dist/src/report/pricing.d.ts +33 -0
  88. package/dist/src/report/pricing.js +343 -0
  89. package/dist/src/report/pricing.js.map +1 -0
  90. package/dist/src/report/sarif.d.ts +39 -0
  91. package/dist/src/report/sarif.js +219 -0
  92. package/dist/src/report/sarif.js.map +1 -0
  93. package/dist/src/report/site.d.ts +87 -0
  94. package/dist/src/report/site.js +742 -0
  95. package/dist/src/report/site.js.map +1 -0
  96. package/dist/src/rules/code-injection.d.ts +47 -0
  97. package/dist/src/rules/code-injection.js +326 -0
  98. package/dist/src/rules/code-injection.js.map +1 -0
  99. package/dist/src/rules/command-injection.d.ts +30 -0
  100. package/dist/src/rules/command-injection.js +286 -0
  101. package/dist/src/rules/command-injection.js.map +1 -0
  102. package/dist/src/rules/contract.d.ts +198 -0
  103. package/dist/src/rules/contract.js +39 -0
  104. package/dist/src/rules/contract.js.map +1 -0
  105. package/dist/src/rules/format-string.d.ts +46 -0
  106. package/dist/src/rules/format-string.js +146 -0
  107. package/dist/src/rules/format-string.js.map +1 -0
  108. package/dist/src/rules/hardcoded-secret.d.ts +38 -0
  109. package/dist/src/rules/hardcoded-secret.js +440 -0
  110. package/dist/src/rules/hardcoded-secret.js.map +1 -0
  111. package/dist/src/rules/lib/guards.d.ts +168 -0
  112. package/dist/src/rules/lib/guards.js +1179 -0
  113. package/dist/src/rules/lib/guards.js.map +1 -0
  114. package/dist/src/rules/lib/html-safety.d.ts +75 -0
  115. package/dist/src/rules/lib/html-safety.js +755 -0
  116. package/dist/src/rules/lib/html-safety.js.map +1 -0
  117. package/dist/src/rules/lib/local-build.d.ts +59 -0
  118. package/dist/src/rules/lib/local-build.js +320 -0
  119. package/dist/src/rules/lib/local-build.js.map +1 -0
  120. package/dist/src/rules/lib/strings.d.ts +208 -0
  121. package/dist/src/rules/lib/strings.js +1970 -0
  122. package/dist/src/rules/lib/strings.js.map +1 -0
  123. package/dist/src/rules/path-traversal.d.ts +33 -0
  124. package/dist/src/rules/path-traversal.js +96 -0
  125. package/dist/src/rules/path-traversal.js.map +1 -0
  126. package/dist/src/rules/registry.d.ts +19 -0
  127. package/dist/src/rules/registry.js +77 -0
  128. package/dist/src/rules/registry.js.map +1 -0
  129. package/dist/src/rules/sql-injection.d.ts +36 -0
  130. package/dist/src/rules/sql-injection.js +299 -0
  131. package/dist/src/rules/sql-injection.js.map +1 -0
  132. package/dist/src/rules/ssrf.d.ts +39 -0
  133. package/dist/src/rules/ssrf.js +111 -0
  134. package/dist/src/rules/ssrf.js.map +1 -0
  135. package/dist/src/rules/unbounded-copy.d.ts +37 -0
  136. package/dist/src/rules/unbounded-copy.js +147 -0
  137. package/dist/src/rules/unbounded-copy.js.map +1 -0
  138. package/dist/src/rules/unsafe-deserialization.d.ts +43 -0
  139. package/dist/src/rules/unsafe-deserialization.js +276 -0
  140. package/dist/src/rules/unsafe-deserialization.js.map +1 -0
  141. package/dist/src/rules/weak-hash.d.ts +37 -0
  142. package/dist/src/rules/weak-hash.js +197 -0
  143. package/dist/src/rules/weak-hash.js.map +1 -0
  144. package/dist/src/rules/xss.d.ts +38 -0
  145. package/dist/src/rules/xss.js +527 -0
  146. package/dist/src/rules/xss.js.map +1 -0
  147. package/dist/src/taint/content-type.d.ts +19 -0
  148. package/dist/src/taint/content-type.js +20 -0
  149. package/dist/src/taint/content-type.js.map +1 -0
  150. package/dist/src/taint/dictionaries.d.ts +23 -0
  151. package/dist/src/taint/dictionaries.js +1778 -0
  152. package/dist/src/taint/dictionaries.js.map +1 -0
  153. package/dist/src/taint/fold.d.ts +59 -0
  154. package/dist/src/taint/fold.js +1177 -0
  155. package/dist/src/taint/fold.js.map +1 -0
  156. package/dist/src/taint/java-scope.d.ts +61 -0
  157. package/dist/src/taint/java-scope.js +212 -0
  158. package/dist/src/taint/java-scope.js.map +1 -0
  159. package/dist/src/taint/local-map.d.ts +79 -0
  160. package/dist/src/taint/local-map.js +603 -0
  161. package/dist/src/taint/local-map.js.map +1 -0
  162. package/dist/src/taint/project.d.ts +172 -0
  163. package/dist/src/taint/project.js +617 -0
  164. package/dist/src/taint/project.js.map +1 -0
  165. package/dist/src/taint/tracer.d.ts +265 -0
  166. package/dist/src/taint/tracer.js +3817 -0
  167. package/dist/src/taint/tracer.js.map +1 -0
  168. package/dist/src/taint/types.d.ts +484 -0
  169. package/dist/src/taint/types.js +47 -0
  170. package/dist/src/taint/types.js.map +1 -0
  171. package/dist/src/taint/visibility.d.ts +25 -0
  172. package/dist/src/taint/visibility.js +96 -0
  173. package/dist/src/taint/visibility.js.map +1 -0
  174. package/docs/examples/defuse.json +20 -0
  175. package/docs/examples/dvwa-urls.json +14 -0
  176. package/docs/examples/github-code-scanning.yml +54 -0
  177. package/docs/examples/tiny.js +4 -0
  178. package/package.json +73 -4
  179. package/src/cli.ts +747 -0
  180. package/src/core/analyze.ts +1072 -0
  181. package/src/core/coverage.ts +269 -0
  182. package/src/core/finding.ts +311 -0
  183. package/src/core/licence.ts +61 -0
  184. package/src/core/paths.ts +59 -0
  185. package/src/core/score.ts +353 -0
  186. package/src/dast/targets.ts +121 -0
  187. package/src/dast/zap.ts +246 -0
  188. package/src/engine/scan.ts +200 -0
  189. package/src/engine/shapes.ts +348 -0
  190. package/src/engine/walk.ts +196 -0
  191. package/src/parse/astDump.ts +85 -0
  192. package/src/parse/languages.ts +232 -0
  193. package/src/parse/location.ts +44 -0
  194. package/src/parse/node-grammars.ts +41 -0
  195. package/src/parse/parser.ts +262 -0
  196. package/src/parse/tree-store.ts +301 -0
  197. package/src/platform/dependency-rule.ts +33 -0
  198. package/src/platform/deps.ts +374 -0
  199. package/src/platform/php-facts.ts +85 -0
  200. package/src/platform/runtime-version.ts +710 -0
  201. package/src/report/colors.ts +107 -0
  202. package/src/report/coverage-table.ts +359 -0
  203. package/src/report/html-report.ts +601 -0
  204. package/src/report/human.ts +811 -0
  205. package/src/report/json.ts +189 -0
  206. package/src/report/mark.ts +47 -0
  207. package/src/report/pricing.ts +374 -0
  208. package/src/report/sarif.ts +235 -0
  209. package/src/report/site.ts +760 -0
  210. package/src/rules/code-injection.ts +346 -0
  211. package/src/rules/command-injection.ts +296 -0
  212. package/src/rules/contract.ts +220 -0
  213. package/src/rules/format-string.ts +158 -0
  214. package/src/rules/hardcoded-secret.ts +485 -0
  215. package/src/rules/lib/guards.ts +1235 -0
  216. package/src/rules/lib/html-safety.ts +793 -0
  217. package/src/rules/lib/local-build.ts +329 -0
  218. package/src/rules/lib/strings.ts +2034 -0
  219. package/src/rules/path-traversal.ts +101 -0
  220. package/src/rules/registry.ts +90 -0
  221. package/src/rules/sql-injection.ts +311 -0
  222. package/src/rules/ssrf.ts +118 -0
  223. package/src/rules/unbounded-copy.ts +162 -0
  224. package/src/rules/unsafe-deserialization.ts +291 -0
  225. package/src/rules/weak-hash.ts +226 -0
  226. package/src/rules/xss.ts +572 -0
  227. package/src/taint/content-type.ts +20 -0
  228. package/src/taint/dictionaries.ts +1855 -0
  229. package/src/taint/fold.ts +1206 -0
  230. package/src/taint/java-scope.ts +200 -0
  231. package/src/taint/local-map.ts +594 -0
  232. package/src/taint/project.ts +774 -0
  233. package/src/taint/tracer.ts +4298 -0
  234. package/src/taint/types.ts +511 -0
  235. package/src/taint/visibility.ts +100 -0
  236. package/tsconfig.json +61 -0
@@ -0,0 +1,489 @@
1
+ /**
2
+ * THE CLIENT REPORT
3
+ * =================
4
+ *
5
+ * The terminal report is for the person who ran the scan. This one is for the
6
+ * person who did not: a project owner who will read it once, possibly printed,
7
+ * and decide what to do. It is one self-contained HTML file - no script, no
8
+ * stylesheet or font fetched from anywhere, the mark drawn inline - so it can
9
+ * be emailed, opened offline and printed to PDF from any browser.
10
+ *
11
+ * It says the same things the terminal does, in the same order of trust:
12
+ *
13
+ * 1. what was PROVEN, each with the path that proves it;
14
+ * 2. what was only MATCHED, said plainly to be unproven;
15
+ * 3. what was checked and CLEARED, with the reason;
16
+ * 4. what was NOT CHECKED at all.
17
+ *
18
+ * The fourth section is not an appendix to skip. A report that lists problems
19
+ * and stays silent about what it never looked at invites the reader to treat
20
+ * "not listed" as "fine" - the exact failure this tool exists to avoid - and a
21
+ * client report is where that misreading costs the most.
22
+ *
23
+ * Two labels, never blended: a proven finding is a filled dot, a guess a
24
+ * hollow ring, so the difference survives a black-and-white printer. The
25
+ * mark's purple is used on the mark and the name only.
26
+ *
27
+ * PLATFORM-FREE ON PURPOSE: string in, string out, no Node imports, so the
28
+ * browser UI can produce the same file from the same engine.
29
+ *
30
+ * EVERY PIECE OF TEXT FROM THE SCANNED CODE IS ESCAPED. A snippet is whatever
31
+ * the scanned project contains, and a report that rendered it as markup would
32
+ * be an XSS vector aimed at the person reading a security report. The test
33
+ * suite feeds it a hostile snippet to make sure.
34
+ */
35
+ import { LICENCE_SPDX } from '../core/licence.js';
36
+ import { LANGUAGES } from '../parse/languages.js';
37
+ import { ALL_RULES } from '../rules/registry.js';
38
+ import { readableDate, RUNTIME_VERSION_RULE } from '../platform/runtime-version.js';
39
+ import { DEPENDENCY_RULE } from '../platform/dependency-rule.js';
40
+ import { isTestPath } from '../rules/lib/strings.js';
41
+ import { markSvg } from './mark.js';
42
+ const SEVERITIES = ['critical', 'high', 'medium', 'low', 'info'];
43
+ const SEVERITY_LABEL = {
44
+ critical: 'Critical',
45
+ high: 'High',
46
+ medium: 'Medium',
47
+ low: 'Low',
48
+ info: 'Info',
49
+ };
50
+ /** Escape for HTML text and attribute values. Everything from the scan goes through this. */
51
+ export function escapeHtml(text) {
52
+ return text
53
+ .replace(/&/g, '&')
54
+ .replace(/</g, '&lt;')
55
+ .replace(/>/g, '&gt;')
56
+ .replace(/"/g, '&quot;')
57
+ .replace(/'/g, '&#39;');
58
+ }
59
+ const e = escapeHtml;
60
+ /** A receipt starts lower-case so it reads inside a sentence; a table cell starts a sentence. */
61
+ function capitalise(text) {
62
+ return text.charAt(0).toUpperCase() + text.slice(1);
63
+ }
64
+ function plural(n, one, many = `${one}s`) {
65
+ return `${n.toLocaleString('en-US')} ${n === 1 ? one : many}`;
66
+ }
67
+ /** The path as the reader knows it: relative to the scanned folder, forward slashes. */
68
+ function relativeTo(...roots) {
69
+ const norm = (p) => p.replace(/\\/g, '/');
70
+ const bases = roots
71
+ .map((r) => norm(r).replace(/^\.\//, '').replace(/\/+$/, ''))
72
+ .filter((b) => b && b !== '.');
73
+ return (file) => {
74
+ const f = norm(file).replace(/^\.\//, '');
75
+ for (const base of bases)
76
+ if (f.startsWith(`${base}/`))
77
+ return f.slice(base.length + 1);
78
+ return f;
79
+ };
80
+ }
81
+ function lastSegment(path) {
82
+ const parts = path.replace(/\\/g, '/').replace(/\/+$/, '').split('/');
83
+ return parts[parts.length - 1] || path;
84
+ }
85
+ /*
86
+ * A REPORT IS NOT A SAFE PLACE FOR A SECRET. The terminal and --json show a
87
+ * hardcoded credential in full, because the person running the scan already
88
+ * has the code. This file is made to be sent to someone, so the value of a
89
+ * reported secret is cut to at most its first three characters (fewer for a
90
+ * short one) and its length - enough to find it in the code, not to use it.
91
+ */
92
+ export function maskSecretLiterals(snippet) {
93
+ return snippet.replace(/(["'`])((?:\\.|(?!\1)[^\\])*)\1/g, (whole, quote, inner) => inner.length < 4
94
+ ? whole
95
+ : `${quote}${inner.slice(0, Math.min(3, Math.floor(inner.length / 4)))}…${quote} [${inner.length} characters, hidden in this report]`);
96
+ }
97
+ function bySeverityThenPlace(a, b) {
98
+ const s = SEVERITIES.indexOf(a.severity) - SEVERITIES.indexOf(b.severity);
99
+ if (s !== 0)
100
+ return s;
101
+ const f = a.location.file.localeCompare(b.location.file);
102
+ return f !== 0 ? f : a.location.startLine - b.location.startLine;
103
+ }
104
+ function countBySeverity(findings) {
105
+ const counts = { critical: 0, high: 0, medium: 0, low: 0, info: 0 };
106
+ for (const f of findings)
107
+ counts[f.severity]++;
108
+ return counts;
109
+ }
110
+ export function renderHtmlReport(result, options) {
111
+ const rel = relativeTo(result.target, options.root ?? '');
112
+ const rules = new Map(ALL_RULES.map((r) => [r.id, r]));
113
+ const proven = result.findings.filter((f) => f.confidence === 'flow-verified').sort(bySeverityThenPlace);
114
+ const guessed = result.findings.filter((f) => f.confidence === 'signature-based').sort(bySeverityThenPlace);
115
+ const cleared = [...result.verifiedClean].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
116
+ const guessedInTests = guessed.filter((f) => isTestPath(f.location.file));
117
+ const project = options.projectName?.trim() || lastSegment(result.target);
118
+ const date = options.generatedAt.toISOString().slice(0, 10);
119
+ const version = result.coverage.engine.version;
120
+ const languagesSeen = Object.entries(result.byLanguage)
121
+ .filter(([, n]) => n > 0)
122
+ .sort((a, b) => b[1] - a[1])
123
+ .map(([id, n]) => `${LANGUAGES.find((l) => l.id === id)?.displayName ?? id} (${n.toLocaleString('en-US')})`);
124
+ /* ------------------------------------------------------------ pieces -- */
125
+ const severityPill = (s) => `<span class="sev sev-${s}">${SEVERITY_LABEL[s]}</span>`;
126
+ const where = (file, line) => `<span class="where">${e(rel(file))}:${line}</span>`;
127
+ const code = (text) => `<code class="snippet">${e(text)}</code>`;
128
+ const stepLabel = {
129
+ source: 'Enters',
130
+ propagation: 'Passes',
131
+ sanitizer: 'Cleaned',
132
+ sink: 'Lands',
133
+ };
134
+ const provenCard = (f, index) => {
135
+ const rule = rules.get(f.ruleId);
136
+ const steps = (f.flowPath ?? [])
137
+ .map((s) => `<li class="step step-${s.kind}"><span class="step-kind">${stepLabel[s.kind]}</span>` +
138
+ `<span class="step-body">${where(s.location.file, s.location.startLine)} ${code(s.location.snippet)}` +
139
+ `<span class="step-why">${e(s.description)}</span></span></li>`)
140
+ .join('');
141
+ return (`<article class="finding card proven" data-rule="${e(f.ruleId)}">` +
142
+ `<header><span class="dot dot-proven" aria-hidden="true"></span>` +
143
+ `<span class="num">P${index + 1}</span>${severityPill(f.severity)}` +
144
+ `<h3>${e(f.ruleName)}</h3></header>` +
145
+ `<p class="loc">${where(f.location.file, f.location.startLine)}</p>` +
146
+ `<p class="what">${e(f.message)}</p>` +
147
+ `<h4>The path Defuse followed</h4><ol class="path">${steps}</ol>` +
148
+ (rule ? `<h4>What this is, and how to fix it</h4><p>${e(rule.explanation)}</p>` : '') +
149
+ `<p class="ref">${e(f.cwe)} · ${e(f.owasp)}</p>` +
150
+ `</article>`);
151
+ };
152
+ const shown = (f) => f.ruleId === 'hardcoded-secret' ? maskSecretLiterals(f.location.snippet) : f.location.snippet;
153
+ const guessRow = (f, ruleLimitations) => `<tr class="finding guess"><td>${severityPill(f.severity)}</td>` +
154
+ `<td>${where(f.location.file, f.location.startLine)}</td>` +
155
+ `<td><div>${e(f.message)}</div>${code(shown(f))}` +
156
+ (f.limitations && f.limitations !== ruleLimitations
157
+ ? `<div class="hitnote">${e(f.limitations)}</div>`
158
+ : '') +
159
+ `</td></tr>`;
160
+ const guessTable = (rows, ruleLimitations) => `<table class="rows"><thead><tr><th>Severity</th><th>Where</th><th>What was matched</th></tr></thead>` +
161
+ `<tbody>${rows.map((f) => guessRow(f, ruleLimitations)).join('')}</tbody></table>`;
162
+ /* ----------------------------------------------------------- sections -- */
163
+ const provenCounts = countBySeverity(proven);
164
+ const guessedCounts = countBySeverity(guessed);
165
+ const severityTable = `<table class="sevtable"><thead><tr><th></th>${SEVERITIES.filter((s) => s !== 'info')
166
+ .map((s) => `<th>${SEVERITY_LABEL[s]}</th>`)
167
+ .join('')}</tr></thead><tbody>` +
168
+ `<tr><th><span class="dot dot-proven" aria-hidden="true"></span>Proven</th>${SEVERITIES.filter((s) => s !== 'info')
169
+ .map((s) => `<td>${provenCounts[s]}</td>`)
170
+ .join('')}</tr>` +
171
+ `<tr><th><span class="dot dot-guess" aria-hidden="true"></span>To review</th>${SEVERITIES.filter((s) => s !== 'info')
172
+ .map((s) => `<td>${guessedCounts[s]}</td>`)
173
+ .join('')}</tr></tbody></table>`;
174
+ const verdict = (proven.length > 0
175
+ ? `Defuse proved ${plural(proven.length, 'problem')} by following attacker-controlled input to the place it does harm. `
176
+ : 'Defuse did not prove any problem in this code. That is not the same as finding none - see section 4 for what it does not check. ') +
177
+ (guessed.length > 0
178
+ ? `It also found ${plural(guessed.length, 'place')} that match a known risky pattern and need a person to check them` +
179
+ (guessedInTests.length > 0 ? ` (${guessedInTests.length.toLocaleString('en-US')} of them in test or example files)` : '') +
180
+ '. '
181
+ : 'Nothing else matched a risky pattern. ') +
182
+ (cleared.length > 0
183
+ ? `${plural(cleared.length, 'line')} that looked risky ${cleared.length === 1 ? 'was' : 'were'} checked and cleared, each with the reason.`
184
+ : '');
185
+ const provenSection = `<section id="proven"><h2><span class="secnum">1</span>Proven problems</h2>` +
186
+ `<p class="lead">Each of these comes with the path Defuse followed: where outside input enters, every step it passes through, and where it lands. ` +
187
+ `Read the path - it is the evidence, and it can be checked line by line. A step marked as passing through a function Defuse does not know is an assumption, and says so.</p>` +
188
+ (proven.length > 0
189
+ ? proven.map(provenCard).join('')
190
+ : `<p class="empty">No problem was proven. Section 2 lists what still needs a person to look at it, and section 4 what was not checked.</p>`) +
191
+ `</section>`;
192
+ const guessGroups = [...new Set(guessed.map((f) => f.ruleId))]
193
+ .map((id) => ({ id, rows: guessed.filter((f) => f.ruleId === id) }))
194
+ .sort((a, b) => {
195
+ const top = (rows) => Math.min(...rows.map((f) => SEVERITIES.indexOf(f.severity)));
196
+ return top(a.rows) - top(b.rows) || b.rows.length - a.rows.length;
197
+ });
198
+ const guessSection = `<section id="review"><h2><span class="secnum">2</span>To review: matched, not proven</h2>` +
199
+ `<p class="lead">These lines match the shape of a known problem, but Defuse could not follow the data to prove it. ` +
200
+ `Some will be real and some will be false alarms. Under each heading is what the check cannot tell - that is the question a reviewer has to answer for each line. ` +
201
+ `Lines in test and example files are listed separately: they rarely run in production, but they are still in the code.</p>` +
202
+ (guessGroups.length > 0
203
+ ? guessGroups
204
+ .map(({ id, rows }) => {
205
+ const rule = rules.get(id);
206
+ const name = rule?.name ?? rows[0]?.ruleName ?? id;
207
+ const limits = rule?.limitations ?? rows[0]?.limitations ?? '';
208
+ const main = rows.filter((f) => !isTestPath(f.location.file));
209
+ const tests = rows.filter((f) => isTestPath(f.location.file));
210
+ return (`<div class="group" data-rule="${e(id)}"><h3><span class="dot dot-guess" aria-hidden="true"></span>${e(name)} <span class="count">${rows.length}</span></h3>` +
211
+ ((explain) => (explain ? `<p class="explain">${e(explain)}</p>` : ''))(rule?.explanation ?? [RUNTIME_VERSION_RULE, DEPENDENCY_RULE].find((r) => r.id === id)?.explanation) +
212
+ `<p class="limits"><strong>What this check cannot tell:</strong> ${e(limits)}</p>` +
213
+ (id === 'hardcoded-secret'
214
+ ? `<p class="hitnote">The values below are cut to their first three characters, so this report does not pass the secrets on. The full values are in the code, and in the scan's own output.</p>`
215
+ : '') +
216
+ (main.length > 0 ? `<h4>In the application code (${main.length})</h4>${guessTable(main, limits)}` : '') +
217
+ (tests.length > 0 ? `<h4>In test and example files (${tests.length})</h4>${guessTable(tests, limits)}` : '') +
218
+ `</div>`);
219
+ })
220
+ .join('')
221
+ : `<p class="empty">Nothing matched a risky pattern.</p>`) +
222
+ `</section>`;
223
+ const clearedSection = `<section id="cleared"><h2><span class="secnum">3</span>Checked and cleared</h2>` +
224
+ `<p class="lead">These lines looked like a problem, and Defuse proved they are not - a value that is fixed on every path, a sanitiser that is shown to run, or the code's own statement of purpose. ` +
225
+ `Each reason is the claim it rests on. A row marked <strong>assumes</strong> also rests on a function Defuse does not model, ` +
226
+ `which the value went through after it was cleaned and which was taken to hand back what it was given - if that function undoes the cleaning, the row is wrong.` +
227
+ (result.stats.regionsProvedUnreachable > 0
228
+ ? ` Separately, ${plural(result.stats.regionsProvedUnreachable, 'branch', 'branches')} were skipped because a fixed condition means they cannot run.`
229
+ : '') +
230
+ `</p>` +
231
+ (cleared.length > 0
232
+ ? `<table class="rows"><thead><tr><th>Where</th><th>Check</th><th>Why it is clear</th></tr></thead><tbody>` +
233
+ cleared
234
+ .map((c) => `<tr class="cleared"><td>${where(c.file, c.line)}</td><td>${e(rules.get(c.ruleId)?.name ?? c.ruleId)}</td>` +
235
+ `<td>${e(capitalise(c.reason ?? 'no reason was recorded for this line'))}` +
236
+ (c.assumes && c.assumes.length > 0
237
+ ? `<br><strong>Assumes</strong> ${e(c.assumes.map((n) => `${n}()`).join(', '))} ` +
238
+ `${c.assumes.length === 1 ? 'hands' : 'hand'} back what ${c.assumes.length === 1 ? 'it was' : 'they were'} given.`
239
+ : '') +
240
+ `</td></tr>`)
241
+ .join('') +
242
+ `</tbody></table>`
243
+ : `<p class="empty">No line needed clearing.</p>`) +
244
+ `</section>`;
245
+ /* ---- 4. what was not checked ---- */
246
+ const presentLanguages = new Set(Object.entries(result.byLanguage).filter(([, n]) => n > 0).map(([id]) => id));
247
+ const gapsHere = result.coverage.gaps.filter((g) => presentLanguages.has(g.language));
248
+ const noRule = result.coverage.owaspCoverage.filter((c) => c.state === 'none');
249
+ const partial = result.coverage.owaspCoverage.filter((c) => c.state === 'partial');
250
+ const limits = result.traceLimits;
251
+ const notCheckedItems = [];
252
+ /*
253
+ * Always present, because it is always true: the walker skips dependency
254
+ * and build folders, and Defuse does not check which package versions are
255
+ * known to be vulnerable. A client asking "is this app secure?" needs to
256
+ * know that half of the question is a different tool.
257
+ */
258
+ notCheckedItems.push(`<li><strong>Other people's code.</strong> Dependency and build folders (node_modules, vendor, dist, build, ` +
259
+ `virtual environments and the like) are not scanned. ` +
260
+ (result.dependencies
261
+ ? `The library versions pinned in ${result.dependencies.read.length} lockfile(s) were checked against osv.dev on ` +
262
+ `${e(readableDate(result.dependencies.checkedAt.slice(0, 10)))} - that says a vulnerable version is installed, not that it is called.` +
263
+ (result.dependencies.notRead.length
264
+ ? ` Not read: ${result.dependencies.notRead.map((f) => `<code>${e(f)}</code>`).join(', ')}.`
265
+ : '') +
266
+ `</li>`
267
+ : `Defuse did not check whether the libraries the project uses have known vulnerabilities - ` +
268
+ `that runs only when the scan is given <code>--deps</code>.</li>`));
269
+ /*
270
+ * The PHP version check reads declarations, which can differ from the server.
271
+ * Say what was read, and say it when nothing could be - a client must not
272
+ * take a missing runtime finding as "the PHP version is fine".
273
+ */
274
+ const runtime = result.runtimeVersions;
275
+ if (runtime && (runtime.declared.length > 0 || runtime.notes.length > 0)) {
276
+ notCheckedItems.push(`<li><strong>The PHP version, as declared, not as running.</strong> Defuse compared the PHP versions this ` +
277
+ `project declares with php.net's support dates and critical advisories, checked ${e(readableDate(runtime.factsChecked))}. ` +
278
+ `What the server actually runs was not seen - confirm with <code>php -v</code> there.` +
279
+ (runtime.declared.length > 0
280
+ ? `<ul>${runtime.declared.map((d) => `<li>${where(d.file, d.line)} <code>${e(d.declared)}</code>: ${e(d.status)}</li>`).join('')}</ul>`
281
+ : '') +
282
+ (runtime.notes.length > 0 ? `<ul>${runtime.notes.map((n) => `<li>${e(n)}</li>`).join('')}</ul>` : '') +
283
+ `</li>`);
284
+ }
285
+ if (options.intake?.trim()) {
286
+ notCheckedItems.push(`<li><strong>How the files were gathered.</strong> ${e(options.intake.trim())}</li>`);
287
+ }
288
+ if (noRule.length > 0) {
289
+ notCheckedItems.push(`<li><strong>Whole kinds of problem Defuse has no check for.</strong> Silence here means nothing either way:<ul>` +
290
+ noRule.map((c) => `<li>${e(c.id)} ${e(c.title)} - for example ${e(c.examples)}</li>`).join('') +
291
+ `</ul></li>`);
292
+ }
293
+ if (partial.length > 0) {
294
+ notCheckedItems.push(`<li><strong>Kinds of problem checked only in part.</strong><ul>` +
295
+ partial.map((c) => `<li>${e(c.id)} ${e(c.title)}: Defuse checks ${e(c.weCheck ?? '')}</li>`).join('') +
296
+ `</ul></li>`);
297
+ }
298
+ if (gapsHere.length > 0) {
299
+ notCheckedItems.push(`<li><strong>Checks that are partial or missing for the languages in this project.</strong><ul>` +
300
+ gapsHere
301
+ .map((g) => `<li>${e(g.ruleName)} in ${e(LANGUAGES.find((l) => l.id === g.language)?.displayName ?? g.language)}: ${e(g.note)}</li>`)
302
+ .join('') +
303
+ `</ul></li>`);
304
+ }
305
+ if (result.coverage.unscannedExtensions.length > 0) {
306
+ notCheckedItems.push(`<li><strong>Files Defuse cannot read</strong> were present and were not scanned: ${e(result.coverage.unscannedExtensions.join(', '))}.</li>`);
307
+ }
308
+ if (result.excluded.fileCount > 0) {
309
+ notCheckedItems.push(`<li><strong>Left out on request:</strong> ${plural(result.excluded.fileCount, 'file')} matching ${e(result.excluded.patterns.join(', '))}.</li>`);
310
+ }
311
+ if (result.oversizedFiles.length > 0) {
312
+ notCheckedItems.push(`<li><strong>Too large to read:</strong> ${result.oversizedFiles.map((f) => e(rel(f))).join(', ')}.</li>`);
313
+ }
314
+ if (result.parseProblems.length > 0) {
315
+ notCheckedItems.push(`<li><strong>Files that did not parse cleanly</strong> (the readable parts were checked; findings near the damage may be missing): ` +
316
+ result.parseProblems.map((p) => e(rel(p.file))).join(', ') +
317
+ `.</li>`);
318
+ }
319
+ if (limits.depthTruncations + limits.recursionStops + limits.unmodelledHops > 0) {
320
+ notCheckedItems.push(`<li><strong>Where the data-flow trace stopped or assumed.</strong> ` +
321
+ `${plural(limits.depthTruncations, 'trace')} reached the depth limit, ` +
322
+ `${plural(limits.recursionStops, 'call')} ${limits.recursionStops === 1 ? 'was' : 'were'} not re-entered because the function was already being followed, and ` +
323
+ `${plural(limits.unmodelledHops, 'step')} passed through a function Defuse has no entry for, where the value was assumed to carry through. ` +
324
+ `A sanitiser hidden past such a point is missed, and the line is reported rather than cleared.</li>`);
325
+ }
326
+ if (result.suppressions.length > 0) {
327
+ notCheckedItems.push(`<li><strong>Silenced in the code itself</strong> with a defuse:ignore comment: ` +
328
+ result.suppressions.map((s) => `${e(rel(s.file))}:${s.line} (${e(s.ruleId)})`).join(', ') +
329
+ `.</li>`);
330
+ }
331
+ /*
332
+ * The engine's own list of what it does not do, word for word. Long, and
333
+ * written for engineers - so it is an appendix rather than the body of
334
+ * section 4, but it is printed in full: shortening it would be choosing which
335
+ * limits a client gets to see.
336
+ */
337
+ const engineLimits = `<section id="engine-limits" class="appendix"><h2><span class="secnum">A</span>Appendix: the engine's known limits, in full</h2>` +
338
+ `<p class="lead">Defuse's own statement of what its analysis does not do, as printed by every scan.</p><ul>` +
339
+ result.coverage.engine.notImplemented.map((n) => `<li>${e(n)}</li>`).join('') +
340
+ `</ul></section>`;
341
+ const notCheckedSection = `<section id="not-checked"><h2><span class="secnum">4</span>What this scan did not check</h2>` +
342
+ `<p class="lead">A problem of a kind listed here would not appear in this report even if it is in the code. ` +
343
+ `"Not listed above" must not be read as "safe".</p>` +
344
+ `<ul class="notchecked">${notCheckedItems.join('')}</ul>` +
345
+ `<p class="lead">The appendix lists the engine's own limits in full.</p>` +
346
+ `</section>`;
347
+ const aboutSection = `<section id="about"><h2><span class="secnum">5</span>About this report</h2>` +
348
+ `<p>Produced by Defuse ${e(version)}, a static analysis tool: it reads the source code and does not run the application. ` +
349
+ `So neither label proves a problem can be exploited in production - that depends on how the code is deployed and called. ` +
350
+ `What the labels do say is how much was checked: <span class="dot dot-proven" aria-hidden="true"></span><strong>proven</strong> means a complete path was followed and is printed; ` +
351
+ `<span class="dot dot-guess" aria-hidden="true"></span><strong>to review</strong> means a pattern matched and nothing more.</p>` +
352
+ `<p>${plural(result.stats.filesParsed, 'file')} read, ${plural(result.stats.shapesExamined, 'code shape')} examined, ` +
353
+ `${plural(result.traceLimits.sourcesFound, 'input source')} recognised. ` +
354
+ `Defuse is open source under ${e(LICENCE_SPDX)}: github.com/Sikkuumi/Defuse.</p>` +
355
+ `</section>`;
356
+ /* ------------------------------------------------------------- page -- */
357
+ const meta = [
358
+ ['Project', project],
359
+ ['Scanned', date],
360
+ ['Files read', `${result.stats.filesParsed.toLocaleString('en-US')}${languagesSeen.length ? ` - ${languagesSeen.join(', ')}` : ''}`],
361
+ ['Engine', `Defuse ${version} · ${result.coverage.engine.analysisLabel.toLowerCase()}`],
362
+ ];
363
+ if (options.preparedBy?.trim())
364
+ meta.push(['Prepared by', options.preparedBy.trim()]);
365
+ return `<!doctype html>
366
+ <html lang="en">
367
+ <head>
368
+ <meta charset="utf-8">
369
+ <meta name="viewport" content="width=device-width, initial-scale=1">
370
+ <meta name="generator" content="Defuse ${e(version)}">
371
+ <title>${e(project)} - Defuse security scan report</title>
372
+ <style>${STYLES}</style>
373
+ </head>
374
+ <body>
375
+ <main class="report" data-count-proven="${proven.length}" data-count-review="${guessed.length}" data-count-cleared="${cleared.length}">
376
+ <header class="top">
377
+ <div class="brand">${markSvg(34)}<span class="brand-name">Defuse</span></div>
378
+ <div class="doc">Security scan report</div>
379
+ </header>
380
+ <h1>${e(project)}</h1>
381
+ <table class="meta"><tbody>${meta.map(([k, v]) => `<tr><th>${e(k)}</th><td>${e(v)}</td></tr>`).join('')}</tbody></table>
382
+ ${options.note?.trim() ? `<p class="note">${e(options.note.trim())}</p>` : ''}
383
+
384
+ <section id="glance">
385
+ <div class="stats">
386
+ <div class="stat"><div class="stat-n"><span class="dot dot-proven big" aria-hidden="true"></span>${proven.length.toLocaleString('en-US')}</div><div class="stat-l">Proven</div><div class="stat-d">A path from outside input to harm was followed and is printed.</div></div>
387
+ <div class="stat"><div class="stat-n"><span class="dot dot-guess big" aria-hidden="true"></span>${guessed.length.toLocaleString('en-US')}</div><div class="stat-l">To review</div><div class="stat-d">Matches a risky pattern. Not proven; some will be false alarms.</div></div>
388
+ <div class="stat"><div class="stat-n"><span class="tick" aria-hidden="true">✓</span>${cleared.length.toLocaleString('en-US')}</div><div class="stat-l">Cleared</div><div class="stat-d">Looked risky, and was shown not to be, with the reason.</div></div>
389
+ </div>
390
+ <p class="verdict">${e(verdict)}</p>
391
+ ${severityTable}
392
+ </section>
393
+
394
+ ${provenSection}
395
+ ${guessSection}
396
+ ${clearedSection}
397
+ ${notCheckedSection}
398
+ ${aboutSection}
399
+ ${engineLimits}
400
+ <footer>Defuse ${e(version)} · ${e(date)} · every finding is labelled proven or to review, and nothing in between.</footer>
401
+ </main>
402
+ </body>
403
+ </html>
404
+ `;
405
+ }
406
+ /*
407
+ * One stylesheet, light, built for paper first. System fonts only - a report
408
+ * that fetched a web font would be a report that phones home when opened.
409
+ */
410
+ const STYLES = `
411
+ :root{--ink:#1d1c18;--muted:#5d5c56;--line:#dcdad3;--soft:#f4f3ef;--paper:#fff;--brand:#792CF7;
412
+ --crit:#a3122f;--high:#b4441b;--med:#7a5a00;--low:#4b5563}
413
+ *{box-sizing:border-box}
414
+ html{background:var(--paper)}
415
+ body{margin:0;background:var(--paper);color:var(--ink);font:14px/1.55 system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;-webkit-print-color-adjust:exact;print-color-adjust:exact}
416
+ .report{max-width:860px;margin:0 auto;padding:32px 24px 48px}
417
+ .top{display:flex;align-items:center;justify-content:space-between;border-bottom:2px solid var(--ink);padding-bottom:12px}
418
+ .brand{display:flex;align-items:center;gap:10px}
419
+ .brand-name{font-weight:700;font-size:20px;letter-spacing:-.01em;color:var(--brand)}
420
+ .doc{font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
421
+ h1{font-size:28px;line-height:1.2;margin:22px 0 10px;letter-spacing:-.01em;overflow-wrap:anywhere}
422
+ h2{font-size:19px;margin:36px 0 8px;padding-top:14px;border-top:1px solid var(--line);display:flex;align-items:baseline;gap:10px}
423
+ .secnum{font:600 12px/1 ui-monospace,"SF Mono",Menlo,Consolas,monospace;color:var(--muted);border:1px solid var(--line);border-radius:3px;padding:3px 6px}
424
+ h3{font-size:15.5px;margin:0}
425
+ h4{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);margin:16px 0 6px}
426
+ p{margin:6px 0}
427
+ .lead{color:var(--muted);max-width:72ch}
428
+ .meta{border-collapse:collapse;margin:0 0 8px}
429
+ .note{border-left:3px solid var(--brand);background:var(--soft);padding:9px 14px;margin:14px 0 6px;max-width:72ch}
430
+ .meta th{text-align:left;font-weight:500;color:var(--muted);padding:2px 18px 2px 0;vertical-align:top;white-space:nowrap}
431
+ .meta td{padding:2px 0;overflow-wrap:anywhere}
432
+ .stats{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin:20px 0 12px}
433
+ .stat{border:1px solid var(--line);border-radius:6px;padding:12px 14px}
434
+ .stat-n{font-size:30px;font-weight:700;display:flex;align-items:center;gap:10px;line-height:1.1}
435
+ .stat-l{font-weight:600;margin-top:2px}
436
+ .stat-d{color:var(--muted);font-size:12.5px}
437
+ .tick{font-size:22px;color:var(--ink)}
438
+ .verdict{font-size:15px;max-width:72ch}
439
+ .dot{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:7px;vertical-align:1px;flex:none}
440
+ .dot.big{width:14px;height:14px;margin-right:0}
441
+ .dot-proven{background:var(--ink);border:2px solid var(--ink)}
442
+ .dot-guess{background:transparent;border:2px solid var(--ink)}
443
+ .sevtable{border-collapse:collapse;margin:12px 0 0;font-variant-numeric:tabular-nums}
444
+ .sevtable th,.sevtable td{border:1px solid var(--line);padding:5px 12px;text-align:right}
445
+ .sevtable thead th{font-weight:600;font-size:12px;color:var(--muted)}
446
+ .sevtable tbody th{text-align:left;font-weight:500;white-space:nowrap}
447
+ .sev{display:inline-block;font:600 11px/1 ui-monospace,"SF Mono",Menlo,Consolas,monospace;letter-spacing:.04em;text-transform:uppercase;border:1px solid currentColor;border-radius:3px;padding:3px 5px;white-space:nowrap}
448
+ .sev-critical{color:var(--crit)}.sev-high{color:var(--high)}.sev-medium{color:var(--med)}.sev-low,.sev-info{color:var(--low)}
449
+ .where{font:12.5px/1.4 ui-monospace,"SF Mono",Menlo,Consolas,monospace;overflow-wrap:anywhere}
450
+ .snippet{display:block;font:12px/1.45 ui-monospace,"SF Mono",Menlo,Consolas,monospace;background:var(--soft);border-radius:4px;padding:5px 8px;margin:4px 0;white-space:pre-wrap;overflow-wrap:anywhere}
451
+ .card{border:1px solid var(--line);border-left:4px solid var(--ink);border-radius:6px;padding:14px 16px;margin:14px 0;break-inside:avoid}
452
+ .card header{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
453
+ .num{font:600 12px/1 ui-monospace,"SF Mono",Menlo,Consolas,monospace;color:var(--muted)}
454
+ .loc{margin:6px 0 2px}
455
+ .what{font-weight:500}
456
+ .path{list-style:none;padding:0;margin:0;counter-reset:s}
457
+ .step{display:flex;gap:10px;padding:6px 0;border-top:1px dashed var(--line)}
458
+ .step:first-child{border-top:0}
459
+ .step-kind{flex:none;width:58px;font:600 11px/1.9 ui-monospace,"SF Mono",Menlo,Consolas,monospace;text-transform:uppercase;letter-spacing:.04em;color:var(--muted)}
460
+ .step-source .step-kind,.step-sink .step-kind{color:var(--ink)}
461
+ .step-body{flex:1;min-width:0}
462
+ .step-why{display:block;color:var(--muted);font-size:13px}
463
+ .ref{color:var(--muted);font-size:12.5px;margin-top:10px}
464
+ .group{margin:18px 0 26px}
465
+ .group h3{display:flex;align-items:center}
466
+ .count{font:600 12px/1 ui-monospace,"SF Mono",Menlo,Consolas,monospace;color:var(--muted);margin-left:8px}
467
+ .explain{max-width:72ch}
468
+ .limits{background:var(--soft);border-radius:4px;padding:8px 10px;font-size:13px;max-width:none}
469
+ .rows{width:100%;border-collapse:collapse;table-layout:fixed;margin:4px 0}
470
+ .rows th{text-align:left;font-size:12px;font-weight:600;color:var(--muted);border-bottom:1px solid var(--line);padding:5px 8px}
471
+ .rows td{border-bottom:1px solid var(--line);padding:7px 8px;vertical-align:top;overflow-wrap:anywhere}
472
+ .rows tr{break-inside:avoid}
473
+ .rows th:nth-child(1){width:96px}.rows th:nth-child(2){width:32%}
474
+ #cleared .rows th:nth-child(1){width:34%}#cleared .rows th:nth-child(2){width:22%}
475
+ .hitnote{color:var(--muted);font-size:12.5px;margin-top:4px}
476
+ .empty{border:1px dashed var(--line);border-radius:6px;padding:12px 14px;color:var(--muted)}
477
+ .notchecked{padding-left:18px}
478
+ .notchecked>li{margin:8px 0}
479
+ .notchecked ul{margin:4px 0;padding-left:18px;color:var(--muted)}
480
+ .appendix ul{font-size:12.5px;color:var(--muted);padding-left:18px}
481
+ .appendix li{margin:5px 0}
482
+ footer{margin-top:40px;padding-top:10px;border-top:1px solid var(--line);color:var(--muted);font-size:12px}
483
+ @media (max-width:640px){.stats{grid-template-columns:1fr}.report{padding:20px 16px 40px}h1{font-size:23px}
484
+ .rows,.rows tbody,.rows tr,.rows td{display:block;width:auto}.rows thead{display:none}
485
+ .rows tr{border-bottom:1px solid var(--line);padding:8px 0}.rows td{border:0;padding:2px 0}}
486
+ @page{size:A4;margin:16mm 14mm}
487
+ @media print{.report{max-width:none;padding:0}.appendix{break-before:page}h2{break-after:avoid}.group h3,.group h4{break-after:avoid}a{color:inherit}}
488
+ `;
489
+ //# sourceMappingURL=html-report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-report.js","sourceRoot":"","sources":["../../../src/report/html-report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * HUMAN-READABLE REPORT
3
+ *
4
+ * Design brief: someone should be able to skim this in a terminal and know
5
+ * three things without reading carefully -
6
+ * 1. where the problem is,
7
+ * 2. how bad it would be,
8
+ * 3. HOW SURE WE ARE.
9
+ *
10
+ * Point 3 is the one other tools bury. It gets its own column here, on every
11
+ * single line, and it gets a summary block that states the count of unverified
12
+ * findings in plain words. If this report ever looks more confident than the
13
+ * engine is, the report is wrong.
14
+ */
15
+ import { type Severity } from '../core/finding.js';
16
+ import type { ScanResult } from '../engine/scan.js';
17
+ export interface HumanReportOptions {
18
+ readonly showSuppressed?: boolean;
19
+ readonly showCoverageMatrix?: boolean;
20
+ readonly minSeverity?: Severity;
21
+ /**
22
+ * Print everything: the gauge arithmetic, each finding's reasoning and
23
+ * limits, OWASP and the full "not checked" list. Off by default because a
24
+ * first-time tester read 200 lines for one finding and called it
25
+ * overwhelming. Nothing is dropped - the short report names every gap in a
26
+ * line and points here, and --json and --report always carry all of it.
27
+ */
28
+ readonly full?: boolean;
29
+ /**
30
+ * What the scan was actually pointed at, as resolved from the command line.
31
+ *
32
+ * This is here because of a real misread. `npm run scan --exclude tests`
33
+ * looks like "scan everything except tests"; npm swallows `--exclude` as one
34
+ * of its own config flags and forwards only `tests`, so the tool scanned the
35
+ * fixture folder and NOTHING else. The report was correct - 75 findings in
36
+ * files written to be vulnerable - and completely misleading, because the one
37
+ * fact that would have explained it was the one fact not printed.
38
+ *
39
+ * A report that says how much it examined but never says WHAT is describing a
40
+ * measurement without its units.
41
+ */
42
+ readonly target?: string;
43
+ }
44
+ export declare function renderHuman(result: ScanResult, options?: HumanReportOptions): string;