@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,168 @@
1
+ /**
2
+ * VALIDATION GUARDS - cleanliness that lives in the control flow.
3
+ * ==============================================================
4
+ *
5
+ * Every sanitiser this engine knew about until now was value-in / value-out:
6
+ *
7
+ * $safe = esc_html($dirty); // takes a dirty value, returns a clean one
8
+ *
9
+ * A GUARD is a different shape. It transforms nothing and returns a boolean,
10
+ * and the fact it establishes is true only where the program can stand after
11
+ * that boolean came out a particular way:
12
+ *
13
+ * if (is_numeric($octet[0]) && is_numeric($octet[1])) {
14
+ * $target = $octet[0] . '.' . $octet[1];
15
+ * shell_exec('ping ' . $target); // <- cannot hold a metacharacter
16
+ * }
17
+ *
18
+ * That is DVWA's `exec/impossible.php`, the author's deliberate FIX, and this
19
+ * scanner reported it for weeks. It is the seventh time this project has
20
+ * punished a fix.
21
+ *
22
+ * WHY THE BAR FOR A GUARD IS SO HIGH.
23
+ *
24
+ * A sanitiser rule that fires too often costs precision. A GUARD rule that
25
+ * fires too often costs SILENCE, and silence is the failure this scanner
26
+ * cannot detect in itself - a tool that has gone quiet looks exactly like a
27
+ * tool that has been fixed. This project has over-corrected before: the
28
+ * lowercase-word rejection that dropped the corpus from 48 findings to 32, and
29
+ * the mathjs.eval regression that was caught only because dvna's flow count
30
+ * fell from 3 to 2.
31
+ *
32
+ * So a check earns a place here on exactly one condition: being on the
33
+ * passing side of it must prove what the value can contain. Three strengths:
34
+ *
35
+ * digits only digits (is_numeric, ctype_digit, preg_match('/^\d+$/'))
36
+ * word only letters, digits and underscore (ctype_alnum, /^\w+$/)
37
+ * fixed only values the programmer wrote down ($x === 'png',
38
+ * in_array($x, ['jpg', 'png'], true))
39
+ *
40
+ * and each one clears a different set of sinks - see guardClears(). A word is
41
+ * not enough for a SQL query unless it sits inside quotes (`WHERE id = id` is
42
+ * true for every row), so `word` hands SQL to the same quote check that
43
+ * escaping uses.
44
+ *
45
+ * These do NOT count, and they are the ones that look reassuring:
46
+ *
47
+ * strlen($x) < 100 a hundred characters of `; rm -rf /` fits fine
48
+ * $x !== '' proves only that something is there
49
+ * isset($x) proves only that it exists
50
+ * preg_match('/\d+/', $x) not anchored: "1; rm -rf /" contains a digit
51
+ * preg_match('/^\d+$/m') multi-line: "1\n; rm -rf /" has a line of digits
52
+ * preg_match(...) !== 0 preg_match returns FALSE on an error, and
53
+ * false !== 0 - the error would pass as a match
54
+ *
55
+ * WHERE A FACT HOLDS (guardFactsAt):
56
+ *
57
+ * if (CHECK) { here } the true branch
58
+ * if (!CHECK) { ... } else { here } the else of a negated check
59
+ * if (!CHECK) { exit; } here after a branch that always leaves
60
+ * CHECK ? here : not-here the matching arm of a ternary
61
+ *
62
+ * and only while the checked value is not written again: a write to it
63
+ * anywhere in that region - including the condition - drops the fact, because
64
+ * `if (is_numeric($x)) { $x = $_GET['y']; run($x); }` is a real injection. The
65
+ * walk also stops at a function boundary: a closure defined inside the branch
66
+ * may run long after the value changed.
67
+ *
68
+ * NOT covered, on purpose or not yet - each one only means a finding stays:
69
+ * Java (no checks recognised at all yet); Python's `abort()` and other calls
70
+ * that never return; compiled regex objects (`re.compile(...).fullmatch`,
71
+ * `const re = /.../; re.test(x)`); `switch` on the value; a check whose
72
+ * result was stored in a variable first; a closure or by-reference
73
+ * parameter that rewrites the value from inside a called function.
74
+ *
75
+ * See safe/validated-guard.php and safe/allow-list-guards.* for the shapes this
76
+ * recognises, and vulnerable/weak-guard.php and vulnerable/allow-list-fence.*
77
+ * for the fence around them.
78
+ */
79
+ import type { Node } from 'web-tree-sitter';
80
+ import type { LanguageId } from '../../parse/languages.js';
81
+ import type { SinkKind } from '../../taint/types.js';
82
+ export type GuardStrength = 'digits' | 'word' | 'fixed' | 'quoted-literal';
83
+ export interface GuardFact {
84
+ readonly strength: GuardStrength;
85
+ /** For `fixed`: the values the check lets through. */
86
+ readonly values?: readonly string[];
87
+ /** For `fixed`: the check compared a lower- or upper-cased copy. */
88
+ readonly caseFolded?: boolean;
89
+ /** A regex `$` in PHP or Python also lets one line break through after the value. */
90
+ readonly newlineTail?: boolean;
91
+ /** The check itself, for the path a reader sees. */
92
+ readonly check: Node;
93
+ }
94
+ /** Does this language have any checks this module recognises? */
95
+ export declare const guardsApply: (language: LanguageId) => boolean;
96
+ /** Which sinks a fact makes safe, and which of those only when the value lands inside quotes. */
97
+ export declare const guardClears: (fact: GuardFact) => {
98
+ readonly kinds: readonly SinkKind[];
99
+ readonly insideQuotesOnly: readonly SinkKind[];
100
+ };
101
+ /** One sentence for the path: what the check proved. */
102
+ export declare function describeFact(fact: GuardFact): string;
103
+ /**
104
+ * The same expression written two ways must compare equal. DVWA writes
105
+ * `$_REQUEST[ 'ip' ]` with spaces and reads it back the same way, but a check
106
+ * written `$_GET["id"]` and a read written `$_GET['id']` are one value too.
107
+ */
108
+ export declare function normalizeExpression(raw: string): string;
109
+ /**
110
+ * What a whole-value regular expression lets through: 'digits', 'word',
111
+ * 'fixed' (a list of literal words, `/^(asc|desc)$/`), or null when anything
112
+ * else could pass - and null is the answer for everything this does not
113
+ * understand.
114
+ *
115
+ * The body may only be digits, letters, underscores, `\d`, `\w`, classes of
116
+ * those, groups, alternation INSIDE a group, and quantifiers. Top-level `|`
117
+ * is refused because it splits the anchors: `/^1|2$/` is "starts with 1" OR
118
+ * "ends with 2", and "1; rm -rf /" starts with 1.
119
+ *
120
+ * `$` lets one trailing newline through in PHP and Python (not in
121
+ * JavaScript, and not with PHP's D flag). A lone newline at the very end
122
+ * cannot open a quote or a tag, and is accepted - but it is recorded, so a
123
+ * fixed list is not vouched for inside a URL (see guardClears).
124
+ */
125
+ export declare function regexAllowList(source: string, options: {
126
+ readonly flags?: string;
127
+ readonly implicitStart?: boolean;
128
+ readonly implicitEnd?: boolean;
129
+ readonly language: LanguageId;
130
+ }): {
131
+ readonly strength: GuardStrength;
132
+ readonly values?: readonly string[];
133
+ readonly caseFolded?: boolean;
134
+ readonly newlineTail?: boolean;
135
+ } | null;
136
+ /**
137
+ * Every value a check proves something about, at this node. Keys are
138
+ * normalised expression texts (see normalizeExpression).
139
+ */
140
+ export declare function guardFactsAt(node: Node, language: LanguageId): ReadonlyMap<string, GuardFact>;
141
+ /**
142
+ * Are ALL the spliced parts of this expression proven safe by a check?
143
+ *
144
+ * Every part must be covered. That is deliberate and it is the lesson from the
145
+ * Jenkins escaper bug, where a sanitiser covering ONE value in an expression
146
+ * was allowed to vouch for a DIFFERENT value beside it. Here, one unchecked
147
+ * part means the whole expression still reports:
148
+ *
149
+ * if (is_numeric($port)) { shell_exec('nc ' . $host . ' ' . $port); }
150
+ * ^^^^^ never checked
151
+ *
152
+ * One level of local indirection is followed, because the shape that matters
153
+ * rebuilds the value inside the branch:
154
+ *
155
+ * $target = $octet[0] . '.' . $octet[1]; <- every piece checked here
156
+ * shell_exec('ping ' . $target); <- and used here
157
+ *
158
+ * A name counts when its value here certainly comes from one assignment: the
159
+ * last plain `name = ...` before this point, sitting directly in a block that
160
+ * also holds this point, with no other write to the name from that assignment
161
+ * to the end of the statement this point is in (so a loop cannot bring a
162
+ * different value round). And every piece of that one value was checked where
163
+ * it was assigned. DVWA writes `$target` twice - once from the request, once
164
+ * rebuilt from checked octets - so "assigned once in the function" was too
165
+ * strict, and "assigned somewhere in the branch" (the first version) never
166
+ * asked whether a later write replaced it.
167
+ */
168
+ export declare function partsAreGuarded(node: Node, dynamicParts: readonly string[], language: LanguageId, kind: SinkKind): boolean;