@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,80 @@
1
+ /**
2
+ * THE NODE SHELL
3
+ * ==============
4
+ *
5
+ * Everything that only makes sense on a machine with a filesystem: walking a
6
+ * directory, honouring --exclude, reading bytes, and shortening paths against
7
+ * the working directory so terminal output stays readable.
8
+ *
9
+ * The analysis itself is NOT here. It moved to src/core/analyze.ts when the
10
+ * browser UI arrived, so the web version could run the same code rather than a
11
+ * lookalike. What is left is the pipeline seam:
12
+ *
13
+ * collectFiles -> read text -> analyze() -> a ScanResult
14
+ *
15
+ * If you find yourself wanting to change how a FINDING is produced, this is the
16
+ * wrong file - that belongs in core/analyze.ts, where the browser sees it too.
17
+ */
18
+ import { type AnalysisResult } from '../core/analyze.js';
19
+ import { type CoverageReport } from '../core/coverage.js';
20
+ import type { Finding } from '../core/finding.js';
21
+ import { type RuntimeCheckResult } from '../platform/runtime-version.js';
22
+ import type { DependencyReport } from '../platform/dependency-rule.js';
23
+ export type { FileParseProblem, Suppression } from '../core/analyze.js';
24
+ export interface ScanOptions {
25
+ /** Only run these rule ids. Empty = all. */
26
+ readonly only?: readonly string[];
27
+ /** Glob-ish paths to skip entirely. Reported, never silently applied. */
28
+ readonly exclude?: readonly string[];
29
+ /** Turn off cross-file resolution (faster, less memory, fewer findings). */
30
+ readonly noCrossFile?: boolean;
31
+ /** Print progress to stderr. */
32
+ readonly onProgress?: (done: number, total: number, file: string) => void;
33
+ }
34
+ export interface ScanResult {
35
+ readonly target: string;
36
+ readonly findings: readonly Finding[];
37
+ /** Lines the tracer proved safe. Reported so "clean" can mean "checked". */
38
+ readonly verifiedClean: AnalysisResult['verifiedClean'];
39
+ /** Branches and cases not followed because they cannot run, with the reason. */
40
+ readonly unreachableCode: AnalysisResult['unreachableCode'];
41
+ /** Map and list reads answered by key or position, with the write that answers each. */
42
+ readonly collectionReadsResolved: AnalysisResult['collectionReadsResolved'];
43
+ /** Calls where a trace ended because the result is new data, not the argument. */
44
+ readonly tracesStoppedAtNewData: AnalysisResult['tracesStoppedAtNewData'];
45
+ /**
46
+ * Which PHP the project declares, and what php.net's facts say about it -
47
+ * platform/runtime-version.ts. Null when --only left the check out.
48
+ */
49
+ readonly runtimeVersions: RuntimeCheckResult | null;
50
+ /**
51
+ * What `scan --deps` read and asked osv.dev. Absent when the check did not run -
52
+ * scan() never sets it; the CLI adds it, because it is the one network call.
53
+ */
54
+ readonly dependencies?: DependencyReport;
55
+ /** Per-file participation, so a file a flow passes through is never "clean". */
56
+ readonly fileRoles: AnalysisResult['fileRoles'];
57
+ /** Where the tracer ran out, counted for this scan. */
58
+ readonly traceLimits: AnalysisResult['traceLimits'];
59
+ readonly stats: AnalysisResult['stats'] & {
60
+ readonly filesFound: number;
61
+ readonly filesWithParseErrors: number;
62
+ };
63
+ readonly byLanguage: AnalysisResult['byLanguage'];
64
+ readonly parseProblems: AnalysisResult['parseProblems'];
65
+ readonly oversizedFiles: readonly string[];
66
+ /** What --exclude removed. A gap the user chose is still a gap worth printing. */
67
+ readonly excluded: {
68
+ readonly patterns: readonly string[];
69
+ readonly fileCount: number;
70
+ readonly directories: readonly string[];
71
+ };
72
+ /** Findings the code explicitly asked us to ignore. Reported, never hidden. */
73
+ readonly suppressions: AnalysisResult['suppressions'];
74
+ readonly coverage: CoverageReport;
75
+ /** How cross-file resolution went. */
76
+ readonly crossFile: AnalysisResult['crossFile'];
77
+ /** What the scan spent on syntax trees, and whether it had to spill. */
78
+ readonly treeMemory: AnalysisResult['treeMemory'];
79
+ }
80
+ export declare function scan(target: string, options?: ScanOptions): Promise<ScanResult>;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * THE NODE SHELL
3
+ * ==============
4
+ *
5
+ * Everything that only makes sense on a machine with a filesystem: walking a
6
+ * directory, honouring --exclude, reading bytes, and shortening paths against
7
+ * the working directory so terminal output stays readable.
8
+ *
9
+ * The analysis itself is NOT here. It moved to src/core/analyze.ts when the
10
+ * browser UI arrived, so the web version could run the same code rather than a
11
+ * lookalike. What is left is the pipeline seam:
12
+ *
13
+ * collectFiles -> read text -> analyze() -> a ScanResult
14
+ *
15
+ * If you find yourself wanting to change how a FINDING is produced, this is the
16
+ * wrong file - that belongs in core/analyze.ts, where the browser sees it too.
17
+ */
18
+ import { readFile } from 'node:fs/promises';
19
+ import path from 'node:path';
20
+ import { analyze } from '../core/analyze.js';
21
+ import { buildCoverageReport } from '../core/coverage.js';
22
+ import { SCANNABLE_EXTENSIONS } from '../parse/languages.js';
23
+ import { useNodeGrammars } from '../parse/node-grammars.js';
24
+ import { checkRuntimeVersions, RUNTIME_VERSION_RULE } from '../platform/runtime-version.js';
25
+ import { collectFiles } from './walk.js';
26
+ export async function scan(target, options = {}) {
27
+ // Tell the parser it is running on Node before anything loads a grammar.
28
+ // The browser worker makes the equivalent call with its own loader.
29
+ useNodeGrammars();
30
+ const walk = await collectFiles(target, {
31
+ scannable: SCANNABLE_EXTENSIONS,
32
+ exclude: options.exclude ?? [],
33
+ });
34
+ // Read the text. A file we cannot read simply never reaches the analyzer;
35
+ // it shows up as the gap between filesFound and filesParsed.
36
+ const inputs = [];
37
+ for (const filePath of walk.files) {
38
+ try {
39
+ inputs.push({ path: filePath, source: await readFile(filePath, 'utf8') });
40
+ }
41
+ catch {
42
+ // permissions, a broken symlink, a race with a build tool
43
+ }
44
+ }
45
+ /**
46
+ * DEFUSE_TREE_BUDGET_MB - how much WebAssembly heap the scan may hold in syntax
47
+ * trees before it starts freeing the least recently used one.
48
+ *
49
+ * An environment variable rather than a flag, deliberately. It exists to be
50
+ * turned down hard in testing: the interesting question about the tree store
51
+ * is not "does it work when nothing is ever evicted" but "are the findings
52
+ * still identical when almost everything is". A budget of 1 MB forces a
53
+ * re-parse for nearly every cross-file hop, which is exactly the condition
54
+ * the identity check should be run under.
55
+ *
56
+ * Anyone who genuinely needs it in production can set it, and the value used
57
+ * is printed in the report either way.
58
+ */
59
+ const budgetMb = Number(process.env['DEFUSE_TREE_BUDGET_MB']);
60
+ const treeBudget = Number.isFinite(budgetMb) && budgetMb > 0 ? Math.round(budgetMb * 1024 * 1024) : undefined;
61
+ const result = await analyze(inputs, {
62
+ ...(options.only ? { only: options.only } : {}),
63
+ ...(options.noCrossFile !== undefined ? { noCrossFile: options.noCrossFile } : {}),
64
+ ...(options.onProgress ? { onProgress: options.onProgress } : {}),
65
+ ...(treeBudget !== undefined ? { treeBudgetBytes: treeBudget } : {}),
66
+ toDisplay: (absolutePath) => path.relative(process.cwd(), absolutePath) || absolutePath,
67
+ });
68
+ /*
69
+ * THE RUNTIME VERSION CHECK reads declarations, not code, so it runs beside
70
+ * the analysis rather than inside it. Its findings join the same list - a
71
+ * PHP release with a known critical flaw belongs next to the code problems,
72
+ * not in a footnote - and they are all signature-based by construction.
73
+ */
74
+ const runtimeVersions = !options.only || options.only.length === 0 || options.only.includes(RUNTIME_VERSION_RULE.id)
75
+ ? await (async () => {
76
+ const files = [];
77
+ for (const filePath of walk.runtimeFiles) {
78
+ try {
79
+ files.push({
80
+ path: path.relative(process.cwd(), filePath) || filePath,
81
+ text: await readFile(filePath, 'utf8'),
82
+ });
83
+ }
84
+ catch {
85
+ // unreadable - same treatment as a source file
86
+ }
87
+ }
88
+ return checkRuntimeVersions(files, {
89
+ today: new Date().toISOString().slice(0, 10),
90
+ phpFilesScanned: result.byLanguage['php'] ?? 0,
91
+ });
92
+ })()
93
+ : null;
94
+ return {
95
+ target,
96
+ findings: [...result.findings, ...(runtimeVersions?.findings ?? [])],
97
+ verifiedClean: result.verifiedClean,
98
+ unreachableCode: result.unreachableCode,
99
+ collectionReadsResolved: result.collectionReadsResolved,
100
+ tracesStoppedAtNewData: result.tracesStoppedAtNewData,
101
+ runtimeVersions,
102
+ fileRoles: result.fileRoles,
103
+ traceLimits: result.traceLimits,
104
+ stats: {
105
+ ...result.stats,
106
+ filesFound: walk.files.length,
107
+ filesSkipped: walk.files.length - result.stats.filesParsed,
108
+ filesWithParseErrors: result.parseProblems.length,
109
+ },
110
+ byLanguage: result.byLanguage,
111
+ parseProblems: result.parseProblems,
112
+ oversizedFiles: walk.oversizedFiles,
113
+ excluded: {
114
+ patterns: options.exclude ?? [],
115
+ fileCount: walk.excludedCount,
116
+ directories: walk.excludedDirectories,
117
+ },
118
+ suppressions: result.suppressions,
119
+ crossFile: result.crossFile,
120
+ treeMemory: result.treeMemory,
121
+ // Rebuilt here because only the walker knows which file types it passed
122
+ // over - the analyzer never saw them.
123
+ coverage: buildCoverageReport([...walk.skippedByExtension.entries()]
124
+ .sort((a, b) => b[1] - a[1])
125
+ .slice(0, 12)
126
+ .map(([ext, count]) => `${ext} (${count} file${count === 1 ? '' : 's'})`)),
127
+ };
128
+ }
129
+ //# sourceMappingURL=scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../../src/engine/scan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * THE SHAPE LAYER
3
+ * ===============
4
+ *
5
+ * This is the translator between "five different languages" and "one set of
6
+ * rules". It uses tree-sitter's QUERY LANGUAGE, which is worth understanding
7
+ * because it is the only unfamiliar syntax in this project.
8
+ *
9
+ * A tree-sitter query is a pattern written as nested parentheses that describes
10
+ * a shape of tree, with @names marking the parts you want back:
11
+ *
12
+ * (call_expression
13
+ * function: (member_expression
14
+ * object: (_) @receiver
15
+ * property: (property_identifier) @method)
16
+ * arguments: (arguments) @args) @call
17
+ *
18
+ * Read it as: "find a call_expression; its `function` child must be a
19
+ * member_expression (that's the `a.b` shape); give me whatever is on the left
20
+ * of the dot as @receiver, the name after the dot as @method, the argument list
21
+ * as @args, and the whole call as @call."
22
+ *
23
+ * `(_)` means "any node". Field names before the colon (`function:`,
24
+ * `object:`) are the ROLE labels you saw in the AST dump. Matching on roles
25
+ * instead of child positions is what makes these patterns stable.
26
+ *
27
+ * Every language below gets its own query because the grammars use different
28
+ * node names for the same idea - JavaScript says `call_expression`, Python says
29
+ * `call`, Java says `method_invocation`. The OUTPUT is identical for all five,
30
+ * and that output is what rules see.
31
+ *
32
+ * ADDING A LANGUAGE LATER means adding two query strings to this file. No rule
33
+ * changes.
34
+ */
35
+ import type { Shape } from '../rules/contract.js';
36
+ import type { LanguageId } from '../parse/languages.js';
37
+ import { type ParsedFile } from '../parse/parser.js';
38
+ /**
39
+ * Lower one parsed file into the flat list of shapes rules will see.
40
+ * Order is source order, which makes reports read top-to-bottom like the file.
41
+ */
42
+ export declare function extractShapes(file: ParsedFile, language: LanguageId): Promise<Shape[]>;
43
+ /** Exposed so a self-check can compile every query against every grammar. */
44
+ export declare const SHAPE_QUERY_SOURCES: {
45
+ readonly calls: Record<LanguageId, string>;
46
+ readonly assignments: Record<LanguageId, string>;
47
+ };
@@ -0,0 +1,328 @@
1
+ /**
2
+ * THE SHAPE LAYER
3
+ * ===============
4
+ *
5
+ * This is the translator between "five different languages" and "one set of
6
+ * rules". It uses tree-sitter's QUERY LANGUAGE, which is worth understanding
7
+ * because it is the only unfamiliar syntax in this project.
8
+ *
9
+ * A tree-sitter query is a pattern written as nested parentheses that describes
10
+ * a shape of tree, with @names marking the parts you want back:
11
+ *
12
+ * (call_expression
13
+ * function: (member_expression
14
+ * object: (_) @receiver
15
+ * property: (property_identifier) @method)
16
+ * arguments: (arguments) @args) @call
17
+ *
18
+ * Read it as: "find a call_expression; its `function` child must be a
19
+ * member_expression (that's the `a.b` shape); give me whatever is on the left
20
+ * of the dot as @receiver, the name after the dot as @method, the argument list
21
+ * as @args, and the whole call as @call."
22
+ *
23
+ * `(_)` means "any node". Field names before the colon (`function:`,
24
+ * `object:`) are the ROLE labels you saw in the AST dump. Matching on roles
25
+ * instead of child positions is what makes these patterns stable.
26
+ *
27
+ * Every language below gets its own query because the grammars use different
28
+ * node names for the same idea - JavaScript says `call_expression`, Python says
29
+ * `call`, Java says `method_invocation`. The OUTPUT is identical for all five,
30
+ * and that output is what rules see.
31
+ *
32
+ * ADDING A LANGUAGE LATER means adding two query strings to this file. No rule
33
+ * changes.
34
+ */
35
+ import { Query } from 'web-tree-sitter';
36
+ import { getLanguage } from '../parse/parser.js';
37
+ /* ---------------------------------------------------------------------------
38
+ * CALL SITES: "something is being called with arguments"
39
+ * ------------------------------------------------------------------------ */
40
+ /** Shared by C and C++, because C++ is a superset of these two shapes. */
41
+ const C_CALL_QUERY = `
42
+ (call_expression
43
+ function: (identifier) @method
44
+ arguments: (argument_list) @args) @call
45
+ (call_expression
46
+ function: (field_expression argument: (_) @receiver field: (field_identifier) @method)
47
+ arguments: (argument_list) @args) @call
48
+ `;
49
+ const CALL_QUERIES = {
50
+ javascript: `
51
+ (call_expression
52
+ function: (member_expression object: (_) @receiver property: (property_identifier) @method)
53
+ arguments: (arguments) @args) @call
54
+ (call_expression
55
+ function: (identifier) @method
56
+ arguments: (arguments) @args) @call
57
+ (new_expression
58
+ constructor: (_) @method
59
+ arguments: (arguments) @args) @call
60
+ `,
61
+ // TypeScript's grammar is JavaScript's grammar plus types, so the call shapes
62
+ // are byte-for-byte identical. Kept as a separate entry rather than an alias
63
+ // so that a future TS-only pattern has an obvious home.
64
+ typescript: `
65
+ (call_expression
66
+ function: (member_expression object: (_) @receiver property: (property_identifier) @method)
67
+ arguments: (arguments) @args) @call
68
+ (call_expression
69
+ function: (identifier) @method
70
+ arguments: (arguments) @args) @call
71
+ (new_expression
72
+ constructor: (_) @method
73
+ arguments: (arguments) @args) @call
74
+ `,
75
+ python: `
76
+ (call
77
+ function: (attribute object: (_) @receiver attribute: (identifier) @method)
78
+ arguments: (argument_list) @args) @call
79
+ (call
80
+ function: (identifier) @method
81
+ arguments: (argument_list) @args) @call
82
+ `,
83
+ java: `
84
+ (method_invocation
85
+ object: (_) @receiver
86
+ name: (identifier) @method
87
+ arguments: (argument_list) @args) @call
88
+ (method_invocation
89
+ name: (identifier) @method
90
+ arguments: (argument_list) @args) @call
91
+ (object_creation_expression
92
+ type: (_) @method
93
+ arguments: (argument_list) @args) @call
94
+ `,
95
+ go: `
96
+ (call_expression
97
+ function: (selector_expression operand: (_) @receiver field: (field_identifier) @method)
98
+ arguments: (argument_list) @args) @call
99
+ (call_expression
100
+ function: (identifier) @method
101
+ arguments: (argument_list) @args) @call
102
+ `,
103
+ // PHP has three call shapes: a bare function, a method on an object, and a
104
+ // static method on a class. `echo` is deliberately NOT here - it is a
105
+ // statement, not a call, and the XSS rule handles it separately.
106
+ php: `
107
+ (function_call_expression
108
+ function: (name) @method
109
+ arguments: (arguments) @args) @call
110
+ (member_call_expression
111
+ object: (_) @receiver
112
+ name: (name) @method
113
+ arguments: (arguments) @args) @call
114
+ (scoped_call_expression
115
+ scope: (_) @receiver
116
+ name: (name) @method
117
+ arguments: (arguments) @args) @call
118
+ (object_creation_expression
119
+ (name) @method
120
+ (arguments) @args) @call
121
+ `,
122
+ /*
123
+ * C. Two shapes only, because C has two: a bare call, and a call through a
124
+ * struct member. Every node type and field name below was read off the real
125
+ * grammar with a query probe rather than assumed - the same discipline the
126
+ * PHP entry learned the hard way, when `if_statement` turned out to use
127
+ * `body` where every other grammar uses `consequence`.
128
+ */
129
+ c: C_CALL_QUERY,
130
+ /*
131
+ * C++ is C plus namespaces. `std::system(cmd)` parses as a
132
+ * qualified_identifier, NOT an identifier, so a query that only matched the
133
+ * C shapes would miss it - and reaching system() through <cstdlib> is the
134
+ * stylistically CORRECT way to do it in C++, which means the well-written
135
+ * code would be the code that slipped through.
136
+ */
137
+ cpp: `
138
+ ${C_CALL_QUERY}
139
+ (call_expression
140
+ function: (qualified_identifier name: (identifier) @method)
141
+ arguments: (argument_list) @args) @call
142
+ `,
143
+ };
144
+ /* ---------------------------------------------------------------------------
145
+ * ASSIGNMENTS: "a name is being bound to a value"
146
+ *
147
+ * Deliberately broad: it covers `x = v`, `const x = v`, object/dict entries
148
+ * `{ key: v }`, and keyword arguments `f(key=v)`. Hardcoded credentials hide in
149
+ * all of those, and so does `element.innerHTML = ...`.
150
+ * ------------------------------------------------------------------------ */
151
+ const JS_ASSIGN_QUERY = `
152
+ (assignment_expression left: (_) @target right: (_) @value) @assign
153
+ (variable_declarator name: (_) @target value: (_) @value) @assign
154
+ (pair key: (_) @target value: (_) @value) @assign
155
+ (augmented_assignment_expression left: (_) @target right: (_) @value) @assign
156
+ `;
157
+ const C_ASSIGN_QUERY = `
158
+ (assignment_expression left: (_) @target right: (_) @value) @assign
159
+ (init_declarator declarator: (_) @target value: (_) @value) @assign
160
+ `;
161
+ const ASSIGNMENT_QUERIES = {
162
+ php: `
163
+ (assignment_expression left: (_) @target right: (_) @value) @assign
164
+ (property_element (variable_name) @target (_) @value) @assign
165
+ (echo_statement "echo" @target (_) @value) @assign
166
+ `,
167
+ javascript: JS_ASSIGN_QUERY,
168
+ typescript: `
169
+ ${JS_ASSIGN_QUERY}
170
+ (public_field_definition name: (_) @target value: (_) @value) @assign
171
+ `,
172
+ python: `
173
+ (assignment left: (_) @target right: (_) @value) @assign
174
+ (pair key: (_) @target value: (_) @value) @assign
175
+ (keyword_argument name: (identifier) @target value: (_) @value) @assign
176
+ `,
177
+ java: `
178
+ (variable_declarator name: (identifier) @target value: (_) @value) @assign
179
+ (assignment_expression left: (_) @target right: (_) @value) @assign
180
+ `,
181
+ go: `
182
+ (short_var_declaration left: (expression_list) @target right: (expression_list) @value) @assign
183
+ (assignment_statement left: (expression_list) @target right: (expression_list) @value) @assign
184
+ (var_spec name: (identifier) @target value: (expression_list) @value) @assign
185
+ (const_spec name: (identifier) @target value: (expression_list) @value) @assign
186
+ (keyed_element (literal_element (identifier) @target) (literal_element (_) @value)) @assign
187
+ `,
188
+ /*
189
+ * C and C++ share these. `init_declarator` is the declaration-with-value
190
+ * form (`char *p = getenv(...)`), and its @target captures the DECLARATOR
191
+ * rather than a bare name - so `*name = getenv("USER")` yields a target of
192
+ * `*name`, pointer star included. The tracer strips it; see POINTER_PREFIX
193
+ * in taint/tracer.ts. Recorded here because it is the kind of detail that
194
+ * silently halves a language's coverage if nobody notices it.
195
+ */
196
+ c: C_ASSIGN_QUERY,
197
+ cpp: C_ASSIGN_QUERY,
198
+ };
199
+ /* ------------------------------------------------------------------------ */
200
+ /** Compiled queries are expensive to build and cheap to reuse. Cache per grammar. */
201
+ const queryCache = new Map();
202
+ async function compile(grammar, key, source) {
203
+ const cacheKey = `${grammar}:${key}`;
204
+ const cached = queryCache.get(cacheKey);
205
+ if (cached)
206
+ return cached;
207
+ const language = await getLanguage(grammar);
208
+ const query = new Query(language, source);
209
+ queryCache.set(cacheKey, query);
210
+ return query;
211
+ }
212
+ function collapse(text) {
213
+ return (text ?? '').replace(/\s+/g, ' ').trim();
214
+ }
215
+ /** Pull one capture out of a match. Returns null when the capture is optional. */
216
+ function captured(match, name) {
217
+ for (const capture of match.captures) {
218
+ if (capture.name === name)
219
+ return capture.node;
220
+ }
221
+ return null;
222
+ }
223
+ /**
224
+ * Argument lists contain punctuation as well as expressions. `namedChildren`
225
+ * gives us only the meaningful ones - see the "named vs anonymous" note in
226
+ * astDump.ts.
227
+ */
228
+ function argumentNodes(argsNode) {
229
+ if (!argsNode)
230
+ return [];
231
+ return argsNode.namedChildren
232
+ .filter((n) => n !== null)
233
+ // PHP wraps every argument in an `argument` node, so the expression a rule
234
+ // wants to examine sits one level down. Without this unwrap every PHP
235
+ // argument reads as the node type `argument` and no content check matches.
236
+ .map((n) => (n.type === 'argument' ? (n.namedChildren.find((c) => c !== null) ?? n) : n));
237
+ }
238
+ /**
239
+ * Go writes `a, b := f(), g()` - both sides are lists. When the lists line up
240
+ * we pair them; when they don't (a multi-return call) we bind every name to the
241
+ * single value node, which is honest enough for signature matching and is
242
+ * flagged as a known imprecision in the Go coverage notes.
243
+ */
244
+ function goPairs(target, value) {
245
+ const targets = target.type === 'expression_list' ? target.namedChildren : [target];
246
+ const values = value.type === 'expression_list' ? value.namedChildren : [value];
247
+ const pairs = [];
248
+ for (let i = 0; i < targets.length; i++) {
249
+ const t = targets[i];
250
+ if (!t)
251
+ continue;
252
+ const v = values.length === targets.length ? values[i] : values[0];
253
+ if (v)
254
+ pairs.push({ t, v });
255
+ }
256
+ return pairs;
257
+ }
258
+ /** Last dotted segment: "config.db.password" -> "password". */
259
+ function lastSegment(text) {
260
+ const cleaned = text.replace(/\[["']?|["']?\]/g, '.').replace(/\.+$/, '');
261
+ const parts = cleaned.split('.');
262
+ return (parts[parts.length - 1] ?? cleaned).trim();
263
+ }
264
+ /**
265
+ * Lower one parsed file into the flat list of shapes rules will see.
266
+ * Order is source order, which makes reports read top-to-bottom like the file.
267
+ */
268
+ export async function extractShapes(file, language) {
269
+ const shapes = [];
270
+ const seen = new Set();
271
+ const callQuery = await compile(file.grammar, 'calls', CALL_QUERIES[language]);
272
+ for (const match of callQuery.matches(file.root)) {
273
+ const call = captured(match, 'call');
274
+ const method = captured(match, 'method');
275
+ if (!call || !method)
276
+ continue;
277
+ // Java's two method_invocation patterns overlap (object is optional), so the
278
+ // same node can match twice. Dedupe by node id.
279
+ if (seen.has(call.id))
280
+ continue;
281
+ seen.add(call.id);
282
+ const receiver = captured(match, 'receiver');
283
+ const receiverText = collapse(receiver?.text);
284
+ const calleeName = collapse(method.text);
285
+ // Java's `method_invocation` has no `function` field - the receiver and the
286
+ // name are separate children - so we rebuild "receiver.name" by hand there.
287
+ const functionNode = call.childForFieldName('function') ?? call.childForFieldName('constructor');
288
+ const site = {
289
+ kind: 'call',
290
+ node: call,
291
+ calleeName,
292
+ receiverText,
293
+ calleeText: collapse(functionNode?.text ?? (receiverText ? `${receiverText}.${calleeName}` : calleeName)),
294
+ args: argumentNodes(captured(match, 'args')),
295
+ };
296
+ shapes.push(site);
297
+ }
298
+ const assignQuery = await compile(file.grammar, 'assignments', ASSIGNMENT_QUERIES[language]);
299
+ for (const match of assignQuery.matches(file.root)) {
300
+ const node = captured(match, 'assign');
301
+ const target = captured(match, 'target');
302
+ const value = captured(match, 'value');
303
+ if (!node || !target || !value)
304
+ continue;
305
+ if (seen.has(node.id))
306
+ continue;
307
+ seen.add(node.id);
308
+ const pairs = language === 'go' ? goPairs(target, value) : [{ t: target, v: value }];
309
+ for (const pair of pairs) {
310
+ const targetText = collapse(pair.t.text);
311
+ shapes.push({
312
+ kind: 'assignment',
313
+ node,
314
+ targetText,
315
+ targetName: lastSegment(targetText).replace(/^["']|["']$/g, ''),
316
+ value: pair.v,
317
+ });
318
+ }
319
+ }
320
+ shapes.sort((a, b) => a.node.startIndex - b.node.startIndex);
321
+ return shapes;
322
+ }
323
+ /** Exposed so a self-check can compile every query against every grammar. */
324
+ export const SHAPE_QUERY_SOURCES = {
325
+ calls: CALL_QUERIES,
326
+ assignments: ASSIGNMENT_QUERIES,
327
+ };
328
+ //# sourceMappingURL=shapes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shapes.js","sourceRoot":"","sources":["../../../src/engine/shapes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * FILE DISCOVERY
3
+ *
4
+ * Turning "scan this path" into "here is the list of files". Boring code, but
5
+ * two decisions in it matter:
6
+ *
7
+ * 1. We skip dependency and build directories. Scanning node_modules finds
8
+ * thousands of "vulnerabilities" in other people's code that you cannot fix
9
+ * and did not write. Dependency risk is a real and separate problem
10
+ * (Software Composition Analysis) and it deserves its own tool, not noise
11
+ * inside this one.
12
+ *
13
+ * 2. We record the extensions we skipped. That list feeds the coverage report,
14
+ * so a scan of a repo that is 70% Ruby says "we did not look at 412 .rb
15
+ * files" instead of quietly reporting "no issues found".
16
+ */
17
+ export interface WalkResult {
18
+ readonly files: readonly string[];
19
+ /** Extension -> how many files with it we passed over. Feeds the coverage report. */
20
+ readonly skippedByExtension: ReadonlyMap<string, number>;
21
+ readonly oversizedFiles: readonly string[];
22
+ /** How many files --exclude removed. Printed, never silent. */
23
+ readonly excludedCount: number;
24
+ /**
25
+ * Directories --exclude pruned. Counted separately because we stop walking
26
+ * them, so we honestly do not know how many files were inside. Saying
27
+ * "0 files excluded" after skipping a whole tree would be its own small lie.
28
+ */
29
+ readonly excludedDirectories: readonly string[];
30
+ /**
31
+ * Files that declare which PHP the project runs on - Dockerfiles, compose
32
+ * files, composer.json, `.php-version`. Not source code, so not in `files`;
33
+ * the runtime version check reads them (platform/runtime-version.ts). The
34
+ * same directories are skipped as for code, so a dependency's composer.json
35
+ * under vendor/ is never mistaken for the project's own.
36
+ */
37
+ readonly runtimeFiles: readonly string[];
38
+ }
39
+ export interface WalkOptions {
40
+ /** Extensions we are able to parse. Everything else is counted and skipped. */
41
+ readonly scannable: readonly string[];
42
+ readonly maxBytes?: number;
43
+ /** Glob-ish patterns from --exclude. */
44
+ readonly exclude?: readonly string[];
45
+ }
46
+ /**
47
+ * Turn a glob into a regular expression.
48
+ *
49
+ * Supported, deliberately a small subset:
50
+ * ** any number of path segments
51
+ * * anything except a path separator
52
+ * ? one character
53
+ * A pattern with no wildcards and no separator (e.g. `vendor`) matches any path
54
+ * SEGMENT with that name, which is what people mean by `--exclude=vendor`.
55
+ *
56
+ * Everything is matched against the path with forward slashes, so the same
57
+ * pattern works on Windows and Unix.
58
+ */
59
+ export declare function globToRegExp(pattern: string): RegExp;
60
+ export declare function collectFiles(target: string, options: WalkOptions): Promise<WalkResult>;