@securecode-ai/mcp 0.4.0 → 0.4.1
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.
- package/README.md +107 -13
- package/dist/api/client.d.ts +1 -1
- package/dist/api/client.js +2 -1
- package/dist/api/client.js.map +1 -1
- package/dist/attack/agentScanExecutor.js +159 -5
- package/dist/attack/agentScanExecutor.js.map +1 -1
- package/dist/attack/agentScanLoop.js +157 -3
- package/dist/attack/agentScanLoop.js.map +1 -1
- package/dist/attack/agentScanProtocol.d.ts +47 -5
- package/dist/attack/agentScanProtocol.js +3 -3
- package/dist/index.js +218 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +5 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.js +80 -3
- package/dist/mcp/tools.js.map +1 -1
- package/dist/project-map/agentMemory.d.ts +75 -0
- package/dist/project-map/agentMemory.js +230 -0
- package/dist/project-map/agentMemory.js.map +1 -0
- package/dist/project-map/crossFileTaintTracker.d.ts +51 -0
- package/dist/project-map/crossFileTaintTracker.js +429 -0
- package/dist/project-map/crossFileTaintTracker.js.map +1 -0
- package/dist/project-map/endpointDiscovery.d.ts +24 -0
- package/dist/project-map/endpointDiscovery.js +101 -0
- package/dist/project-map/endpointDiscovery.js.map +1 -0
- package/dist/project-map/guardEvaluator.js +37 -0
- package/dist/project-map/guardEvaluator.js.map +1 -1
- package/dist/project-map/guardPatterns.js +16 -5
- package/dist/project-map/guardPatterns.js.map +1 -1
- package/dist/project-map/sanitizers.js +10 -0
- package/dist/project-map/sanitizers.js.map +1 -1
- package/dist/project-map/scanCache.d.ts +67 -0
- package/dist/project-map/scanCache.js +169 -0
- package/dist/project-map/scanCache.js.map +1 -0
- package/dist/project-map/sinkRegistry.js +165 -0
- package/dist/project-map/sinkRegistry.js.map +1 -1
- package/dist/project-map/taintSources.d.ts +19 -1
- package/dist/project-map/taintSources.js +69 -2
- package/dist/project-map/taintSources.js.map +1 -1
- package/dist/project-map/taintTracker.d.ts +4 -1
- package/dist/project-map/taintTracker.js +188 -5
- package/dist/project-map/taintTracker.js.map +1 -1
- package/dist/tools/agentMemoryTools.d.ts +18 -0
- package/dist/tools/agentMemoryTools.js +107 -0
- package/dist/tools/agentMemoryTools.js.map +1 -0
- package/dist/tools/agentScan.js +206 -33
- package/dist/tools/agentScan.js.map +1 -1
- package/dist/utils/files.js +19 -2
- package/dist/utils/files.js.map +1 -1
- package/dist/utils/listFiles.d.ts +17 -0
- package/dist/utils/listFiles.js +126 -0
- package/dist/utils/listFiles.js.map +1 -0
- package/dist/utils/listImports.d.ts +15 -0
- package/dist/utils/listImports.js +164 -0
- package/dist/utils/listImports.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Cross-file taint tracker — follows taint across file boundaries.
|
|
4
|
+
*
|
|
5
|
+
* The single-file `trackTaint` engine finds source→sink flows within one
|
|
6
|
+
* file. This module extends that to multi-file flows by:
|
|
7
|
+
*
|
|
8
|
+
* 1. Parsing the entry file's imports (collectImports)
|
|
9
|
+
* 2. Resolving each import to a file on disk (resolveModulePath)
|
|
10
|
+
* 3. Indexing each imported function: does it return tainted data?
|
|
11
|
+
* Does it propagate taint through a specific parameter?
|
|
12
|
+
* 4. Running taint tracking on the entry file with the cross-file
|
|
13
|
+
* function index, so calls to imported functions that propagate
|
|
14
|
+
* taint are recognised.
|
|
15
|
+
*
|
|
16
|
+
* Limits prevent runaway analysis on large codebases:
|
|
17
|
+
* maxDepth (3) — how many import hops to follow
|
|
18
|
+
* maxFiles (10) — total files parsed per trace
|
|
19
|
+
* maxFunctionBodySize (5000 chars) — skip oversized functions
|
|
20
|
+
*/
|
|
21
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
24
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
25
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
26
|
+
}
|
|
27
|
+
Object.defineProperty(o, k2, desc);
|
|
28
|
+
}) : (function(o, m, k, k2) {
|
|
29
|
+
if (k2 === undefined) k2 = k;
|
|
30
|
+
o[k2] = m[k];
|
|
31
|
+
}));
|
|
32
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
33
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
34
|
+
}) : function(o, v) {
|
|
35
|
+
o["default"] = v;
|
|
36
|
+
});
|
|
37
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
38
|
+
var ownKeys = function(o) {
|
|
39
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
40
|
+
var ar = [];
|
|
41
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
42
|
+
return ar;
|
|
43
|
+
};
|
|
44
|
+
return ownKeys(o);
|
|
45
|
+
};
|
|
46
|
+
return function (mod) {
|
|
47
|
+
if (mod && mod.__esModule) return mod;
|
|
48
|
+
var result = {};
|
|
49
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
50
|
+
__setModuleDefault(result, mod);
|
|
51
|
+
return result;
|
|
52
|
+
};
|
|
53
|
+
})();
|
|
54
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
55
|
+
exports.trackTaintCrossFile = trackTaintCrossFile;
|
|
56
|
+
const fs = __importStar(require("fs"));
|
|
57
|
+
const path = __importStar(require("path"));
|
|
58
|
+
const parserLoader_1 = require("./parserLoader");
|
|
59
|
+
const astHelpers_1 = require("./astHelpers");
|
|
60
|
+
const taintTracker_1 = require("./taintTracker");
|
|
61
|
+
// ── Constants ───────────────────────────────────────────────────────────────
|
|
62
|
+
const DEFAULT_MAX_DEPTH = 3;
|
|
63
|
+
const DEFAULT_MAX_FILES = 10;
|
|
64
|
+
const MAX_FUNCTION_BODY_SIZE = 5000;
|
|
65
|
+
const FUNCTION_NODE_TYPES = new Set([
|
|
66
|
+
'function_declaration', 'function_definition', 'method_definition',
|
|
67
|
+
'async_function_declaration', 'async_function_definition',
|
|
68
|
+
'arrow_function', 'function_expression',
|
|
69
|
+
'generator_function_declaration', 'generator_declaration',
|
|
70
|
+
]);
|
|
71
|
+
/**
|
|
72
|
+
* Build a map of `functionName → FunctionAnalysis` for all exported
|
|
73
|
+
* functions in the file. Each function is analyzed to determine whether
|
|
74
|
+
* it returns tainted data and which parameters propagate taint.
|
|
75
|
+
*/
|
|
76
|
+
async function indexFileFunctions(source, language, file) {
|
|
77
|
+
const out = new Map();
|
|
78
|
+
const parsed = await (0, parserLoader_1.parseSource)(source, language);
|
|
79
|
+
if (!parsed)
|
|
80
|
+
return out;
|
|
81
|
+
const { root } = parsed;
|
|
82
|
+
for (const node of (0, astHelpers_1.walk)(root)) {
|
|
83
|
+
if (!FUNCTION_NODE_TYPES.has(node.type))
|
|
84
|
+
continue;
|
|
85
|
+
const name = functionName(node, source);
|
|
86
|
+
if (!name)
|
|
87
|
+
continue;
|
|
88
|
+
// Skip functions that are too large
|
|
89
|
+
const bodyText = source.slice(node.startIndex, node.endIndex);
|
|
90
|
+
if (bodyText.length > MAX_FUNCTION_BODY_SIZE)
|
|
91
|
+
continue;
|
|
92
|
+
const params = extractParams(node, source);
|
|
93
|
+
const line = node.startPosition.row + 1;
|
|
94
|
+
// Analyze: seed each parameter as tainted, check if the function
|
|
95
|
+
// returns tainted data from any param
|
|
96
|
+
const taintThroughParams = new Set();
|
|
97
|
+
let returnsTainted = false;
|
|
98
|
+
for (let i = 0; i < params.length; i++) {
|
|
99
|
+
const seedName = params[i];
|
|
100
|
+
if (!seedName)
|
|
101
|
+
continue;
|
|
102
|
+
// Run taint tracking on just this function body with the param
|
|
103
|
+
// seeded as a source. If any result mentions this param as the
|
|
104
|
+
// source, the function propagates taint through it.
|
|
105
|
+
const flows = await trackTaintWithSeed(source, language, seedName, node);
|
|
106
|
+
if (flows.length > 0) {
|
|
107
|
+
taintThroughParams.add(i);
|
|
108
|
+
returnsTainted = true; // if it reaches a sink it could also be returned
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Also check if the function returns tainted data directly
|
|
112
|
+
// (e.g. `return req.body` without going through a sink)
|
|
113
|
+
if (!returnsTainted) {
|
|
114
|
+
returnsTainted = checkReturnsTainted(node, source, language);
|
|
115
|
+
}
|
|
116
|
+
out.set(name, { name, file, line, params, returnsTainted, taintThroughParams });
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Check if a function returns tainted data by examining return statements.
|
|
122
|
+
* Looks for returns of taint sources (req.body, etc.) or tainted variables.
|
|
123
|
+
*/
|
|
124
|
+
function checkReturnsTainted(fnNode, source, language) {
|
|
125
|
+
let found = false;
|
|
126
|
+
for (const child of (0, astHelpers_1.walk)(fnNode)) {
|
|
127
|
+
if (child.type !== 'return_statement' && child.type !== 'return')
|
|
128
|
+
continue;
|
|
129
|
+
// Skip the function's own return type annotation
|
|
130
|
+
if (child.parent && FUNCTION_NODE_TYPES.has(child.parent.type) && child === child.parent.namedChildren[0])
|
|
131
|
+
continue;
|
|
132
|
+
const expr = child.namedChildren.find(c => c.type !== 'return' && c.type !== 'return_statement');
|
|
133
|
+
if (!expr)
|
|
134
|
+
continue;
|
|
135
|
+
const text = source.slice(expr.startIndex, expr.endIndex);
|
|
136
|
+
// Simple heuristic: if the return expression contains a known source pattern
|
|
137
|
+
// like req.body, request.GET, etc.
|
|
138
|
+
if (isTaintSourceText(text, language)) {
|
|
139
|
+
found = true;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return found;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Check if text looks like a taint source (req.body, request.GET, etc.)
|
|
147
|
+
* without needing a full AST parse.
|
|
148
|
+
*/
|
|
149
|
+
function isTaintSourceText(text, language) {
|
|
150
|
+
const patterns = [];
|
|
151
|
+
if (language === 'python') {
|
|
152
|
+
patterns.push('request.GET', 'request.POST', 'request.args', 'request.form', 'request.json', 'request.body', 'request.headers', 'request.cookies', 'request.values', 'request.data', 'request.META', 'request.FILES', 'sys.argv', 'os.environ', 'os.getenv');
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
patterns.push('req.body', 'req.query', 'req.params', 'req.headers', 'req.cookies', 'req.files', 'request.body', 'request.query', 'request.params', 'request.headers', 'request.cookies', 'ctx.body', 'ctx.query', 'ctx.params', 'ctx.headers', 'event.body', 'req.json', 'request.json');
|
|
156
|
+
}
|
|
157
|
+
return patterns.some(p => text.includes(p));
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Run taint tracking on a file with a parameter seeded as a taint source.
|
|
161
|
+
* Uses the seedParams option of trackTaint to pre-taint the parameter
|
|
162
|
+
* at module scope, so it propagates into the function's scope.
|
|
163
|
+
*/
|
|
164
|
+
async function trackTaintWithSeed(source, language, seedParam, fnNode) {
|
|
165
|
+
const results = await (0, taintTracker_1.trackTaint)(source, language, [seedParam]);
|
|
166
|
+
// Filter: only keep results where the source involves our seed
|
|
167
|
+
return results.filter(r => r.source === `<param:${seedParam}>` ||
|
|
168
|
+
r.propagationPath.some(p => p.variable === seedParam && p.operation === 'source'));
|
|
169
|
+
}
|
|
170
|
+
/** Extract parameter names from a function node. */
|
|
171
|
+
function extractParams(fnNode, source) {
|
|
172
|
+
const params = [];
|
|
173
|
+
for (const child of fnNode.namedChildren) {
|
|
174
|
+
if (child.type === 'parameters' || child.type === 'formal_parameters' || child.type === 'parameter_list') {
|
|
175
|
+
for (const param of child.namedChildren) {
|
|
176
|
+
if ((0, astHelpers_1.isIdentifier)(param)) {
|
|
177
|
+
// Plain identifier: `name` (JS/Python)
|
|
178
|
+
params.push(source.slice(param.startIndex, param.endIndex));
|
|
179
|
+
}
|
|
180
|
+
else if (param.type === 'required_parameter') {
|
|
181
|
+
// TypeScript required parameter: `name: string` — take the identifier
|
|
182
|
+
const id = param.namedChildren.find(c => (0, astHelpers_1.isIdentifier)(c));
|
|
183
|
+
if (id)
|
|
184
|
+
params.push(source.slice(id.startIndex, id.endIndex));
|
|
185
|
+
}
|
|
186
|
+
else if (param.type === 'shorthand_property_identifier_pattern' || param.type === 'shorthand_property_identifier') {
|
|
187
|
+
params.push(source.slice(param.startIndex, param.endIndex));
|
|
188
|
+
}
|
|
189
|
+
else if (param.type === 'assignment_pattern') {
|
|
190
|
+
// default param: `x = 'default'` — take the left side
|
|
191
|
+
const left = param.namedChildren[0];
|
|
192
|
+
if (left && (0, astHelpers_1.isIdentifier)(left)) {
|
|
193
|
+
params.push(source.slice(left.startIndex, left.endIndex));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else if (param.type === 'identifier' || param.type === 'typed_parameter') {
|
|
197
|
+
params.push(source.slice(param.startIndex, param.endIndex));
|
|
198
|
+
}
|
|
199
|
+
else if (param.type === 'typed_parameter') {
|
|
200
|
+
// Python typed param: `name: str`
|
|
201
|
+
const id = param.namedChildren.find(c => (0, astHelpers_1.isIdentifier)(c));
|
|
202
|
+
if (id)
|
|
203
|
+
params.push(source.slice(id.startIndex, id.endIndex));
|
|
204
|
+
}
|
|
205
|
+
// Skip rest params, destructuring patterns — too complex for now
|
|
206
|
+
}
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return params;
|
|
211
|
+
}
|
|
212
|
+
/** Get the name of a function node (mirrors taintTracker's functionName). */
|
|
213
|
+
function functionName(fn, source) {
|
|
214
|
+
const id = fn.namedChildren.find(c => (0, astHelpers_1.isIdentifier)(c));
|
|
215
|
+
if (id)
|
|
216
|
+
return source.slice(id.startIndex, id.endIndex);
|
|
217
|
+
const parent = fn.parent;
|
|
218
|
+
if (parent && (parent.type === 'lexical_declaration' || parent.type === 'variable_declaration' || parent.type === 'assignment')) {
|
|
219
|
+
const name = parent.child(0);
|
|
220
|
+
if (name && (0, astHelpers_1.isIdentifier)(name))
|
|
221
|
+
return source.slice(name.startIndex, name.endIndex);
|
|
222
|
+
}
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
// ── Language detection ──────────────────────────────────────────────────────
|
|
226
|
+
function toSinkLanguage(filePath) {
|
|
227
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
228
|
+
const map = {
|
|
229
|
+
'.js': 'javascript', '.jsx': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript',
|
|
230
|
+
'.ts': 'typescript', '.tsx': 'tsx',
|
|
231
|
+
'.py': 'python',
|
|
232
|
+
};
|
|
233
|
+
return map[ext] || null;
|
|
234
|
+
}
|
|
235
|
+
// ── Main entry point ───────────────────────────────────────────────────────
|
|
236
|
+
/**
|
|
237
|
+
* Track taint propagation across file boundaries.
|
|
238
|
+
*
|
|
239
|
+
* Parses the entry file, resolves its imports, indexes imported functions
|
|
240
|
+
* for taint behavior, then runs taint tracking with cross-file knowledge.
|
|
241
|
+
*
|
|
242
|
+
* Returns CrossFileTaintResult[] with full file:line paths.
|
|
243
|
+
*/
|
|
244
|
+
async function trackTaintCrossFile(opts) {
|
|
245
|
+
const maxDepth = opts.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
246
|
+
const maxFiles = opts.maxFiles ?? DEFAULT_MAX_FILES;
|
|
247
|
+
const entryAbs = path.resolve(opts.workspaceRoot, opts.filePath);
|
|
248
|
+
const language = toSinkLanguage(opts.filePath);
|
|
249
|
+
if (!language)
|
|
250
|
+
return [];
|
|
251
|
+
const entrySource = fs.readFileSync(entryAbs, 'utf8');
|
|
252
|
+
const entryParsed = await (0, parserLoader_1.parseSource)(entrySource, language);
|
|
253
|
+
if (!entryParsed)
|
|
254
|
+
return [];
|
|
255
|
+
// 1. Collect imports from the entry file
|
|
256
|
+
const imports = (0, astHelpers_1.collectImports)(entryParsed.root, entrySource);
|
|
257
|
+
// imports = Map { localName -> moduleSpec }
|
|
258
|
+
// 2. Build a function index from imported files
|
|
259
|
+
const functionIndex = new Map();
|
|
260
|
+
const visitedFiles = new Set([opts.filePath]);
|
|
261
|
+
const filesToIndex = [];
|
|
262
|
+
for (const [localName, moduleSpec] of imports) {
|
|
263
|
+
filesToIndex.push({ spec: moduleSpec, fromFile: opts.filePath, depth: 0 });
|
|
264
|
+
}
|
|
265
|
+
let filesParsed = 1;
|
|
266
|
+
while (filesToIndex.length > 0 && filesParsed < maxFiles) {
|
|
267
|
+
const { spec, fromFile, depth } = filesToIndex.shift();
|
|
268
|
+
if (depth >= maxDepth)
|
|
269
|
+
continue;
|
|
270
|
+
const resolvedFile = (0, astHelpers_1.resolveModulePath)(spec, path.resolve(opts.workspaceRoot, fromFile), opts.workspaceRoot);
|
|
271
|
+
if (!resolvedFile)
|
|
272
|
+
continue; // node_modules or unresolvable
|
|
273
|
+
if (visitedFiles.has(resolvedFile))
|
|
274
|
+
continue; // cycle guard
|
|
275
|
+
visitedFiles.add(resolvedFile);
|
|
276
|
+
const resolvedAbs = path.resolve(opts.workspaceRoot, resolvedFile);
|
|
277
|
+
let source;
|
|
278
|
+
try {
|
|
279
|
+
source = fs.readFileSync(resolvedAbs, 'utf8');
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
const fileLang = toSinkLanguage(resolvedFile);
|
|
285
|
+
if (!fileLang)
|
|
286
|
+
continue;
|
|
287
|
+
const index = await indexFileFunctions(source, fileLang, resolvedFile);
|
|
288
|
+
for (const [name, analysis] of index) {
|
|
289
|
+
functionIndex.set(name, analysis);
|
|
290
|
+
}
|
|
291
|
+
filesParsed++;
|
|
292
|
+
// Queue deeper imports (depth+1) if we haven't hit the limit
|
|
293
|
+
if (depth + 1 < maxDepth) {
|
|
294
|
+
const parsed = await (0, parserLoader_1.parseSource)(source, fileLang);
|
|
295
|
+
if (parsed) {
|
|
296
|
+
const deeperImports = (0, astHelpers_1.collectImports)(parsed.root, source);
|
|
297
|
+
for (const [_, deeperSpec] of deeperImports) {
|
|
298
|
+
filesToIndex.push({ spec: deeperSpec, fromFile: resolvedFile, depth: depth + 1 });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// 3. Run single-file taint tracking on the entry file
|
|
304
|
+
const entryFlows = await (0, taintTracker_1.trackTaint)(entrySource, language);
|
|
305
|
+
if (entryFlows.length === 0) {
|
|
306
|
+
// No flows in the entry file — check if any imported functions
|
|
307
|
+
// have sinks that could be reached through tainted parameters
|
|
308
|
+
return checkImportedFunctionSinks(functionIndex, imports, opts.workspaceRoot, language);
|
|
309
|
+
}
|
|
310
|
+
// 4. Convert single-file results to cross-file results
|
|
311
|
+
// For now: if the entry file has flows, report them with the entry file
|
|
312
|
+
// as both source and sink file. If the flow involves a call to an
|
|
313
|
+
// indexed function, add the cross-file step.
|
|
314
|
+
const results = [];
|
|
315
|
+
for (const flow of entryFlows) {
|
|
316
|
+
const steps = [];
|
|
317
|
+
let sourceFile = opts.filePath;
|
|
318
|
+
let sinkFile = opts.filePath;
|
|
319
|
+
for (const step of flow.propagationPath) {
|
|
320
|
+
// Check if this step involves a call to an indexed function
|
|
321
|
+
const callMatch = step.description.match(/^([\w$]+)\(\)/);
|
|
322
|
+
if (callMatch && (step.operation === 'call-pass' || step.operation === 'sink-arg')) {
|
|
323
|
+
const fnName = callMatch[1];
|
|
324
|
+
const analysis = functionIndex.get(fnName);
|
|
325
|
+
if (analysis && analysis.file !== opts.filePath) {
|
|
326
|
+
// This step crosses a file boundary
|
|
327
|
+
steps.push({
|
|
328
|
+
file: opts.filePath,
|
|
329
|
+
line: step.line,
|
|
330
|
+
variable: step.variable,
|
|
331
|
+
operation: 'call-cross-file',
|
|
332
|
+
description: `${fnName}() in ${analysis.file}:${analysis.line} ${analysis.returnsTainted ? 'returns tainted data' : 'propagates taint'}`,
|
|
333
|
+
});
|
|
334
|
+
sourceFile = analysis.file;
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
steps.push({
|
|
339
|
+
file: opts.filePath,
|
|
340
|
+
line: step.line,
|
|
341
|
+
variable: step.variable,
|
|
342
|
+
operation: step.operation === 'source' ? 'source' : 'sink-arg',
|
|
343
|
+
description: step.description,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
results.push({
|
|
347
|
+
source: flow.source,
|
|
348
|
+
sourceLine: flow.sourceLine,
|
|
349
|
+
sourceFile,
|
|
350
|
+
sink: flow.sink,
|
|
351
|
+
sinkLine: flow.sinkLine,
|
|
352
|
+
sinkFile,
|
|
353
|
+
canonicalType: flow.canonicalType,
|
|
354
|
+
crossFileSteps: steps,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
// 5. Also check imported functions for sinks reachable through params
|
|
358
|
+
const importedSinks = await checkImportedFunctionSinks(functionIndex, imports, opts.workspaceRoot, language);
|
|
359
|
+
results.push(...importedSinks);
|
|
360
|
+
// Dedup by (source, sink, sinkFile, sinkLine)
|
|
361
|
+
const seen = new Set();
|
|
362
|
+
const deduped = [];
|
|
363
|
+
for (const r of results) {
|
|
364
|
+
const key = `${r.source}:${r.sink}:${r.sinkFile}:${r.sinkLine}`;
|
|
365
|
+
if (!seen.has(key)) {
|
|
366
|
+
seen.add(key);
|
|
367
|
+
deduped.push(r);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return deduped;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Check imported functions for sinks reachable through tainted parameters.
|
|
374
|
+
* If an imported function has a sink in its body and the caller passes
|
|
375
|
+
* tainted data to the relevant parameter, that's a cross-file flow.
|
|
376
|
+
*/
|
|
377
|
+
async function checkImportedFunctionSinks(functionIndex, imports, workspaceRoot, language) {
|
|
378
|
+
const results = [];
|
|
379
|
+
for (const [name, analysis] of functionIndex) {
|
|
380
|
+
if (analysis.taintThroughParams.size === 0)
|
|
381
|
+
continue;
|
|
382
|
+
// This function has sinks reachable through params.
|
|
383
|
+
// Read its source and run trackTaint with all params seeded to find sinks.
|
|
384
|
+
const fileAbs = path.resolve(workspaceRoot, analysis.file);
|
|
385
|
+
let source;
|
|
386
|
+
try {
|
|
387
|
+
source = fs.readFileSync(fileAbs, 'utf8');
|
|
388
|
+
}
|
|
389
|
+
catch {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
// Seed all params of this function as taint sources
|
|
393
|
+
const flows = await (0, taintTracker_1.trackTaint)(source, language, analysis.params);
|
|
394
|
+
for (const flow of flows) {
|
|
395
|
+
// Only include flows where the source is a seeded parameter
|
|
396
|
+
const isParamSource = analysis.params.includes(flow.source.replace('<param:', '').replace('>', '')) ||
|
|
397
|
+
flow.propagationPath.some(p => p.operation === 'source' && analysis.params.includes(p.variable));
|
|
398
|
+
if (!isParamSource)
|
|
399
|
+
continue;
|
|
400
|
+
results.push({
|
|
401
|
+
source: `${name}() param: ${flow.source}`,
|
|
402
|
+
sourceLine: analysis.line,
|
|
403
|
+
sourceFile: analysis.file,
|
|
404
|
+
sink: flow.sink,
|
|
405
|
+
sinkLine: flow.sinkLine,
|
|
406
|
+
sinkFile: analysis.file,
|
|
407
|
+
canonicalType: flow.canonicalType,
|
|
408
|
+
crossFileSteps: [
|
|
409
|
+
{
|
|
410
|
+
file: analysis.file,
|
|
411
|
+
line: analysis.line,
|
|
412
|
+
variable: name,
|
|
413
|
+
operation: 'call-cross-file',
|
|
414
|
+
description: `${name}() called with tainted data — param "${flow.source}" reaches sink`,
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
file: analysis.file,
|
|
418
|
+
line: flow.sinkLine,
|
|
419
|
+
variable: flow.sink,
|
|
420
|
+
operation: 'sink-arg',
|
|
421
|
+
description: `${flow.sink}(${flow.propagationPath[flow.propagationPath.length - 1]?.variable || ''})`,
|
|
422
|
+
},
|
|
423
|
+
],
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return results;
|
|
428
|
+
}
|
|
429
|
+
//# sourceMappingURL=crossFileTaintTracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crossFileTaintTracker.js","sourceRoot":"","sources":["../../src/project-map/crossFileTaintTracker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Endpoint discovery — scans the workspace for HTTP route handler definitions
|
|
3
|
+
* across all supported frameworks.
|
|
4
|
+
*
|
|
5
|
+
* Uses ripgrep (via searchCode) for speed. Detects:
|
|
6
|
+
* - Next.js App Router: export async function GET/POST/PATCH/DELETE/PUT
|
|
7
|
+
* - Express: app.get/post/patch/delete/put('path', handler)
|
|
8
|
+
* router.get/post/...('path', handler)
|
|
9
|
+
* - FastAPI: @app.get/post/...("/path")
|
|
10
|
+
* @router.get/post/...("/path")
|
|
11
|
+
* - Flask: @app.route("/path") @app.get/post/...("/path")
|
|
12
|
+
* - Django: path('url', views.handler) re_path(...)
|
|
13
|
+
*
|
|
14
|
+
* Returns a compact list of { method, path, file, line, framework } entries.
|
|
15
|
+
*/
|
|
16
|
+
export interface DiscoveredEndpoint {
|
|
17
|
+
method: string;
|
|
18
|
+
path: string;
|
|
19
|
+
file: string;
|
|
20
|
+
line: number;
|
|
21
|
+
framework: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function discoverEndpoints(workspaceRoot: string, glob?: string): Promise<DiscoveredEndpoint[]>;
|
|
24
|
+
export declare function formatEndpoints(endpoints: DiscoveredEndpoint[]): string;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Endpoint discovery — scans the workspace for HTTP route handler definitions
|
|
4
|
+
* across all supported frameworks.
|
|
5
|
+
*
|
|
6
|
+
* Uses ripgrep (via searchCode) for speed. Detects:
|
|
7
|
+
* - Next.js App Router: export async function GET/POST/PATCH/DELETE/PUT
|
|
8
|
+
* - Express: app.get/post/patch/delete/put('path', handler)
|
|
9
|
+
* router.get/post/...('path', handler)
|
|
10
|
+
* - FastAPI: @app.get/post/...("/path")
|
|
11
|
+
* @router.get/post/...("/path")
|
|
12
|
+
* - Flask: @app.route("/path") @app.get/post/...("/path")
|
|
13
|
+
* - Django: path('url', views.handler) re_path(...)
|
|
14
|
+
*
|
|
15
|
+
* Returns a compact list of { method, path, file, line, framework } entries.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.discoverEndpoints = discoverEndpoints;
|
|
19
|
+
exports.formatEndpoints = formatEndpoints;
|
|
20
|
+
const searchCode_1 = require("../utils/searchCode");
|
|
21
|
+
const HTTP_METHODS = ['GET', 'POST', 'PATCH', 'DELETE', 'PUT'];
|
|
22
|
+
// Patterns to search for. Each entry has a regex and a framework label.
|
|
23
|
+
const PATTERNS = [
|
|
24
|
+
// Next.js App Router: export async function GET / POST / etc.
|
|
25
|
+
{ regex: 'export\\s+async\\s+function\\s+(GET|POST|PATCH|DELETE|PUT)\\b', framework: 'nextjs' },
|
|
26
|
+
// Express: app.METHOD( or router.METHOD(
|
|
27
|
+
{ regex: '(app|router)\\.(get|post|patch|delete|put)\\s*\\(', framework: 'express' },
|
|
28
|
+
// Express: app.all(
|
|
29
|
+
{ regex: '(app|router)\\.all\\s*\\(', framework: 'express' },
|
|
30
|
+
// FastAPI: @app.get( / @router.get(
|
|
31
|
+
{ regex: '@(app|router)\\.(get|post|patch|delete|put)\\s*\\(', framework: 'fastapi' },
|
|
32
|
+
// Flask: @app.route(
|
|
33
|
+
{ regex: '@app\\.route\\s*\\(', framework: 'flask' },
|
|
34
|
+
// Flask 2.0: @app.get( / @app.post( etc
|
|
35
|
+
{ regex: '@app\\.(get|post|patch|delete|put)\\s*\\(', framework: 'flask' },
|
|
36
|
+
// Django: path('url', views.handler)
|
|
37
|
+
{ regex: '(re_)?path\\s*\\(\\s*[\'"]', framework: 'django' },
|
|
38
|
+
];
|
|
39
|
+
function extractMethod(text, framework) {
|
|
40
|
+
const upper = text.toUpperCase();
|
|
41
|
+
for (const m of HTTP_METHODS) {
|
|
42
|
+
if (upper.includes(m))
|
|
43
|
+
return m;
|
|
44
|
+
}
|
|
45
|
+
if (framework === 'flask' && text.includes('.route('))
|
|
46
|
+
return '*';
|
|
47
|
+
if (framework === 'django')
|
|
48
|
+
return '*';
|
|
49
|
+
if (upper.includes('.ALL('))
|
|
50
|
+
return '*';
|
|
51
|
+
return '?';
|
|
52
|
+
}
|
|
53
|
+
function extractPath(text) {
|
|
54
|
+
// Try to find a string literal (single or double quoted) in the line
|
|
55
|
+
const match = text.match(/['"`]([^'"`]+)['"`]/);
|
|
56
|
+
return match ? match[1] : '';
|
|
57
|
+
}
|
|
58
|
+
async function discoverEndpoints(workspaceRoot, glob) {
|
|
59
|
+
const allEndpoints = [];
|
|
60
|
+
const seen = new Set(); // dedup by file:line
|
|
61
|
+
for (const { regex, framework } of PATTERNS) {
|
|
62
|
+
try {
|
|
63
|
+
const result = await (0, searchCode_1.searchCode)(workspaceRoot, regex, glob);
|
|
64
|
+
for (const hit of result.hits) {
|
|
65
|
+
const key = `${hit.path}:${hit.line}`;
|
|
66
|
+
if (seen.has(key))
|
|
67
|
+
continue;
|
|
68
|
+
seen.add(key);
|
|
69
|
+
allEndpoints.push({
|
|
70
|
+
method: extractMethod(hit.text, framework),
|
|
71
|
+
path: extractPath(hit.text),
|
|
72
|
+
file: hit.path,
|
|
73
|
+
line: hit.line,
|
|
74
|
+
framework,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// ripgrep not available — skip this pattern
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Sort by file, then line
|
|
83
|
+
allEndpoints.sort((a, b) => {
|
|
84
|
+
if (a.file !== b.file)
|
|
85
|
+
return a.file.localeCompare(b.file);
|
|
86
|
+
return a.line - b.line;
|
|
87
|
+
});
|
|
88
|
+
return allEndpoints;
|
|
89
|
+
}
|
|
90
|
+
function formatEndpoints(endpoints) {
|
|
91
|
+
if (endpoints.length === 0) {
|
|
92
|
+
return 'No HTTP route handlers found in the workspace.';
|
|
93
|
+
}
|
|
94
|
+
const lines = [`Found ${endpoints.length} endpoint(s):`];
|
|
95
|
+
for (const ep of endpoints) {
|
|
96
|
+
const pathStr = ep.path ? ` ${ep.path}` : '';
|
|
97
|
+
lines.push(` ${ep.method.padEnd(6)} ${ep.file}:${ep.line} [${ep.framework}]${pathStr}`);
|
|
98
|
+
}
|
|
99
|
+
return lines.join('\n');
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=endpointDiscovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpointDiscovery.js","sourceRoot":"","sources":["../../src/project-map/endpointDiscovery.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;AAiDH,8CAmCC;AAED,0CAWC;AA/FD,oDAA4D;AAU5D,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAU,CAAC;AAExE,wEAAwE;AACxE,MAAM,QAAQ,GAA2C;IACrD,8DAA8D;IAC9D,EAAE,KAAK,EAAE,+DAA+D,EAAE,SAAS,EAAE,QAAQ,EAAE;IAC/F,yCAAyC;IACzC,EAAE,KAAK,EAAE,mDAAmD,EAAE,SAAS,EAAE,SAAS,EAAE;IACpF,oBAAoB;IACpB,EAAE,KAAK,EAAE,2BAA2B,EAAE,SAAS,EAAE,SAAS,EAAE;IAC5D,oCAAoC;IACpC,EAAE,KAAK,EAAE,oDAAoD,EAAE,SAAS,EAAE,SAAS,EAAE;IACrF,qBAAqB;IACrB,EAAE,KAAK,EAAE,qBAAqB,EAAE,SAAS,EAAE,OAAO,EAAE;IACpD,wCAAwC;IACxC,EAAE,KAAK,EAAE,2CAA2C,EAAE,SAAS,EAAE,OAAO,EAAE;IAC1E,qCAAqC;IACrC,EAAE,KAAK,EAAE,4BAA4B,EAAE,SAAS,EAAE,QAAQ,EAAE;CAC/D,CAAC;AAEF,SAAS,aAAa,CAAC,IAAY,EAAE,SAAiB;IAClD,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACjC,KAAK,MAAM,CAAC,IAAI,YAAY,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IACD,IAAI,SAAS,KAAK,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,GAAG,CAAC;IAClE,IAAI,SAAS,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC;IACvC,IAAI,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,GAAG,CAAC;IACxC,OAAO,GAAG,CAAC;AACf,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC7B,qEAAqE;IACrE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAChD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACjC,CAAC;AAEM,KAAK,UAAU,iBAAiB,CACnC,aAAqB,EACrB,IAAa;IAEb,MAAM,YAAY,GAAyB,EAAE,CAAC;IAC9C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC,CAAC,qBAAqB;IAErD,KAAK,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC1C,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,IAAA,uBAAU,EAAC,aAAa,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;YAC5D,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC5B,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;gBACtC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;oBAAE,SAAS;gBAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBAEd,YAAY,CAAC,IAAI,CAAC;oBACd,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC;oBAC1C,IAAI,EAAE,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC;oBAC3B,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,SAAS;iBACZ,CAAC,CAAC;YACP,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACL,4CAA4C;QAChD,CAAC;IACL,CAAC;IAED,0BAA0B;IAC1B,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACvB,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI;YAAE,OAAO,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC3D,OAAO,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC;IAC3B,CAAC,CAAC,CAAC;IAEH,OAAO,YAAY,CAAC;AACxB,CAAC;AAED,SAAgB,eAAe,CAAC,SAA+B;IAC3D,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,gDAAgD,CAAC;IAC5D,CAAC;IAED,MAAM,KAAK,GAAa,CAAC,SAAS,SAAS,CAAC,MAAM,eAAe,CAAC,CAAC;IACnE,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7C,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,SAAS,IAAI,OAAO,EAAE,CAAC,CAAC;IAC7F,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC"}
|
|
@@ -123,6 +123,43 @@ function identifyGuardType(fnRoot, source, language) {
|
|
|
123
123
|
&& /(zod|joi|yup|pydantic|schema)/i.test(text)) {
|
|
124
124
|
return 'validator';
|
|
125
125
|
}
|
|
126
|
+
// ── 9. Python-specific guards ─────────────────────────────────────────
|
|
127
|
+
if (language === 'python') {
|
|
128
|
+
// Python allowlist: if x in {'a', 'b'} or if x in ('a', 'b') or if x in ['a', 'b']
|
|
129
|
+
if (/if\s+\w+\s+in\s*[\(\{\[]\s*['"][^\])}]*[\])}]/.test(text)) {
|
|
130
|
+
const hasLiteralSet = /in\s*[\(\{\[]\s*['"][^)\]}]*['"]\s*[,)\]}]/.test(text);
|
|
131
|
+
return hasLiteralSet ? 'allowlist-literal' : 'allowlist-dynamic';
|
|
132
|
+
}
|
|
133
|
+
// Python parameterized query: cursor.execute("...%s...", [params]) or
|
|
134
|
+
// cursor.execute("...:name...", {...}) or session.execute(text("...:name..."), {...})
|
|
135
|
+
for (const node of (0, astHelpers_1.walk)(fnRoot)) {
|
|
136
|
+
if (node.type !== 'call' && node.type !== 'call_expression')
|
|
137
|
+
continue;
|
|
138
|
+
const p = (0, astHelpers_1.callParts)(node, source);
|
|
139
|
+
if (!p || !p.receiver)
|
|
140
|
+
continue;
|
|
141
|
+
if (['execute', 'executemany', 'execute_many'].includes(p.method)) {
|
|
142
|
+
if (p.args.length >= 2) {
|
|
143
|
+
const firstArg = p.args[0];
|
|
144
|
+
const argText = source.slice(firstArg.startIndex, firstArg.endIndex);
|
|
145
|
+
const isStringLike = firstArg.type === 'string';
|
|
146
|
+
const hasPlaceholder = argText.includes('%s') || /:\w+/.test(argText);
|
|
147
|
+
const hasConcat = argText.includes(' + ') || argText.includes('% ') || argText.includes('.format(');
|
|
148
|
+
if (isStringLike && hasPlaceholder && !hasConcat) {
|
|
149
|
+
return 'parameterized-query';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Python constant-time comparison: secrets.compare_digest or hmac.compare_digest
|
|
155
|
+
if (/compare_digest|secrets\.compare|hmac\.compare/.test(text)) {
|
|
156
|
+
return 'auth-api-key'; // constant-time comparison = auth guard
|
|
157
|
+
}
|
|
158
|
+
// Python rate limiting: flask_limiter, slowapi, ratelimit decorator
|
|
159
|
+
if (/flask_limiter|Limiter|slowapi|ratelimit|@limiter|@rate_limit/i.test(text)) {
|
|
160
|
+
return 'rate-limit';
|
|
161
|
+
}
|
|
162
|
+
}
|
|
126
163
|
return 'unknown';
|
|
127
164
|
}
|
|
128
165
|
// ── Evaluation ──────────────────────────────────────────────────────────────
|