@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,265 @@
1
+ /**
2
+ * THE TAINT TRACER
3
+ * ================
4
+ *
5
+ * This is the engine that turns "signature-based" into "flow-verified".
6
+ *
7
+ * HOW IT WORKS, in one paragraph:
8
+ * We read each function body top to bottom, keeping a notebook of which
9
+ * variables currently hold dirty data. When a line assigns something, we work
10
+ * out whether the right-hand side is dirty and write that in the notebook. When
11
+ * a line calls something dangerous, we look up its arguments in the notebook.
12
+ * If a dirty value reaches a sink and no sanitiser washed it on the way, we can
13
+ * finally say the thing a pattern match never could: *this specific value, from
14
+ * this specific source, reaches this specific sink.* And we can show every hop.
15
+ *
16
+ * WHAT IT DELIBERATELY DOES NOT DO - each of these is a documented gap, not an
17
+ * oversight, and each is printed in the scan report:
18
+ *
19
+ * 1. BOUNDED REACH. Functions defined in this file are followed, and so are
20
+ * functions in another file when a relative import resolves to it. What
21
+ * still ends a trace: a call into a third-party package, a dynamic or
22
+ * aliased import, a name several modules define (we decline rather than
23
+ * guess), and any chain deeper than four calls. A cut at that depth, or at
24
+ * a call that looks recursive, does NOT end the value: it is carried past
25
+ * the unread call exactly as in 3 below, with the reason for the stop
26
+ * written into the path.
27
+ *
28
+ * 2. STATEMENT ORDER, NOT BRANCH LOGIC. We read lines in order and ignore
29
+ * whether an `if` is true. So a value cleaned only inside `if (isAdmin)`
30
+ * is treated as cleaned afterwards. That direction of error loses findings
31
+ * rather than inventing them, which is the right way to be wrong.
32
+ *
33
+ * 3. UNKNOWN FUNCTIONS CARRY TAINT, AND SAY SO. `foo(dirty)` keeps the taint
34
+ * and records the hop as UNMODELLED, naming `foo` on the finding. Treating
35
+ * unknowns as clean was tried first and produced ZERO verified flows across
36
+ * 2,766 Java files, every one a known vulnerability - so the quiet option
37
+ * was not the honest one. This is the weakest link in any path we print,
38
+ * which is exactly why it is printed rather than smoothed over.
39
+ *
40
+ * 4. CONTAINERS ARE TAINTED WHOLE. `list.add(dirty)` dirties the list, not
41
+ * the slot - so a clean element read back out of that list is treated as
42
+ * dirty too. Following values into objects at all requires this trade; the
43
+ * alternative was the previous behaviour, which was to lose the value
44
+ * entirely the moment it entered a StringBuilder or a List.
45
+ *
46
+ * 5. NO LOOP FIXPOINT. A variable that becomes dirty at the bottom of a loop
47
+ * and is used at the top is missed on the first pass. Real analysers
48
+ * iterate to a fixpoint; we do not, yet.
49
+ *
50
+ * Every one of those makes the tool MISS things (false negatives). None of them
51
+ * makes it invent things. That asymmetry is deliberate: a flow-verified label is
52
+ * a promise, and a promise that is sometimes wrong is worth less than no promise
53
+ * at all.
54
+ */
55
+ import type { Node } from 'web-tree-sitter';
56
+ import type { FlowStep } from '../core/finding.js';
57
+ import type { LanguageId } from '../parse/languages.js';
58
+ import type { ParsedFile } from '../parse/parser.js';
59
+ import { type SinkKind, type TaintDictionary } from './types.js';
60
+ /**
61
+ * A flow that reached a sink but was correctly sanitised on the way.
62
+ * Not a vulnerability - evidence that this line is fine.
63
+ */
64
+ export interface SanitizedFlow {
65
+ readonly ruleId: string;
66
+ readonly kind: SinkKind;
67
+ readonly sinkNode: Node;
68
+ /** The file the sink is in - may not be the file being scanned. */
69
+ readonly filePath: string;
70
+ /**
71
+ * What made it clean, in words, with the line it happened on - the receipt.
72
+ * See cleanReason: a line printed as "checked and cleared" has to say what
73
+ * checked it.
74
+ */
75
+ readonly reason: string;
76
+ /**
77
+ * Functions Defuse does not model that the cleaned value passed through
78
+ * AFTER it was cleaned. Each was assumed to hand back what it was given; if
79
+ * one undoes the cleaning, the receipt is wrong, and it says so up front.
80
+ */
81
+ readonly assumes?: readonly string[];
82
+ }
83
+ /** One confirmed source-to-sink flow, ready to become a finding. */
84
+ export interface FlowResult {
85
+ readonly ruleId: string;
86
+ readonly kind: SinkKind;
87
+ /** The exact tainted expression - what the finding points at. */
88
+ readonly node: Node;
89
+ /** The whole sink call or assignment - used to match up with signature findings. */
90
+ readonly sinkNode: Node;
91
+ readonly path: readonly FlowStep[];
92
+ readonly origin: string;
93
+ readonly sinkDescription: string;
94
+ /** Functions on the path that we do not model. Named on the finding. */
95
+ readonly unknownHops: readonly string[];
96
+ /**
97
+ * The value reached this sink only because a clean write was skipped for
98
+ * being conditional. The finding is still REPORTED - a missed bug is the one
99
+ * failure this scanner cannot see in itself - but it is reported as a guess,
100
+ * because a branch was assumed rather than followed. See branchAssumed.
101
+ */
102
+ readonly branchAssumed?: boolean;
103
+ /**
104
+ * The value reached this sink out of a collection that took several elements,
105
+ * so which one came back is a guess. Reported, not certified. See
106
+ * containerGuess.
107
+ */
108
+ readonly containerGuess?: boolean;
109
+ /**
110
+ * The value went through escaping that only protects inside quotes, and was
111
+ * put into the query somewhere this engine cannot read, so whether it is
112
+ * quoted is unknown. Reported, not certified - and not cleared either.
113
+ */
114
+ readonly quoteUnknown?: boolean;
115
+ /**
116
+ * An SSRF flow whose request cannot go where the attacker chooses: the value
117
+ * sits after a fixed start of the address (`path`, with that start), or the
118
+ * code runs in a browser, so the request is the user's own (`browser`).
119
+ * Reported as a guess with the reason, never as proven SSRF and never cleared.
120
+ */
121
+ readonly fixedDestination?: {
122
+ readonly kind: 'path' | 'browser';
123
+ readonly before: string;
124
+ } | undefined;
125
+ /**
126
+ * A path-traversal flow whose value comes only from the program's own
127
+ * command line, environment or terminal. Reported as a guess with the
128
+ * reason, never as proven: the person who chose the path can read the file.
129
+ */
130
+ readonly localSource?: boolean | undefined;
131
+ /** The file the sink is in - may not be the file being scanned. */
132
+ readonly filePath: string;
133
+ }
134
+ /** How a clean part of a string is written in a urlPrefix. */
135
+ export declare const CLEAN_PART = "\u0001";
136
+ /**
137
+ * Does the text before a value pin the destination of the address it is part
138
+ * of? Only when a path, query or fragment has already begun - after a host
139
+ * (`https://api.example.com/`), after a clean base (`${base}/`), or in a
140
+ * relative path (`./`, `../`, `/api/`, `docs/`).
141
+ *
142
+ * NOT pinned, and each is a real SSRF shape: nothing before the value; a
143
+ * scheme with the host still open (`https://`); a host with no slash after it
144
+ * (`https://api.example.com` + `@evil.example`); a clean base with nothing
145
+ * after it, which may itself end without a slash; `https:///`, where URL
146
+ * parsers skip the extra slash and the value becomes the host.
147
+ *
148
+ * One shape it cannot see: a clean base holding only a scheme with no slashes
149
+ * (`https:`) followed by `/` and the value - parsers read `https:/evil` as
150
+ * host `evil`. A configuration value shaped like that is rare enough to
151
+ * accept, and it is written down here rather than discovered later.
152
+ */
153
+ export declare function destinationPinned(before: string): boolean;
154
+ /** A function we can follow a value into. */
155
+ export interface LocalFunction {
156
+ readonly name: string;
157
+ readonly params: readonly string[];
158
+ readonly body: Node;
159
+ /**
160
+ * How many arguments a call to this function may pass: fewer than minArgs or
161
+ * more than maxArgs and it cannot be this function. Recorded at indexing
162
+ * time, while the tree is hot - a cross-file target's tree may be evicted by
163
+ * the time a call to it is resolved. See ARITY_ENFORCED.
164
+ */
165
+ readonly minArgs?: number | undefined;
166
+ readonly maxArgs?: number | undefined;
167
+ }
168
+ /**
169
+ * Index every function in a tree by name. Exported because the cross-file
170
+ * resolver needs exactly the same view of another file that the tracer has of
171
+ * this one - if the two disagreed about what counts as a function, a value
172
+ * could be followed into something that is not there.
173
+ */
174
+ export declare function collectFunctions(root: Node, language: LanguageId): Map<string, LocalFunction>;
175
+ /**
176
+ * How the tracer asks "is there a function called X in a file this one imports?"
177
+ * Implemented by src/taint/project.ts; absent when scanning a single file.
178
+ */
179
+ export interface CrossFileResolver {
180
+ resolve(fromPath: string, name: string): {
181
+ readonly path: string;
182
+ readonly fn: LocalFunction;
183
+ } | null;
184
+ /** Called when several files export that name and we refuse to guess. */
185
+ noteAmbiguous(fromPath: string, name: string, candidates: number): void;
186
+ }
187
+ /**
188
+ * Where this trace RAN OUT, counted.
189
+ *
190
+ * The engine has always documented these limits in prose - "call chains deeper
191
+ * than four functions are not followed" sits in the capability block on every
192
+ * scan. But a general disclaimer and a measurement are different products. The
193
+ * disclaimer says the gap could exist; the count says it happened 38 times in
194
+ * YOUR code, which is the version a person can act on.
195
+ *
196
+ * A comment in this file used to claim these were "recorded as misses". They
197
+ * were not - they returned null and vanished. Now the claim is true.
198
+ */
199
+ export interface TraceLimits {
200
+ /** Calls not followed because the chain already ran four deep. */
201
+ readonly depthTruncations: number;
202
+ /** Calls not re-entered because the function was already on the stack. */
203
+ readonly recursionStops: number;
204
+ /** Hops through a function we do not model, where taint was carried anyway. */
205
+ readonly unmodelledHops: number;
206
+ /**
207
+ * How many distinct places an attacker-controlled SOURCE was recognised.
208
+ *
209
+ * This exists because of a silence. Elasticsearch - 3,999 Java files, a REST
210
+ * API, a network service - produced zero flow-verified findings, and the
211
+ * report had no way to say why. It contains 0 `@RequestParam` and 0
212
+ * `getParameter()`: it uses its own REST layer, which the dictionaries do not
213
+ * model, so the tracer never found a single source to start from.
214
+ *
215
+ * Every number in a report is downstream of this one. Zero sources means
216
+ * zero flows can exist no matter how good the tracer is, and a reader
217
+ * deserves to be told that rather than reading "0 flow-verified" as good news.
218
+ */
219
+ readonly sourcesFound: number;
220
+ /**
221
+ * Statements not walked because the syntax tree was nested deeper than the
222
+ * walk will go.
223
+ *
224
+ * Distinct from depthTruncations, which counts CALL chains. This one counts
225
+ * places where a single EXPRESSION was too deep - and it exists because a
226
+ * 25,809-file scan of the TypeScript repository died with "Maximum call stack
227
+ * size exceeded" on a file containing one expression with 1,499 `+` operators.
228
+ * The walk recursed 1,499 frames and took the process with it.
229
+ *
230
+ * A crash is the worst possible answer: it produces no report at all, so a
231
+ * whole scan is lost to one pathological file. Stopping is better - but only
232
+ * if we SAY we stopped, which is what this counter is for.
233
+ */
234
+ readonly astTruncations: number;
235
+ }
236
+ /**
237
+ * Code the tracer did not walk because a condition fixed at compile time means
238
+ * it cannot run - the dead arm of an `if`, a case a decided switch cannot
239
+ * reach. Not a finding, and not a proof about any one line: a record of a
240
+ * decision, so that a flow which disappeared because of it did not disappear
241
+ * silently.
242
+ */
243
+ export interface UnreachableRegion {
244
+ readonly node: Node;
245
+ readonly filePath: string;
246
+ readonly why: string;
247
+ }
248
+ export interface TraceOutcome {
249
+ readonly flows: readonly FlowResult[];
250
+ readonly sanitized: readonly SanitizedFlow[];
251
+ readonly unreachable: readonly UnreachableRegion[];
252
+ /** Reads from a local map resolved to the one put that supplies them - see local-map.ts. */
253
+ readonly resolvedReads: readonly UnreachableRegion[];
254
+ /** Traces ended at a read whose result is new data - see FreshResultSpec. */
255
+ readonly freshStops: readonly UnreachableRegion[];
256
+ readonly limits: TraceLimits;
257
+ }
258
+ export declare function traceFile(file: ParsedFile, language: LanguageId, resolver?: CrossFileResolver,
259
+ /**
260
+ * Absolute path -> the path a human should see. Resolution needs absolute
261
+ * paths; reports need short ones. Keeping both straight matters more once a
262
+ * single finding can name two different files.
263
+ */
264
+ toDisplay?: (absolutePath: string) => string): TraceOutcome;
265
+ export { type TaintDictionary };