@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,235 @@
1
+ /**
2
+ * SARIF 2.1.0 EXPORT
3
+ *
4
+ * SARIF is the OASIS format GitHub, GitLab and Azure DevOps read. Emitting it
5
+ * is what turns this from "a thing you run" into "a thing a pull request runs",
6
+ * and it is the single cheapest step on the roadmap.
7
+ *
8
+ * THE PROBLEM SARIF CREATES FOR THIS PROJECT, and how it is solved here.
9
+ *
10
+ * Every finding this tool emits is labelled signature-based (pattern matched,
11
+ * data flow NOT traced) or flow-verified (a source-to-sink path was followed
12
+ * and is printed). That distinction is the whole product. SARIF has no field
13
+ * for it: `level` carries severity, and there is nowhere that means "we did not
14
+ * check this one".
15
+ *
16
+ * So a naive export loses the differentiator at exactly the moment it matters -
17
+ * in CI, where nobody reads a terminal and a red annotation looks equally
18
+ * certain either way. Three things carry it across instead, deliberately
19
+ * redundant, because a consumer that ignores one should still meet the others:
20
+ *
21
+ * 1. `message.text` OPENS with the label. It is the one string every SARIF
22
+ * consumer displays, so the label cannot be dropped by a viewer that
23
+ * ignores everything else.
24
+ * 2. `properties.confidence` holds the machine-readable value, alongside
25
+ * `properties.limitations` - the rule's own statement of what it did not
26
+ * check.
27
+ * 3. `codeFlows` is populated for flow-verified findings ONLY. SARIF has a
28
+ * first-class representation for a traced path, and GitHub renders it as a
29
+ * clickable sequence of steps. A finding with a codeFlow is one we proved;
30
+ * a finding without one is one we guessed. That asymmetry is visible in
31
+ * the UI without reading any of our text.
32
+ *
33
+ * WHAT IS DELIBERATELY NOT CLAIMED: `securitySeverity` is emitted because
34
+ * GitHub sorts by it, but it is derived from our severity rather than from CVSS,
35
+ * and the property block says so. Inventing a CVSS vector we never computed
36
+ * would be the same class of lie the confidence labels exist to prevent.
37
+ */
38
+
39
+ import type * as Sarif from 'sarif';
40
+ import type { Finding, Severity } from '../core/finding.js';
41
+ import type { ScanResult } from '../engine/scan.js';
42
+
43
+ /**
44
+ * SARIF has four levels; we have five severities.
45
+ *
46
+ * `low` maps to note rather than warning on purpose: a note does not fail a
47
+ * default GitHub check, and after the test-file ranking work most `low`
48
+ * findings are exactly the "unescaped, but no source traced" class that should
49
+ * be visible without blocking anyone's merge.
50
+ */
51
+ const SARIF_LEVEL: Record<Severity, 'error' | 'warning' | 'note' | 'none'> = {
52
+ critical: 'error',
53
+ high: 'error',
54
+ medium: 'warning',
55
+ low: 'note',
56
+ info: 'none',
57
+ };
58
+
59
+ /** GitHub buckets these: >=9 critical, >=7 high, >=4 medium, >0 low. */
60
+ const SECURITY_SEVERITY: Record<Severity, string> = {
61
+ critical: '9.3',
62
+ high: '7.5',
63
+ medium: '5.0',
64
+ low: '3.0',
65
+ info: '0.0',
66
+ };
67
+
68
+ /** SARIF wants a URI, and a Windows path is not one. */
69
+ function toUri(filePath: string): string {
70
+ return filePath.replace(/\\/g, '/').replace(/^\.\//, '');
71
+ }
72
+
73
+ function regionOf(location: Finding['location']) {
74
+ return {
75
+ startLine: Math.max(1, location.startLine),
76
+ startColumn: Math.max(1, location.startColumn + 1), // SARIF columns are 1-based
77
+ endLine: Math.max(1, location.endLine),
78
+ endColumn: Math.max(1, location.endColumn + 1),
79
+ ...(location.snippet ? { snippet: { text: location.snippet } } : {}),
80
+ };
81
+ }
82
+
83
+ function physicalLocation(location: Finding['location']) {
84
+ return {
85
+ physicalLocation: {
86
+ artifactLocation: { uri: toUri(location.file) },
87
+ region: regionOf(location),
88
+ },
89
+ };
90
+ }
91
+
92
+ export function renderSarif(result: ScanResult, pretty = true): string {
93
+ // One rule descriptor per rule that actually fired. Emitting the whole
94
+ // catalogue would list rules this run never applied, which reads as coverage.
95
+ const seen = new Map<string, Finding>();
96
+ for (const finding of result.findings) {
97
+ if (!seen.has(finding.ruleId)) seen.set(finding.ruleId, finding);
98
+ }
99
+
100
+ const rules = [...seen.values()].map((finding) => ({
101
+ id: finding.ruleId,
102
+ name: finding.ruleName,
103
+ shortDescription: { text: finding.ruleName },
104
+ fullDescription: {
105
+ // The limitations text travels with the rule, so a consumer reading the
106
+ // rule metadata meets the caveats without opening our JSON report.
107
+ text: finding.limitations,
108
+ },
109
+ help: {
110
+ text: `${finding.ruleName}\n\n${finding.limitations}`,
111
+ markdown: `**${finding.ruleName}**\n\n${finding.limitations}`,
112
+ },
113
+ properties: {
114
+ tags: ['security', finding.cwe, finding.owasp].filter(Boolean),
115
+ 'security-severity': SECURITY_SEVERITY[finding.severity],
116
+ precision: 'medium',
117
+ },
118
+ }));
119
+
120
+ const results = result.findings.map((finding) => {
121
+ const label = finding.verified ? 'flow-verified' : 'signature-based, UNVERIFIED';
122
+ const base = {
123
+ ruleId: finding.ruleId,
124
+ level: SARIF_LEVEL[finding.severity],
125
+ message: { text: `[${label}] ${finding.message}\n\n${finding.reasoning}` },
126
+ locations: [physicalLocation(finding.location)],
127
+ properties: {
128
+ confidence: finding.confidence,
129
+ severity: finding.severity,
130
+ cwe: finding.cwe,
131
+ owasp: finding.owasp,
132
+ limitations: finding.limitations,
133
+ 'security-severity': SECURITY_SEVERITY[finding.severity],
134
+ securitySeverityIsDerived:
135
+ 'Derived from our own severity, not from a CVSS vector. No CVSS score was computed.',
136
+ },
137
+ };
138
+
139
+ // A traced path becomes a real SARIF codeFlow. Signature findings get none,
140
+ // which is the honest shape: there is no flow to show.
141
+ if (!finding.verified || !finding.flowPath || finding.flowPath.length === 0) return base;
142
+
143
+ return {
144
+ ...base,
145
+ codeFlows: [
146
+ {
147
+ message: { text: `Traced ${finding.flowPath.length} steps from source to sink.` },
148
+ threadFlows: [
149
+ {
150
+ locations: finding.flowPath.map((step) => ({
151
+ location: {
152
+ ...physicalLocation(step.location),
153
+ message: { text: `${step.kind}: ${step.description}` },
154
+ },
155
+ // SARIF's own vocabulary for taint. A consumer that understands
156
+ // it can render source and sink differently without parsing our
157
+ // prose.
158
+ kinds:
159
+ step.kind === 'source'
160
+ ? ['taint', 'source']
161
+ : step.kind === 'sink'
162
+ ? ['taint', 'sink']
163
+ : ['taint'],
164
+ })),
165
+ },
166
+ ],
167
+ },
168
+ ],
169
+ };
170
+ });
171
+
172
+ /*
173
+ * TYPED AS `Sarif.Log`, WHICH IS THE POINT.
174
+ *
175
+ * The JSON report's own comment says emitting invalid SARIF and calling it
176
+ * SARIF would be its own small lie. A hand-rolled object literal that merely
177
+ * LOOKS like SARIF is exactly that lie waiting to happen, so the shape is
178
+ * checked by the compiler against the official 2.1.0 type definitions
179
+ * (@types/sarif, a devDependency). A misspelled property or a wrong nesting
180
+ * level fails `npm run build` rather than failing silently inside somebody's
181
+ * CI three months from now.
182
+ *
183
+ * This validates STRUCTURE, not semantics: the compiler confirms this is a
184
+ * well-formed SARIF log, not that GitHub will render it the way intended.
185
+ */
186
+ const document: Sarif.Log = {
187
+ $schema: 'https://json.schemastore.org/sarif-2.1.0.json',
188
+ version: '2.1.0',
189
+ runs: [
190
+ {
191
+ tool: {
192
+ driver: {
193
+ name: 'Defuse',
194
+ version: result.coverage.engine.version,
195
+ informationUri: 'https://github.com/',
196
+ rules,
197
+ },
198
+ },
199
+ results,
200
+ invocations: [
201
+ {
202
+ executionSuccessful: true,
203
+ // Files we could not parse are stated, not swallowed - a green CI
204
+ // run over a file nobody read is the quietest kind of false clean.
205
+ ...(result.parseProblems.length > 0
206
+ ? {
207
+ toolExecutionNotifications: result.parseProblems.map((problem) => ({
208
+ level: 'warning' as const,
209
+ message: {
210
+ text:
211
+ `${problem.file} did not parse cleanly (${problem.issues.length} issue(s)). ` +
212
+ `Findings in this file may be incomplete.`,
213
+ },
214
+ })),
215
+ }
216
+ : {}),
217
+ },
218
+ ],
219
+ properties: {
220
+ // The same honesty block the JSON report leads with. A CI consumer
221
+ // that only ever reads SARIF still gets the engine's own account of
222
+ // what it does not do.
223
+ signatureBasedFindings: result.findings.filter((f) => !f.verified).length,
224
+ flowVerifiedFindings: result.findings.filter((f) => f.verified).length,
225
+ notImplemented: result.coverage.engine.notImplemented,
226
+ meaningOfConfidence: result.coverage.engine.meaningOfConfidence,
227
+ filesParsed: result.stats.filesParsed,
228
+ filesSkipped: result.stats.filesSkipped,
229
+ },
230
+ },
231
+ ],
232
+ };
233
+
234
+ return pretty ? JSON.stringify(document, null, 2) : JSON.stringify(document);
235
+ }