@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,232 @@
1
+ /**
2
+ * THE GAUGES
3
+ * ==========
4
+ *
5
+ * Three needles. They are never added together, and this file will not let
6
+ * them be - there is no `overallScore` export, and there never should be.
7
+ *
8
+ * WHY THAT MATTERS. The comment at the top of finding.ts says it best:
9
+ *
10
+ * "Severity = how bad is this if it turns out to be real? Deliberately
11
+ * separate from confidence, which is how sure are we it's real?
12
+ * Conflating those two numbers is the single most common lie in SAST
13
+ * tooling: a tool shows one 'risk' bar and you cannot tell whether it
14
+ * means 'probably harmless but catastrophic if not' or 'definitely real
15
+ * but minor'."
16
+ *
17
+ * A single 0-500 risk dial IS that bar. So the dial got split along the exact
18
+ * line the project already draws:
19
+ *
20
+ * VERIFIED EXPOSURE severity, over findings we PROVED reach a sink
21
+ * UNVERIFIED SURFACE severity, over findings we only pattern-matched
22
+ * ANALYSIS COVERAGE how much of the code we could actually reason about
23
+ *
24
+ * Two of those are the same weights over two different confidence levels, kept
25
+ * side by side so you can see the shape of what is proven against the shape of
26
+ * what is merely suspected. Reading them together is the point; averaging them
27
+ * would destroy it.
28
+ *
29
+ * Every reading carries its own arithmetic in `working`, so the number can be
30
+ * CHECKED rather than believed. That is the whole standard here: a score you
31
+ * cannot recompute by hand is a vibe with a decimal point on it.
32
+ */
33
+ /**
34
+ * How much one finding contributes, by severity.
35
+ *
36
+ * These are a judgement, not a measurement, and calling them anything else
37
+ * would be the first lie. What makes them defensible is that they are FIXED,
38
+ * PUBLISHED and applied identically to both severity gauges - so while the
39
+ * absolute number is arbitrary, every comparison made with it is real: two
40
+ * criticals always outweigh three mediums, in both directions, in both shells.
41
+ */
42
+ export const SEVERITY_WEIGHT = {
43
+ critical: 25,
44
+ high: 15,
45
+ medium: 7,
46
+ low: 3,
47
+ info: 1,
48
+ };
49
+ /** The top of the severity dials. A sum past this is reported, not hidden. */
50
+ export const GAUGE_MAX = 500;
51
+ function weigh(findings) {
52
+ const bySeverity = new Map();
53
+ let total = 0;
54
+ for (const finding of findings) {
55
+ bySeverity.set(finding.severity, (bySeverity.get(finding.severity) ?? 0) + 1);
56
+ total += SEVERITY_WEIGHT[finding.severity];
57
+ }
58
+ return { total, bySeverity };
59
+ }
60
+ const ORDER = ['critical', 'high', 'medium', 'low', 'info'];
61
+ function showWorking(bySeverity, total) {
62
+ const lines = ORDER.filter((s) => bySeverity.get(s)).map((s) => {
63
+ const n = bySeverity.get(s) ?? 0;
64
+ return `${n} ${s} x ${SEVERITY_WEIGHT[s]} = ${n * SEVERITY_WEIGHT[s]}`;
65
+ });
66
+ if (lines.length === 0)
67
+ return ['nothing in this category'];
68
+ if (lines.length > 1)
69
+ lines.push(`total = ${total}`);
70
+ return lines;
71
+ }
72
+ function severityGauge(id, label, meaning, findings, examinedNothing) {
73
+ const { total, bySeverity } = weigh(findings);
74
+ const working = showWorking(bySeverity, total);
75
+ if (total > GAUGE_MAX) {
76
+ working.push(`past the top of the dial by ${total - GAUGE_MAX} - needle pinned at ${GAUGE_MAX}`);
77
+ }
78
+ const segments = ORDER.filter((s) => bySeverity.get(s)).map((severity) => {
79
+ const count = bySeverity.get(severity) ?? 0;
80
+ const weight = count * SEVERITY_WEIGHT[severity];
81
+ return { severity, count, weight, share: total > 0 ? weight / total : 0 };
82
+ });
83
+ return {
84
+ id,
85
+ label,
86
+ value: Math.min(total, GAUGE_MAX),
87
+ max: GAUGE_MAX,
88
+ unit: '',
89
+ meaning,
90
+ working,
91
+ rawTotal: total,
92
+ capped: total > GAUGE_MAX,
93
+ noReading: examinedNothing,
94
+ segments,
95
+ };
96
+ }
97
+ /**
98
+ * How much of the analysis actually completed.
99
+ *
100
+ * This is the gauge no other scanner shows, and it is the one that tells you
101
+ * how to read the other two. A verified-exposure of 0 means something very
102
+ * different at 95% coverage than at 30%: the first is reassuring, the second
103
+ * means we barely looked.
104
+ *
105
+ * It is an average of the ratios we can genuinely compute, and each one is
106
+ * listed in `working` so an average that hides a bad component cannot pass
107
+ * unnoticed. Ratios we cannot compute are OMITTED rather than assumed to be
108
+ * 100% - an unmeasured thing is not a passing thing.
109
+ */
110
+ function coverageGauge(result, filesFound, examinedNothing) {
111
+ const working = [];
112
+ const parts = [];
113
+ const found = filesFound || result.stats.filesParsed;
114
+ if (found > 0) {
115
+ const rate = result.stats.filesParsed / found;
116
+ parts.push(rate);
117
+ working.push(`files read: ${result.stats.filesParsed}/${found} = ${Math.round(rate * 100)}%`);
118
+ }
119
+ const { rules, languages, implemented, partial } = result.coverage.counts;
120
+ const cells = rules * languages;
121
+ if (cells > 0) {
122
+ // A partial rule counts as half. It does something; it does not do the job.
123
+ const rate = (implemented + partial * 0.5) / cells;
124
+ parts.push(rate);
125
+ working.push(`rules available: ${implemented} full + ${partial} partial of ${cells} = ${Math.round(rate * 100)}%`);
126
+ }
127
+ if (result.crossFile.enabled) {
128
+ /*
129
+ * A local import that resolved to nothing is a module the analysis never
130
+ * entered. Leaving it out of the mean let a scan that linked 0 of 31 imports
131
+ * read 82%, because no call could even be attempted across the gap.
132
+ */
133
+ const imports = result.crossFile.importEdges + result.crossFile.importsUnresolved;
134
+ if (imports > 0) {
135
+ const rate = result.crossFile.importEdges / imports;
136
+ parts.push(rate);
137
+ working.push(`local imports resolved: ${result.crossFile.importEdges}/${imports} = ${Math.round(rate * 100)}%`);
138
+ }
139
+ const attempts = result.crossFile.resolved + result.crossFile.ambiguous;
140
+ if (attempts > 0) {
141
+ const rate = result.crossFile.resolved / attempts;
142
+ parts.push(rate);
143
+ working.push(`cross-file calls followed: ${result.crossFile.resolved}/${attempts} = ${Math.round(rate * 100)}%`);
144
+ }
145
+ else {
146
+ working.push('cross-file calls followed: none attempted, so not counted');
147
+ }
148
+ }
149
+ else {
150
+ working.push('cross-file resolution was OFF, so it is not counted here');
151
+ }
152
+ // Stated first, because it is the denominator everything else divides into.
153
+ // "94% of the analysis completed" is not a useful sentence when the analysis
154
+ // had nothing to run on.
155
+ working.unshift(`code constructs examined: ${result.stats.shapesExamined}`);
156
+ const value = parts.length > 0 ? Math.round((parts.reduce((a, b) => a + b, 0) / parts.length) * 100) : 0;
157
+ if (parts.length > 1)
158
+ working.push(`mean of ${parts.length} measured ratios = ${value}%`);
159
+ return {
160
+ id: 'analysis-coverage',
161
+ label: 'Analysis coverage',
162
+ value,
163
+ max: 100,
164
+ unit: '%',
165
+ meaning: examinedNothing
166
+ ? 'Nothing in this scan could be analysed - not one statement, call or ' +
167
+ 'assignment reached a rule. There is no coverage figure to give, because ' +
168
+ 'there was nothing to cover. This is not a clean result; it is no result.'
169
+ : 'How much of the analysis actually completed. This is not a security score - ' +
170
+ 'it is how much weight the other two dials can carry. Low coverage with no ' +
171
+ 'findings means we barely looked, not that the code is fine.',
172
+ working,
173
+ rawTotal: value,
174
+ capped: false,
175
+ noReading: examinedNothing,
176
+ // A ratio has no composition to show. Empty rather than faked.
177
+ segments: [],
178
+ };
179
+ }
180
+ /**
181
+ * Build all three readings.
182
+ *
183
+ * `filesFound` comes from the walker (Node) or the drop handler (browser), so
184
+ * it is passed in rather than read off the result - the analyzer only ever
185
+ * sees the files that made it that far.
186
+ */
187
+ export function scoreAnalysis(result, filesFound = 0) {
188
+ const verified = result.findings.filter((f) => f.confidence === 'flow-verified');
189
+ const signature = result.findings.filter((f) => f.confidence === 'signature-based');
190
+ // Not one statement, call or assignment reached a rule. Parsing a file is not
191
+ // the same as analysing it: a file of whitespace parses perfectly.
192
+ const examinedNothing = result.stats.shapesExamined === 0;
193
+ const gauges = [
194
+ severityGauge('verified-exposure', 'Verified exposure', 'Severity weight of findings where attacker data was TRACED into the sink. ' +
195
+ 'Every point here is backed by a printed path. Zero means no flow was proven - ' +
196
+ 'it does not mean none exists.', verified, examinedNothing),
197
+ severityGauge('unverified-surface', 'Unverified surface', 'The same weights over findings we only pattern-matched. Deliberately a ' +
198
+ 'separate needle: these may be real or may be false positives, and averaging ' +
199
+ 'them into the proven ones would hide which is which.', signature, examinedNothing),
200
+ coverageGauge(result, filesFound, examinedNothing),
201
+ ];
202
+ // These are SCAN-WIDE counts, not a tally of what appears in the findings
203
+ // above. A trace can pass through an unmodelled function and then never reach
204
+ // a sink, so this number is legitimately larger than the unmodelled hops you
205
+ // can see in the printed paths. Saying "3" while the paths show 2 looks like
206
+ // an error until the label admits which population it is counting.
207
+ const blindSpots = [
208
+ {
209
+ label: 'expressions too deeply nested to walk (generated or minified code)',
210
+ count: result.traceLimits.astTruncations,
211
+ },
212
+ { label: 'call chains cut at the depth limit', count: result.traceLimits.depthTruncations },
213
+ { label: 'recursive calls not re-entered', count: result.traceLimits.recursionStops },
214
+ {
215
+ label: 'unmodelled functions stepped through (scan-wide, not only on reported paths)',
216
+ count: result.traceLimits.unmodelledHops,
217
+ },
218
+ {
219
+ label: 'cross-file calls declined as ambiguous',
220
+ count: result.crossFile.enabled ? result.crossFile.ambiguous : 0,
221
+ },
222
+ { label: 'files that did not fully parse', count: result.parseProblems.length },
223
+ ].filter((spot) => spot.count > 0);
224
+ return {
225
+ gauges,
226
+ examinedNothing,
227
+ shapesExamined: result.stats.shapesExamined,
228
+ blindSpots,
229
+ sourcesFound: result.traceLimits.sourcesFound,
230
+ };
231
+ }
232
+ //# sourceMappingURL=score.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score.js","sourceRoot":"","sources":["../../../src/core/score.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { Finding } from '../core/finding.js';
2
+ export declare const ZAP_RULE: Record<string, string>;
3
+ export interface Target {
4
+ id: string;
5
+ rule: string;
6
+ zapRule: string;
7
+ file: string;
8
+ url: string;
9
+ cookie: string;
10
+ from: string;
11
+ param: string;
12
+ trigger?: string;
13
+ untestable?: string;
14
+ }
15
+ export type Fields = string | {
16
+ untestable: string;
17
+ };
18
+ export interface UrlConfig {
19
+ base: string;
20
+ root: string;
21
+ rules: [string, string, string?, Fields?][];
22
+ }
23
+ export interface BuiltTargets {
24
+ targets: Target[];
25
+ skipped: number;
26
+ noUrl: number;
27
+ }
28
+ export declare function buildTargets(report: {
29
+ findings: Finding[];
30
+ }, config: UrlConfig): BuiltTargets;
@@ -0,0 +1,79 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ export const ZAP_RULE = {
5
+ 'sql-injection': '40018',
6
+ 'xss': '40012',
7
+ 'command-injection': '90020',
8
+ 'code-injection': '90019',
9
+ 'ssrf': '40046',
10
+ 'path-traversal': '6',
11
+ };
12
+ // No matching rule means no URL: a file that is only included by other files has no address of its own.
13
+ function toAddress(config, file) {
14
+ const relative = path.relative(config.root, file).replaceAll('\\', '/');
15
+ for (const [pattern, url, cookie, fields] of config.rules) {
16
+ const rule = new RegExp(pattern);
17
+ if (rule.test(relative)) {
18
+ return {
19
+ url: new URL(relative.replace(rule, url), config.base).href,
20
+ cookie: cookie === undefined ? '' : relative.replace(rule, cookie),
21
+ ...(typeof fields === 'string' ? { trigger: relative.replace(rule, fields) } : {}),
22
+ ...(typeof fields === 'object' ? { untestable: fields.untestable } : {}),
23
+ };
24
+ }
25
+ }
26
+ return undefined;
27
+ }
28
+ // Defuse's proven findings -> the list of addresses ZAP should try. A finding is a target only
29
+ // when it is flow-verified, has a ZAP rule, names a request input, and its file has a URL.
30
+ export function buildTargets(report, config) {
31
+ const targets = [];
32
+ let skipped = 0;
33
+ let noUrl = 0;
34
+ for (const f of report.findings) {
35
+ if (f.confidence !== 'flow-verified')
36
+ continue;
37
+ const zapRule = ZAP_RULE[f.ruleId];
38
+ const source = f.flowPath.find((step) => step.kind === 'source');
39
+ const match = source && /\$_(GET|POST|REQUEST|COOKIE)\[\s*['"]([^'"]+)['"]\s*\]/.exec(source.location.snippet);
40
+ if (zapRule === undefined || !match) {
41
+ skipped++;
42
+ continue;
43
+ }
44
+ const address = toAddress(config, f.location.file);
45
+ if (address === undefined) {
46
+ noUrl++;
47
+ continue;
48
+ }
49
+ targets.push({
50
+ id: `${f.ruleId}:${f.location.file}:${f.location.startLine}`,
51
+ rule: f.ruleId,
52
+ zapRule,
53
+ file: f.location.file,
54
+ url: address.url,
55
+ cookie: address.cookie,
56
+ from: match[1] ?? '',
57
+ param: match[2] ?? '',
58
+ ...(address.trigger !== undefined ? { trigger: address.trigger } : {}),
59
+ ...(address.untestable !== undefined ? { untestable: address.untestable } : {}),
60
+ });
61
+ }
62
+ return { targets, skipped, noUrl };
63
+ }
64
+ function main() {
65
+ const [reportPath, configPath] = process.argv.slice(2);
66
+ if (reportPath === undefined || configPath === undefined) {
67
+ console.error('usage: node dist/src/dast/targets.js report.json urls.json');
68
+ process.exit(1);
69
+ }
70
+ const report = JSON.parse(readFileSync(reportPath, 'utf8'));
71
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
72
+ const { targets, skipped, noUrl } = buildTargets(report, config);
73
+ console.log(JSON.stringify(targets, null, 2));
74
+ console.error(`${targets.length} targets; ${skipped} proven findings skipped (no ZAP rule or no named request input); ${noUrl} skipped (no URL rule matched the file)`);
75
+ }
76
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
77
+ main();
78
+ }
79
+ //# sourceMappingURL=targets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"targets.js","sourceRoot":"","sources":["../../../src/dast/targets.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import { type Target } from './targets.js';
2
+ /**
3
+ * STEP TWO OF THE DAST ADD-ON: hand the targets to ZAP, put each verdict next to
4
+ * the finding it came from.
5
+ *
6
+ * targets.ts turned Defuse's proven (flow-verified) findings into a list of
7
+ * addresses to probe. This file drives OWASP ZAP's active scanner against each
8
+ * one, restricted to the single ZAP rule that matches the finding's category,
9
+ * and reports ONE verdict per target:
10
+ *
11
+ * confirmed ZAP reproduced the bug at that URL with its own payloads.
12
+ * not-confirmed ZAP ran the rule and raised nothing. THE FINDING STANDS.
13
+ * could-not-test ZAP never got to render a verdict (input isn't a request
14
+ * parameter, the scan errored, or it timed out).
15
+ *
16
+ * The honesty rule from finding.ts carries over: not-confirmed is NOT cleared.
17
+ * A dynamic scanner that can't reach a code path proves nothing about it, so a
18
+ * finding Defuse traced by data flow keeps its status unless ZAP positively
19
+ * reproduces it. ZAP only ever ADDS confidence; it never subtracts a finding.
20
+ *
21
+ * Every target appears in the output exactly once. The run asserts
22
+ * results.length === targets.length so nothing is dropped silently.
23
+ */
24
+ type Verdict = 'confirmed' | 'not-confirmed' | 'could-not-test';
25
+ interface Result {
26
+ id: string;
27
+ rule: string;
28
+ zapRule: string;
29
+ url: string;
30
+ from: string;
31
+ param: string;
32
+ zap: Verdict;
33
+ detail: string;
34
+ }
35
+ interface Outcome {
36
+ zap: Verdict;
37
+ detail: string;
38
+ }
39
+ export declare function groupKey(t: Target): string;
40
+ export declare function classify(zapRule: string, alertPluginIds: string[]): Verdict;
41
+ export declare function joinResults(targets: Target[], byKey: Map<string, Outcome>): Result[];
42
+ export {};
@@ -0,0 +1,184 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { pathToFileURL } from 'node:url';
3
+ import { buildTargets } from './targets.js';
4
+ // Which HTTP methods carry the tainted input. $_REQUEST is GET or POST, and a page
5
+ // often guards one while reading the other ($_REQUEST['ip'] behind an isset($_POST['Submit'])),
6
+ // so a REQUEST source has to be tried both ways. A COOKIE source is not a request
7
+ // parameter this harness can mutate (the Cookie header is pinned for auth), so it is
8
+ // reported could-not-test, never not-confirmed.
9
+ function methods(from) {
10
+ if (from === 'POST')
11
+ return ['POST'];
12
+ if (from === 'GET')
13
+ return ['GET'];
14
+ if (from === 'REQUEST')
15
+ return ['GET', 'POST'];
16
+ return [];
17
+ }
18
+ // One scan covers every target that attacks the same spot the same way.
19
+ export function groupKey(t) {
20
+ return [t.zapRule, t.from, t.url, t.param, t.cookie, t.trigger ?? '', t.untestable ?? ''].join('|');
21
+ }
22
+ // confirmed iff ZAP raised at least one alert of the finding's own rule.
23
+ export function classify(zapRule, alertPluginIds) {
24
+ return alertPluginIds.includes(zapRule) ? 'confirmed' : 'not-confirmed';
25
+ }
26
+ // Put each ZAP outcome next to its target by id. A target with no outcome (a
27
+ // group that never ran) becomes could-not-test, never a dropped row.
28
+ export function joinResults(targets, byKey) {
29
+ return targets.map((t) => {
30
+ const outcome = byKey.get(groupKey(t)) ?? { zap: 'could-not-test', detail: 'no scan ran for this target' };
31
+ return { id: t.id, rule: t.rule, zapRule: t.zapRule, url: t.url, from: t.from, param: t.param, ...outcome };
32
+ });
33
+ }
34
+ const ZAP = process.env.ZAP ?? 'http://localhost:8080';
35
+ const AUTH = process.env.DAST_COOKIE ?? ''; // e.g. "PHPSESSID=abc"; combined with each target's security cookie
36
+ const POLL_MS = 3000;
37
+ const SCAN_TIMEOUT_MS = Number(process.env.DAST_SCAN_TIMEOUT_MS ?? 300_000);
38
+ const RULE_DESC = 'defuse-dast-cookie';
39
+ async function zap(apiPath, params = {}) {
40
+ const url = new URL(`/JSON/${apiPath}/`, ZAP);
41
+ for (const [k, v] of Object.entries(params))
42
+ url.searchParams.set(k, v);
43
+ const res = await fetch(url);
44
+ const body = (await res.json());
45
+ if (typeof body.code === 'string' && typeof body.message === 'string') {
46
+ throw new Error(`ZAP ${apiPath}: ${body.message} (${body.code})`);
47
+ }
48
+ return body;
49
+ }
50
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
51
+ async function setCookie(cookie) {
52
+ await zap('replacer/action/removeRule', { description: RULE_DESC }).catch(() => undefined);
53
+ if (cookie === '')
54
+ return;
55
+ await zap('replacer/action/addRule', {
56
+ description: RULE_DESC,
57
+ enabled: 'true',
58
+ matchType: 'REQ_HEADER',
59
+ matchString: 'Cookie',
60
+ matchRegex: 'false',
61
+ replacement: cookie,
62
+ });
63
+ }
64
+ // One method against one target. confirmed / not-confirmed come from ZAP; a thrown
65
+ // ZAP error becomes could-not-test.
66
+ async function scanOnce(t, verb, trigger) {
67
+ try {
68
+ const cookie = [AUTH, t.cookie].filter(Boolean).join('; ');
69
+ await setCookie(cookie);
70
+ const seedParams = [`${t.param}=test`, trigger].filter(Boolean).join('&');
71
+ // Seed the request so the URL is in ZAP's tree (the scan errors otherwise) and so
72
+ // the vulnerable branch actually runs before the scanner starts mutating the param.
73
+ let scanUrl = t.url;
74
+ let postData = '';
75
+ if (verb === 'GET') {
76
+ scanUrl = `${t.url}?${seedParams}`;
77
+ await zap('core/action/accessUrl', { url: scanUrl, followRedirects: 'false' });
78
+ }
79
+ else {
80
+ postData = seedParams;
81
+ const { pathname, host } = new URL(t.url);
82
+ const req = `POST ${pathname} HTTP/1.1\r\n` +
83
+ `Host: ${host}\r\n` +
84
+ (cookie ? `Cookie: ${cookie}\r\n` : '') +
85
+ `Content-Type: application/x-www-form-urlencoded\r\n` +
86
+ `Content-Length: ${Buffer.byteLength(postData)}\r\n\r\n${postData}`;
87
+ await zap('core/action/sendRequest', { request: req, followRedirects: 'false' });
88
+ }
89
+ // Only the finding's own rule runs. The policy is global state, so groups go
90
+ // one at a time. ponytail: sequential scans, parallelise per-rule policies if it's ever too slow.
91
+ await zap('ascan/action/disableAllScanners', { scanPolicyName: '' });
92
+ await zap('ascan/action/enableScanners', { ids: t.zapRule, scanPolicyName: '' });
93
+ const started = await zap('ascan/action/scan', {
94
+ url: scanUrl,
95
+ method: verb,
96
+ postData,
97
+ recurse: 'false',
98
+ inScopeOnly: 'false',
99
+ });
100
+ const scanId = String(started.scan);
101
+ const deadline = Date.now() + SCAN_TIMEOUT_MS;
102
+ for (;;) {
103
+ const status = String((await zap('ascan/view/status', { scanId })).status);
104
+ if (status === '100')
105
+ break;
106
+ if (Date.now() > deadline) {
107
+ await zap('ascan/action/stop', { scanId }).catch(() => undefined);
108
+ return { zap: 'could-not-test', detail: `scan timed out after ${SCAN_TIMEOUT_MS / 1000}s` };
109
+ }
110
+ await sleep(POLL_MS);
111
+ }
112
+ const alertIds = (await zap('ascan/view/alertsIds', { scanId })).alertsIds;
113
+ const pluginIds = [];
114
+ for (const id of alertIds) {
115
+ const alert = (await zap('core/view/alert', { id })).alert;
116
+ if (alert?.pluginId !== undefined)
117
+ pluginIds.push(String(alert.pluginId));
118
+ }
119
+ const verdict = classify(t.zapRule, pluginIds);
120
+ const hits = pluginIds.filter((p) => p === t.zapRule).length;
121
+ return {
122
+ zap: verdict,
123
+ detail: verdict === 'confirmed' ? `ZAP rule ${t.zapRule} raised ${hits} alert(s) via ${verb}` : `ZAP rule ${t.zapRule} ran via ${verb} and raised none`,
124
+ };
125
+ }
126
+ catch (e) {
127
+ return { zap: 'could-not-test', detail: `${verb}: ${e.message}` };
128
+ }
129
+ }
130
+ // A target may need more than one method (REQUEST). confirmed wins; otherwise
131
+ // not-confirmed if any method actually ran; could-not-test only if none could.
132
+ async function scanGroup(t, trigger) {
133
+ // Not "not-confirmed": that would say ZAP tried and failed, when it never got in.
134
+ if (t.untestable !== undefined)
135
+ return { zap: 'could-not-test', detail: t.untestable };
136
+ const verbs = methods(t.from);
137
+ if (verbs.length === 0) {
138
+ return { zap: 'could-not-test', detail: `input comes from a ${t.from.toLowerCase()}, not a request parameter this scan can mutate` };
139
+ }
140
+ const outcomes = [];
141
+ for (const verb of verbs) {
142
+ const outcome = await scanOnce(t, verb, trigger);
143
+ if (outcome.zap === 'confirmed')
144
+ return outcome;
145
+ outcomes.push(outcome);
146
+ }
147
+ return outcomes.find((o) => o.zap === 'not-confirmed') ?? outcomes[0];
148
+ }
149
+ async function main() {
150
+ const [reportPath, configPath] = process.argv.slice(2);
151
+ if (reportPath === undefined || configPath === undefined) {
152
+ console.error('usage: DAST_COOKIE=PHPSESSID=... node dist/src/dast/zap.js report.json urls.json');
153
+ process.exit(1);
154
+ }
155
+ const report = JSON.parse(readFileSync(reportPath, 'utf8'));
156
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
157
+ const { targets, skipped, noUrl } = buildTargets(report, config);
158
+ const trigger = config.trigger ?? '';
159
+ // One scan per group; its outcome fills every target in the group.
160
+ const byKey = new Map();
161
+ let done = 0;
162
+ for (const t of targets) {
163
+ const key = groupKey(t);
164
+ if (byKey.has(key))
165
+ continue;
166
+ process.stderr.write(`[${++done}] ${t.rule} ${t.from} ${t.url} (${t.param}) ... `);
167
+ const outcome = await scanGroup(t, t.trigger ?? trigger);
168
+ byKey.set(key, outcome);
169
+ process.stderr.write(`${outcome.zap}\n`);
170
+ }
171
+ const results = joinResults(targets, byKey);
172
+ if (results.length !== targets.length) {
173
+ throw new Error(`dropped targets: ${targets.length} in, ${results.length} out`);
174
+ }
175
+ console.log(JSON.stringify(results, null, 2));
176
+ const n = (v) => results.filter((r) => r.zap === v).length;
177
+ console.error(`\n${results.length} findings tested: ${n('confirmed')} confirmed, ${n('not-confirmed')} not confirmed, ${n('could-not-test')} could not test.` +
178
+ `\nBefore ZAP: ${skipped} proven findings had no ZAP rule or named input, ${noUrl} had no URL.` +
179
+ `\nA not-confirmed finding stays a finding: ZAP reproducing nothing does not clear what Defuse traced.`);
180
+ }
181
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
182
+ void main();
183
+ }
184
+ //# sourceMappingURL=zap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zap.js","sourceRoot":"","sources":["../../../src/dast/zap.ts"],"names":[],"mappings":"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"}