circle-ir 3.110.0 → 3.112.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.
- package/configs/sinks/nodejs.json +18 -0
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +54 -2
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js +26 -2
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +155 -7
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/browser/circle-ir.js +149 -6
- package/dist/core/circle-ir-core.cjs +139 -4
- package/dist/core/circle-ir-core.js +139 -4
- package/package.json +1 -1
|
@@ -10551,10 +10551,20 @@ var DEFAULT_SINKS = [
|
|
|
10551
10551
|
{ method: "bash", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10552
10552
|
{ method: "shell", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10553
10553
|
{ method: "sh", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10554
|
-
|
|
10554
|
+
// cognium-dev #187 Sprint 54: arg_positions [0, 1] so the JS shell-mode shape
|
|
10555
|
+
// `spawn('sh', ['-c', tainted])` / `execFile('/bin/sh', ['-c', tainted])`
|
|
10556
|
+
// surfaces taint at the argv-array position (arg[1]).
|
|
10557
|
+
{ method: "spawn", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
|
|
10558
|
+
{ method: "spawnSync", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
|
|
10559
|
+
{ method: "execFile", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
|
|
10560
|
+
{ method: "execFileSync", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
|
|
10555
10561
|
{ method: "fork", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10556
10562
|
{ method: "popen", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10557
10563
|
{ method: "system", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10564
|
+
// execa (npm) — parses tainted shell-style strings into program+argv;
|
|
10565
|
+
// arg[0] is shell-injectable. cognium-dev #187 Sprint 54.
|
|
10566
|
+
{ method: "command", class: "execa", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10567
|
+
{ method: "commandSync", class: "execa", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10558
10568
|
// Apache Commons Exec
|
|
10559
10569
|
// Note: bare class 'Executor' removed (see comment above) — DefaultExecutor matched explicitly.
|
|
10560
10570
|
{ method: "setCommandline", class: "DefaultExecutor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
@@ -11320,8 +11330,27 @@ var DEFAULT_SINKS = [
|
|
|
11320
11330
|
// SQL query calls are covered by class-specific patterns above (Connection, Pool, Client, JdbcTemplate)
|
|
11321
11331
|
// Note: `raw` is shared with Python (Django ORM) — scoped to JS+TS to avoid leaking.
|
|
11322
11332
|
{ method: "raw", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11333
|
+
// sqlite3 (npm) — Database/Statement methods. The JS plugin resolves
|
|
11334
|
+
// `const db = new sqlite3.Database(...); db.all(sql)` to the resolution
|
|
11335
|
+
// target `Connection.all`, so class-scoped patterns matching `Connection`
|
|
11336
|
+
// hit (see #186 Sprint 55 matcher extension consulting call.resolution.target).
|
|
11337
|
+
// `exec`/`run`/`all`/`get`/`each` follow the same shape.
|
|
11338
|
+
{ method: "all", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
11339
|
+
{ method: "run", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
11340
|
+
{ method: "each", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
11341
|
+
{ method: "get", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
11342
|
+
{ method: "exec", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
11323
11343
|
// Browser DOM XSS sinks
|
|
11324
11344
|
{ method: "setAttribute", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
|
|
11345
|
+
// Angular DomSanitizer.bypassSecurityTrust* — CWE-79 (#184 Sprint 55).
|
|
11346
|
+
// These methods explicitly bypass Angular's built-in sanitizer; passing
|
|
11347
|
+
// tainted strings re-introduces DOM-injection risk. Distinctive method
|
|
11348
|
+
// names — classless + language-scoped is safe.
|
|
11349
|
+
{ method: "bypassSecurityTrustHtml", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11350
|
+
{ method: "bypassSecurityTrustScript", type: "xss", cwe: "CWE-79", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11351
|
+
{ method: "bypassSecurityTrustStyle", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11352
|
+
{ method: "bypassSecurityTrustUrl", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11353
|
+
{ method: "bypassSecurityTrustResourceUrl", type: "xss", cwe: "CWE-79", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11325
11354
|
// Express.js XSS (response methods)
|
|
11326
11355
|
{ method: "send", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
11327
11356
|
{ method: "write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
@@ -11334,6 +11363,19 @@ var DEFAULT_SINKS = [
|
|
|
11334
11363
|
{ method: "runInContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11335
11364
|
{ method: "runInNewContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11336
11365
|
{ method: "runInThisContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11366
|
+
// `new vm.Script(taint)` — Node core `vm` module compiles strings. The
|
|
11367
|
+
// JS plugin emits method_name = 'vm.Script' for the constructor call;
|
|
11368
|
+
// the matcher's dotted-simple-name fallback (taint-matcher.ts:1664) lets
|
|
11369
|
+
// pattern.method = 'Script' hit on method_name = 'vm.Script'. The
|
|
11370
|
+
// `class: 'constructor'` short-circuit accepts the no-receiver shape.
|
|
11371
|
+
// (#188 Sprint 55)
|
|
11372
|
+
{ method: "Script", class: "constructor", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11373
|
+
// `setImmediate(taintedString)` — like setTimeout/setInterval, Node will
|
|
11374
|
+
// evaluate the first argument when it is a string. The callback-shape
|
|
11375
|
+
// suppression in taint-matcher.ts:1342 already covers setTimeout/
|
|
11376
|
+
// setInterval; the Sprint 55 fix extends that gate to setImmediate too.
|
|
11377
|
+
// (#188 Sprint 55)
|
|
11378
|
+
{ method: "setImmediate", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11337
11379
|
// protobufjs Root.parse(schemaText) compiles a textual schema into JS at runtime;
|
|
11338
11380
|
// tainted schema → code execution (CVE-2026-41242). Issue #94.
|
|
11339
11381
|
{ method: "parse", class: "protobuf", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
@@ -11396,6 +11438,14 @@ var DEFAULT_SINKS = [
|
|
|
11396
11438
|
// got library
|
|
11397
11439
|
{ method: "get", class: "got", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
11398
11440
|
{ method: "post", class: "got", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
11441
|
+
// got/request npm packages default-export a callable function:
|
|
11442
|
+
// const got = require('got'); got(req.query.url)
|
|
11443
|
+
// const request = require('request'); request(req.query.url, cb)
|
|
11444
|
+
// The classless method names are distinctive enough (`got`, `request`) that
|
|
11445
|
+
// the FP risk is acceptable; both are scoped to JS/TS so they don't leak
|
|
11446
|
+
// into other plugins. (#185 Sprint 55)
|
|
11447
|
+
{ method: "got", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11448
|
+
{ method: "request", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11399
11449
|
// superagent
|
|
11400
11450
|
{ method: "get", class: "superagent", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
11401
11451
|
{ method: "post", class: "superagent", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
@@ -11565,7 +11615,9 @@ var DEFAULT_SINKS = [
|
|
|
11565
11615
|
{ method: "aggregate", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0] },
|
|
11566
11616
|
// pymongo dynamic attribute-access pattern: `db.users.find({...})` — receiver
|
|
11567
11617
|
// class isn't statically known. Method-only entries restricted to Python.
|
|
11568
|
-
// cognium-dev#104 Sprint 22
|
|
11618
|
+
// cognium-dev#104 Sprint 22, #194 Sprint 54 added `find`/`aggregate`.
|
|
11619
|
+
{ method: "find", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11620
|
+
{ method: "aggregate", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11569
11621
|
{ method: "find_one", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11570
11622
|
{ method: "update_one", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0, 1], languages: ["python"] },
|
|
11571
11623
|
{ method: "update_many", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0, 1], languages: ["python"] },
|
|
@@ -12145,10 +12197,50 @@ var PYTHON_TAINTED_PATTERNS = [
|
|
|
12145
12197
|
function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code) {
|
|
12146
12198
|
const sourceLines = code !== void 0 ? code.split("\n") : void 0;
|
|
12147
12199
|
const sources = findSources(calls, types, config.sources, sourceLines, language);
|
|
12148
|
-
const
|
|
12200
|
+
const sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
|
|
12201
|
+
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines);
|
|
12149
12202
|
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
|
|
12150
12203
|
return { sources, sinks, sanitizers };
|
|
12151
12204
|
}
|
|
12205
|
+
function expandPromisifyAliases(patterns, sourceLines, language) {
|
|
12206
|
+
if (!sourceLines || sourceLines.length === 0) return patterns;
|
|
12207
|
+
if (language !== "javascript" && language !== "typescript") return patterns;
|
|
12208
|
+
const PROMISIFY_RE = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:util\s*\.\s*)?promisify\s*\(\s*([A-Za-z_$][\w$]*)\s*\)/;
|
|
12209
|
+
const innerToPattern = /* @__PURE__ */ new Map();
|
|
12210
|
+
for (const p of patterns) {
|
|
12211
|
+
if (p.languages && !p.languages.includes(language)) continue;
|
|
12212
|
+
if (p.class !== void 0) continue;
|
|
12213
|
+
if (!innerToPattern.has(p.method)) innerToPattern.set(p.method, p);
|
|
12214
|
+
}
|
|
12215
|
+
for (const p of patterns) {
|
|
12216
|
+
if (p.languages && !p.languages.includes(language)) continue;
|
|
12217
|
+
if (innerToPattern.has(p.method)) continue;
|
|
12218
|
+
innerToPattern.set(p.method, p);
|
|
12219
|
+
}
|
|
12220
|
+
const aliasPatterns = [];
|
|
12221
|
+
const seenAliases = /* @__PURE__ */ new Set();
|
|
12222
|
+
for (const line of sourceLines) {
|
|
12223
|
+
const m = PROMISIFY_RE.exec(line);
|
|
12224
|
+
if (!m) continue;
|
|
12225
|
+
const aliasName = m[1];
|
|
12226
|
+
const innerName = m[2];
|
|
12227
|
+
if (!aliasName || !innerName) continue;
|
|
12228
|
+
if (seenAliases.has(aliasName)) continue;
|
|
12229
|
+
const template = innerToPattern.get(innerName);
|
|
12230
|
+
if (!template) continue;
|
|
12231
|
+
seenAliases.add(aliasName);
|
|
12232
|
+
aliasPatterns.push({
|
|
12233
|
+
method: aliasName,
|
|
12234
|
+
type: template.type,
|
|
12235
|
+
cwe: template.cwe,
|
|
12236
|
+
severity: template.severity,
|
|
12237
|
+
arg_positions: template.arg_positions,
|
|
12238
|
+
languages: [language],
|
|
12239
|
+
note: `util.promisify alias of ${innerName} \u2014 cognium-dev #187`
|
|
12240
|
+
});
|
|
12241
|
+
}
|
|
12242
|
+
return aliasPatterns.length === 0 ? patterns : patterns.concat(aliasPatterns);
|
|
12243
|
+
}
|
|
12152
12244
|
function attachSourceLineCode(sources, sinks, code) {
|
|
12153
12245
|
const lines = code.split("\n");
|
|
12154
12246
|
for (const s of sources) {
|
|
@@ -12514,6 +12606,41 @@ function isSafeGoExecCommandCall(call, pattern, language) {
|
|
|
12514
12606
|
if (SHELL_PROGRAMS.has(program)) return false;
|
|
12515
12607
|
return true;
|
|
12516
12608
|
}
|
|
12609
|
+
function isSafeJSChildProcessCall(call, pattern, language) {
|
|
12610
|
+
if (language !== "javascript" && language !== "typescript") return false;
|
|
12611
|
+
if (pattern.type !== "command_injection") return false;
|
|
12612
|
+
const m = call.method_name;
|
|
12613
|
+
if (m !== "spawn" && m !== "spawnSync" && m !== "execFile" && m !== "execFileSync") return false;
|
|
12614
|
+
const programArg = call.arguments.find((a) => a.position === 0);
|
|
12615
|
+
if (!programArg) return false;
|
|
12616
|
+
let program;
|
|
12617
|
+
if (programArg.literal !== null && programArg.literal !== void 0) {
|
|
12618
|
+
program = String(programArg.literal).split("/").pop() ?? String(programArg.literal);
|
|
12619
|
+
} else {
|
|
12620
|
+
const expr = (programArg.expression ?? "").trim();
|
|
12621
|
+
if (!(expr.startsWith('"') || expr.startsWith("`") || expr.startsWith("'"))) {
|
|
12622
|
+
return false;
|
|
12623
|
+
}
|
|
12624
|
+
const stripped = expr.slice(1, -1);
|
|
12625
|
+
program = stripped.split("/").pop() ?? stripped;
|
|
12626
|
+
}
|
|
12627
|
+
const SHELL_PROGRAMS = /* @__PURE__ */ new Set([
|
|
12628
|
+
"sh",
|
|
12629
|
+
"bash",
|
|
12630
|
+
"zsh",
|
|
12631
|
+
"dash",
|
|
12632
|
+
"ash",
|
|
12633
|
+
"ksh",
|
|
12634
|
+
"cmd",
|
|
12635
|
+
"cmd.exe",
|
|
12636
|
+
"powershell",
|
|
12637
|
+
"pwsh",
|
|
12638
|
+
"powershell.exe",
|
|
12639
|
+
"pwsh.exe"
|
|
12640
|
+
]);
|
|
12641
|
+
if (SHELL_PROGRAMS.has(program)) return false;
|
|
12642
|
+
return true;
|
|
12643
|
+
}
|
|
12517
12644
|
function isSafeRustCommandCall(call, pattern, language) {
|
|
12518
12645
|
if (language !== "rust") return false;
|
|
12519
12646
|
if (pattern.type !== "command_injection") return false;
|
|
@@ -12728,6 +12855,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12728
12855
|
if (isSafeRustCommandCall(call, pattern, language)) {
|
|
12729
12856
|
continue;
|
|
12730
12857
|
}
|
|
12858
|
+
if (isSafeJSChildProcessCall(call, pattern, language)) {
|
|
12859
|
+
continue;
|
|
12860
|
+
}
|
|
12731
12861
|
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
|
|
12732
12862
|
continue;
|
|
12733
12863
|
}
|
|
@@ -12743,7 +12873,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12743
12873
|
}
|
|
12744
12874
|
}
|
|
12745
12875
|
}
|
|
12746
|
-
if (pattern.type === "code_injection" && (call.method_name === "setInterval" || call.method_name === "setTimeout")) {
|
|
12876
|
+
if (pattern.type === "code_injection" && (call.method_name === "setInterval" || call.method_name === "setTimeout" || call.method_name === "setImmediate")) {
|
|
12747
12877
|
const firstArg = call.arguments.find((a) => a.position === 0);
|
|
12748
12878
|
if (firstArg && isFunctionCallbackArgument(firstArg)) {
|
|
12749
12879
|
continue;
|
|
@@ -13060,6 +13190,11 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
|
|
|
13060
13190
|
if (typeHierarchy && typeHierarchy.couldBeType(call.receiver, pattern.class)) {
|
|
13061
13191
|
return true;
|
|
13062
13192
|
}
|
|
13193
|
+
const resolvedTarget = call.resolution?.target;
|
|
13194
|
+
const expectedClassMethodTail = `${pattern.class}.${pattern.method}`;
|
|
13195
|
+
if (resolvedTarget && (resolvedTarget === expectedClassMethodTail || resolvedTarget.endsWith("." + expectedClassMethodTail))) {
|
|
13196
|
+
return true;
|
|
13197
|
+
}
|
|
13063
13198
|
if (pattern.allow_unresolved_receiver && !call.receiver_type && !call.receiver_type_fqn && call.receiver.includes(".")) {
|
|
13064
13199
|
return true;
|
|
13065
13200
|
}
|
|
@@ -10485,10 +10485,20 @@ var DEFAULT_SINKS = [
|
|
|
10485
10485
|
{ method: "bash", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10486
10486
|
{ method: "shell", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10487
10487
|
{ method: "sh", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10488
|
-
|
|
10488
|
+
// cognium-dev #187 Sprint 54: arg_positions [0, 1] so the JS shell-mode shape
|
|
10489
|
+
// `spawn('sh', ['-c', tainted])` / `execFile('/bin/sh', ['-c', tainted])`
|
|
10490
|
+
// surfaces taint at the argv-array position (arg[1]).
|
|
10491
|
+
{ method: "spawn", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
|
|
10492
|
+
{ method: "spawnSync", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
|
|
10493
|
+
{ method: "execFile", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
|
|
10494
|
+
{ method: "execFileSync", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
|
|
10489
10495
|
{ method: "fork", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10490
10496
|
{ method: "popen", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10491
10497
|
{ method: "system", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10498
|
+
// execa (npm) — parses tainted shell-style strings into program+argv;
|
|
10499
|
+
// arg[0] is shell-injectable. cognium-dev #187 Sprint 54.
|
|
10500
|
+
{ method: "command", class: "execa", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10501
|
+
{ method: "commandSync", class: "execa", languages: ["javascript", "typescript"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10492
10502
|
// Apache Commons Exec
|
|
10493
10503
|
// Note: bare class 'Executor' removed (see comment above) — DefaultExecutor matched explicitly.
|
|
10494
10504
|
{ method: "setCommandline", class: "DefaultExecutor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
@@ -11254,8 +11264,27 @@ var DEFAULT_SINKS = [
|
|
|
11254
11264
|
// SQL query calls are covered by class-specific patterns above (Connection, Pool, Client, JdbcTemplate)
|
|
11255
11265
|
// Note: `raw` is shared with Python (Django ORM) — scoped to JS+TS to avoid leaking.
|
|
11256
11266
|
{ method: "raw", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11267
|
+
// sqlite3 (npm) — Database/Statement methods. The JS plugin resolves
|
|
11268
|
+
// `const db = new sqlite3.Database(...); db.all(sql)` to the resolution
|
|
11269
|
+
// target `Connection.all`, so class-scoped patterns matching `Connection`
|
|
11270
|
+
// hit (see #186 Sprint 55 matcher extension consulting call.resolution.target).
|
|
11271
|
+
// `exec`/`run`/`all`/`get`/`each` follow the same shape.
|
|
11272
|
+
{ method: "all", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
11273
|
+
{ method: "run", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
11274
|
+
{ method: "each", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
11275
|
+
{ method: "get", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
11276
|
+
{ method: "exec", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
11257
11277
|
// Browser DOM XSS sinks
|
|
11258
11278
|
{ method: "setAttribute", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
|
|
11279
|
+
// Angular DomSanitizer.bypassSecurityTrust* — CWE-79 (#184 Sprint 55).
|
|
11280
|
+
// These methods explicitly bypass Angular's built-in sanitizer; passing
|
|
11281
|
+
// tainted strings re-introduces DOM-injection risk. Distinctive method
|
|
11282
|
+
// names — classless + language-scoped is safe.
|
|
11283
|
+
{ method: "bypassSecurityTrustHtml", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11284
|
+
{ method: "bypassSecurityTrustScript", type: "xss", cwe: "CWE-79", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11285
|
+
{ method: "bypassSecurityTrustStyle", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11286
|
+
{ method: "bypassSecurityTrustUrl", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11287
|
+
{ method: "bypassSecurityTrustResourceUrl", type: "xss", cwe: "CWE-79", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11259
11288
|
// Express.js XSS (response methods)
|
|
11260
11289
|
{ method: "send", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
11261
11290
|
{ method: "write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
@@ -11268,6 +11297,19 @@ var DEFAULT_SINKS = [
|
|
|
11268
11297
|
{ method: "runInContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11269
11298
|
{ method: "runInNewContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11270
11299
|
{ method: "runInThisContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11300
|
+
// `new vm.Script(taint)` — Node core `vm` module compiles strings. The
|
|
11301
|
+
// JS plugin emits method_name = 'vm.Script' for the constructor call;
|
|
11302
|
+
// the matcher's dotted-simple-name fallback (taint-matcher.ts:1664) lets
|
|
11303
|
+
// pattern.method = 'Script' hit on method_name = 'vm.Script'. The
|
|
11304
|
+
// `class: 'constructor'` short-circuit accepts the no-receiver shape.
|
|
11305
|
+
// (#188 Sprint 55)
|
|
11306
|
+
{ method: "Script", class: "constructor", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11307
|
+
// `setImmediate(taintedString)` — like setTimeout/setInterval, Node will
|
|
11308
|
+
// evaluate the first argument when it is a string. The callback-shape
|
|
11309
|
+
// suppression in taint-matcher.ts:1342 already covers setTimeout/
|
|
11310
|
+
// setInterval; the Sprint 55 fix extends that gate to setImmediate too.
|
|
11311
|
+
// (#188 Sprint 55)
|
|
11312
|
+
{ method: "setImmediate", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11271
11313
|
// protobufjs Root.parse(schemaText) compiles a textual schema into JS at runtime;
|
|
11272
11314
|
// tainted schema → code execution (CVE-2026-41242). Issue #94.
|
|
11273
11315
|
{ method: "parse", class: "protobuf", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
@@ -11330,6 +11372,14 @@ var DEFAULT_SINKS = [
|
|
|
11330
11372
|
// got library
|
|
11331
11373
|
{ method: "get", class: "got", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
11332
11374
|
{ method: "post", class: "got", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
11375
|
+
// got/request npm packages default-export a callable function:
|
|
11376
|
+
// const got = require('got'); got(req.query.url)
|
|
11377
|
+
// const request = require('request'); request(req.query.url, cb)
|
|
11378
|
+
// The classless method names are distinctive enough (`got`, `request`) that
|
|
11379
|
+
// the FP risk is acceptable; both are scoped to JS/TS so they don't leak
|
|
11380
|
+
// into other plugins. (#185 Sprint 55)
|
|
11381
|
+
{ method: "got", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11382
|
+
{ method: "request", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11333
11383
|
// superagent
|
|
11334
11384
|
{ method: "get", class: "superagent", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
11335
11385
|
{ method: "post", class: "superagent", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
@@ -11499,7 +11549,9 @@ var DEFAULT_SINKS = [
|
|
|
11499
11549
|
{ method: "aggregate", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0] },
|
|
11500
11550
|
// pymongo dynamic attribute-access pattern: `db.users.find({...})` — receiver
|
|
11501
11551
|
// class isn't statically known. Method-only entries restricted to Python.
|
|
11502
|
-
// cognium-dev#104 Sprint 22
|
|
11552
|
+
// cognium-dev#104 Sprint 22, #194 Sprint 54 added `find`/`aggregate`.
|
|
11553
|
+
{ method: "find", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11554
|
+
{ method: "aggregate", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11503
11555
|
{ method: "find_one", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11504
11556
|
{ method: "update_one", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0, 1], languages: ["python"] },
|
|
11505
11557
|
{ method: "update_many", type: "nosql_injection", cwe: "CWE-943", severity: "critical", arg_positions: [0, 1], languages: ["python"] },
|
|
@@ -12079,10 +12131,50 @@ var PYTHON_TAINTED_PATTERNS = [
|
|
|
12079
12131
|
function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code) {
|
|
12080
12132
|
const sourceLines = code !== void 0 ? code.split("\n") : void 0;
|
|
12081
12133
|
const sources = findSources(calls, types, config.sources, sourceLines, language);
|
|
12082
|
-
const
|
|
12134
|
+
const sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
|
|
12135
|
+
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines);
|
|
12083
12136
|
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
|
|
12084
12137
|
return { sources, sinks, sanitizers };
|
|
12085
12138
|
}
|
|
12139
|
+
function expandPromisifyAliases(patterns, sourceLines, language) {
|
|
12140
|
+
if (!sourceLines || sourceLines.length === 0) return patterns;
|
|
12141
|
+
if (language !== "javascript" && language !== "typescript") return patterns;
|
|
12142
|
+
const PROMISIFY_RE = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:util\s*\.\s*)?promisify\s*\(\s*([A-Za-z_$][\w$]*)\s*\)/;
|
|
12143
|
+
const innerToPattern = /* @__PURE__ */ new Map();
|
|
12144
|
+
for (const p of patterns) {
|
|
12145
|
+
if (p.languages && !p.languages.includes(language)) continue;
|
|
12146
|
+
if (p.class !== void 0) continue;
|
|
12147
|
+
if (!innerToPattern.has(p.method)) innerToPattern.set(p.method, p);
|
|
12148
|
+
}
|
|
12149
|
+
for (const p of patterns) {
|
|
12150
|
+
if (p.languages && !p.languages.includes(language)) continue;
|
|
12151
|
+
if (innerToPattern.has(p.method)) continue;
|
|
12152
|
+
innerToPattern.set(p.method, p);
|
|
12153
|
+
}
|
|
12154
|
+
const aliasPatterns = [];
|
|
12155
|
+
const seenAliases = /* @__PURE__ */ new Set();
|
|
12156
|
+
for (const line of sourceLines) {
|
|
12157
|
+
const m = PROMISIFY_RE.exec(line);
|
|
12158
|
+
if (!m) continue;
|
|
12159
|
+
const aliasName = m[1];
|
|
12160
|
+
const innerName = m[2];
|
|
12161
|
+
if (!aliasName || !innerName) continue;
|
|
12162
|
+
if (seenAliases.has(aliasName)) continue;
|
|
12163
|
+
const template = innerToPattern.get(innerName);
|
|
12164
|
+
if (!template) continue;
|
|
12165
|
+
seenAliases.add(aliasName);
|
|
12166
|
+
aliasPatterns.push({
|
|
12167
|
+
method: aliasName,
|
|
12168
|
+
type: template.type,
|
|
12169
|
+
cwe: template.cwe,
|
|
12170
|
+
severity: template.severity,
|
|
12171
|
+
arg_positions: template.arg_positions,
|
|
12172
|
+
languages: [language],
|
|
12173
|
+
note: `util.promisify alias of ${innerName} \u2014 cognium-dev #187`
|
|
12174
|
+
});
|
|
12175
|
+
}
|
|
12176
|
+
return aliasPatterns.length === 0 ? patterns : patterns.concat(aliasPatterns);
|
|
12177
|
+
}
|
|
12086
12178
|
function attachSourceLineCode(sources, sinks, code) {
|
|
12087
12179
|
const lines = code.split("\n");
|
|
12088
12180
|
for (const s of sources) {
|
|
@@ -12448,6 +12540,41 @@ function isSafeGoExecCommandCall(call, pattern, language) {
|
|
|
12448
12540
|
if (SHELL_PROGRAMS.has(program)) return false;
|
|
12449
12541
|
return true;
|
|
12450
12542
|
}
|
|
12543
|
+
function isSafeJSChildProcessCall(call, pattern, language) {
|
|
12544
|
+
if (language !== "javascript" && language !== "typescript") return false;
|
|
12545
|
+
if (pattern.type !== "command_injection") return false;
|
|
12546
|
+
const m = call.method_name;
|
|
12547
|
+
if (m !== "spawn" && m !== "spawnSync" && m !== "execFile" && m !== "execFileSync") return false;
|
|
12548
|
+
const programArg = call.arguments.find((a) => a.position === 0);
|
|
12549
|
+
if (!programArg) return false;
|
|
12550
|
+
let program;
|
|
12551
|
+
if (programArg.literal !== null && programArg.literal !== void 0) {
|
|
12552
|
+
program = String(programArg.literal).split("/").pop() ?? String(programArg.literal);
|
|
12553
|
+
} else {
|
|
12554
|
+
const expr = (programArg.expression ?? "").trim();
|
|
12555
|
+
if (!(expr.startsWith('"') || expr.startsWith("`") || expr.startsWith("'"))) {
|
|
12556
|
+
return false;
|
|
12557
|
+
}
|
|
12558
|
+
const stripped = expr.slice(1, -1);
|
|
12559
|
+
program = stripped.split("/").pop() ?? stripped;
|
|
12560
|
+
}
|
|
12561
|
+
const SHELL_PROGRAMS = /* @__PURE__ */ new Set([
|
|
12562
|
+
"sh",
|
|
12563
|
+
"bash",
|
|
12564
|
+
"zsh",
|
|
12565
|
+
"dash",
|
|
12566
|
+
"ash",
|
|
12567
|
+
"ksh",
|
|
12568
|
+
"cmd",
|
|
12569
|
+
"cmd.exe",
|
|
12570
|
+
"powershell",
|
|
12571
|
+
"pwsh",
|
|
12572
|
+
"powershell.exe",
|
|
12573
|
+
"pwsh.exe"
|
|
12574
|
+
]);
|
|
12575
|
+
if (SHELL_PROGRAMS.has(program)) return false;
|
|
12576
|
+
return true;
|
|
12577
|
+
}
|
|
12451
12578
|
function isSafeRustCommandCall(call, pattern, language) {
|
|
12452
12579
|
if (language !== "rust") return false;
|
|
12453
12580
|
if (pattern.type !== "command_injection") return false;
|
|
@@ -12662,6 +12789,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12662
12789
|
if (isSafeRustCommandCall(call, pattern, language)) {
|
|
12663
12790
|
continue;
|
|
12664
12791
|
}
|
|
12792
|
+
if (isSafeJSChildProcessCall(call, pattern, language)) {
|
|
12793
|
+
continue;
|
|
12794
|
+
}
|
|
12665
12795
|
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
|
|
12666
12796
|
continue;
|
|
12667
12797
|
}
|
|
@@ -12677,7 +12807,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12677
12807
|
}
|
|
12678
12808
|
}
|
|
12679
12809
|
}
|
|
12680
|
-
if (pattern.type === "code_injection" && (call.method_name === "setInterval" || call.method_name === "setTimeout")) {
|
|
12810
|
+
if (pattern.type === "code_injection" && (call.method_name === "setInterval" || call.method_name === "setTimeout" || call.method_name === "setImmediate")) {
|
|
12681
12811
|
const firstArg = call.arguments.find((a) => a.position === 0);
|
|
12682
12812
|
if (firstArg && isFunctionCallbackArgument(firstArg)) {
|
|
12683
12813
|
continue;
|
|
@@ -12994,6 +13124,11 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
|
|
|
12994
13124
|
if (typeHierarchy && typeHierarchy.couldBeType(call.receiver, pattern.class)) {
|
|
12995
13125
|
return true;
|
|
12996
13126
|
}
|
|
13127
|
+
const resolvedTarget = call.resolution?.target;
|
|
13128
|
+
const expectedClassMethodTail = `${pattern.class}.${pattern.method}`;
|
|
13129
|
+
if (resolvedTarget && (resolvedTarget === expectedClassMethodTail || resolvedTarget.endsWith("." + expectedClassMethodTail))) {
|
|
13130
|
+
return true;
|
|
13131
|
+
}
|
|
12997
13132
|
if (pattern.allow_unresolved_receiver && !call.receiver_type && !call.receiver_type_fqn && call.receiver.includes(".")) {
|
|
12998
13133
|
return true;
|
|
12999
13134
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "circle-ir",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.112.0",
|
|
4
4
|
"description": "High-performance Static Application Security Testing (SAST) library for detecting security vulnerabilities through taint analysis",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|