circle-ir 3.108.0 → 3.110.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/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +24 -13
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +496 -0
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +11 -0
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/browser/circle-ir.js +369 -16
- package/dist/core/circle-ir-core.cjs +27 -13
- package/dist/core/circle-ir-core.js +27 -13
- package/package.json +1 -1
|
@@ -12195,21 +12195,32 @@ var DEFAULT_SINKS = [
|
|
|
12195
12195
|
{ method: "from_string", class: "Template", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
12196
12196
|
{ method: "Template", class: "jinja2", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
12197
12197
|
{ method: "Template", class: "mako", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
12198
|
-
// Python Log Injection
|
|
12199
|
-
|
|
12200
|
-
|
|
12201
|
-
|
|
12202
|
-
|
|
12203
|
-
|
|
12198
|
+
// Python Log Injection — cognium-dev #193: positions [0, 1, 2, 3, 4]
|
|
12199
|
+
// cover both the format string (arg 0) and Python `logging`'s positional
|
|
12200
|
+
// *args (1..N), which get rendered into the log line via `%` substitution
|
|
12201
|
+
// (e.g. `log.warning("user=%s", user)` taints via arg 1). Five-arg cap
|
|
12202
|
+
// matches the established explicit-enumeration pattern used elsewhere in
|
|
12203
|
+
// this file.
|
|
12204
|
+
{ method: "info", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
12205
|
+
{ method: "warning", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
12206
|
+
{ method: "warn", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
12207
|
+
{ method: "error", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
12208
|
+
{ method: "debug", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
12209
|
+
{ method: "critical", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
12210
|
+
{ method: "fatal", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
12211
|
+
{ method: "exception", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
12212
|
+
{ method: "log", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [1, 2, 3, 4] },
|
|
12204
12213
|
// Python `logging` module top-level functions (e.g. logging.info(...))
|
|
12205
12214
|
// — cognium-dev#104 Sprint 22: OOP fixtures use `import logging; logging.info(self.msg)`.
|
|
12206
|
-
{ method: "info", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0] },
|
|
12207
|
-
{ method: "warning", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0] },
|
|
12208
|
-
{ method: "
|
|
12209
|
-
{ method: "
|
|
12210
|
-
{ method: "
|
|
12211
|
-
{ method: "
|
|
12212
|
-
{ method: "
|
|
12215
|
+
{ method: "info", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
12216
|
+
{ method: "warning", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
12217
|
+
{ method: "warn", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
12218
|
+
{ method: "error", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
12219
|
+
{ method: "debug", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
12220
|
+
{ method: "critical", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
12221
|
+
{ method: "fatal", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
12222
|
+
{ method: "log", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [1, 2, 3, 4] },
|
|
12223
|
+
{ method: "exception", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
12213
12224
|
// =========================================================================
|
|
12214
12225
|
// Java CWE-Bench Enhancement Patterns (Collection/Builder)
|
|
12215
12226
|
// =========================================================================
|
|
@@ -13859,6 +13870,9 @@ function receiverMightBeClass(receiver, className) {
|
|
|
13859
13870
|
return true;
|
|
13860
13871
|
}
|
|
13861
13872
|
}
|
|
13873
|
+
if (className === "Logger" && /\.getLogger\(.*\)$/.test(receiver)) {
|
|
13874
|
+
return true;
|
|
13875
|
+
}
|
|
13862
13876
|
}
|
|
13863
13877
|
if (receiver.includes("::") && receiver.endsWith(")")) {
|
|
13864
13878
|
const scopedMatch = receiver.match(/^(\w+)::(\w+)\(.*\)$/);
|
|
@@ -24415,6 +24429,25 @@ var JS_XSS_SANITIZERS = [
|
|
|
24415
24429
|
/\bbypassSecurityTrust/
|
|
24416
24430
|
// Angular
|
|
24417
24431
|
];
|
|
24432
|
+
var JS_LOG_INJECTION_SANITIZERS = [
|
|
24433
|
+
/\bstripCrlf\s*\(/,
|
|
24434
|
+
/\bstripCRLF\s*\(/,
|
|
24435
|
+
/\bremoveNewlines\s*\(/,
|
|
24436
|
+
/\bsanitizeLogValue\s*\(/,
|
|
24437
|
+
// Inline CRLF-stripping regex literal: .replace(/[\r\n]/g, '')
|
|
24438
|
+
/\.replace\(\s*\/\[\s*\\r\s*\\n\s*\]\/[gimsu]*\s*,\s*['"`]['"`]\s*\)/,
|
|
24439
|
+
// Inline CRLF sequence: .replace(/\r\n/g, '')
|
|
24440
|
+
/\.replace\(\s*\/\\r\\n\/[gimsu]*\s*,\s*['"`]['"`]\s*\)/
|
|
24441
|
+
];
|
|
24442
|
+
var PY_BUILTIN_LDAP_SANITIZERS = ["escape_filter_chars", "filter_format"];
|
|
24443
|
+
var PY_LDAP_METACHARS = ["(", ")", "=", "*", "\\"];
|
|
24444
|
+
var PY_DEF_RE = /^\s*def\s+([A-Za-z_]\w*)\s*\(\s*([A-Za-z_]\w*)\s*\)\s*:\s*$/;
|
|
24445
|
+
var PY_LDAP_STRIP_RETURN_RE = /^\s*return\s+re\.sub\(\s*r?["']\[([^"'\]]+)\]["']\s*,\s*r?["']["']\s*,\s*([A-Za-z_]\w*)\s*\)\s*$/;
|
|
24446
|
+
var PY_XML_PARSER_HARDENED_RE = /\bXMLParser\s*\([^)]*\bresolve_entities\s*=\s*False\b[^)]*\)/;
|
|
24447
|
+
var PY_SQL_EXEC_METHODS = /* @__PURE__ */ new Set(["execute", "executemany"]);
|
|
24448
|
+
var PY_INLINE_FULLMATCH_RE = /\bre\s*\.\s*fullmatch\s*\(\s*r?"((?:[^"\\]|\\.)*)"/g;
|
|
24449
|
+
var PY_INLINE_MATCH_ANCHORED_RE = /\bre\s*\.\s*match\s*\(\s*r?"\^((?:[^"\\]|\\.)*)\$"/g;
|
|
24450
|
+
var PY_BIND_PLACEHOLDER_RE = /\?|%s|:[A-Za-z_]\w*/;
|
|
24418
24451
|
var DATA_PARSER_TYPES = /* @__PURE__ */ new Set([
|
|
24419
24452
|
"Parser",
|
|
24420
24453
|
// commonmark, airline, picocli, jcommander
|
|
@@ -25125,9 +25158,329 @@ var SinkFilterPass = class {
|
|
|
25125
25158
|
return true;
|
|
25126
25159
|
});
|
|
25127
25160
|
}
|
|
25161
|
+
if (["javascript", "typescript"].includes(language)) {
|
|
25162
|
+
const sourceLines = ctx.code.split("\n");
|
|
25163
|
+
filtered = filtered.filter((sink) => {
|
|
25164
|
+
if (sink.type !== "log_injection") return true;
|
|
25165
|
+
const sinkLineText = sourceLines[sink.line - 1] ?? "";
|
|
25166
|
+
if (JS_LOG_INJECTION_SANITIZERS.some((p) => p.test(sinkLineText))) return false;
|
|
25167
|
+
const varNames = extractSourceIdentifiers(sinkLineText);
|
|
25168
|
+
for (const v of varNames) {
|
|
25169
|
+
if (isAssignedFromSanitizerPattern(sourceLines, sink.line, v, JS_LOG_INJECTION_SANITIZERS)) {
|
|
25170
|
+
return false;
|
|
25171
|
+
}
|
|
25172
|
+
}
|
|
25173
|
+
return true;
|
|
25174
|
+
});
|
|
25175
|
+
}
|
|
25176
|
+
if (language === "python") {
|
|
25177
|
+
const sourceLines = ctx.code.split("\n");
|
|
25178
|
+
const wrappers = findPythonLdapStripWrappers(sourceLines);
|
|
25179
|
+
const allLdapSanitizers = [...PY_BUILTIN_LDAP_SANITIZERS, ...wrappers];
|
|
25180
|
+
if (allLdapSanitizers.length > 0) {
|
|
25181
|
+
const ldapPatterns = allLdapSanitizers.map(
|
|
25182
|
+
(name2) => new RegExp(`\\b${escapeRegex(name2)}\\s*\\(`)
|
|
25183
|
+
);
|
|
25184
|
+
filtered = filtered.filter((sink) => {
|
|
25185
|
+
if (sink.type !== "ldap_injection") return true;
|
|
25186
|
+
const sinkLineText = sourceLines[sink.line - 1] ?? "";
|
|
25187
|
+
if (ldapPatterns.some((p) => p.test(sinkLineText))) return false;
|
|
25188
|
+
const varNames = extractSourceIdentifiers(sinkLineText);
|
|
25189
|
+
for (const v of varNames) {
|
|
25190
|
+
if (isAssignedFromSanitizerPattern(sourceLines, sink.line, v, ldapPatterns)) {
|
|
25191
|
+
return false;
|
|
25192
|
+
}
|
|
25193
|
+
}
|
|
25194
|
+
return true;
|
|
25195
|
+
});
|
|
25196
|
+
}
|
|
25197
|
+
}
|
|
25198
|
+
if (language === "python") {
|
|
25199
|
+
const sourceLines = ctx.code.split("\n");
|
|
25200
|
+
filtered = filtered.filter((sink) => {
|
|
25201
|
+
if (sink.type !== "xxe") return true;
|
|
25202
|
+
const lookback = 30;
|
|
25203
|
+
const lo = Math.max(0, sink.line - 1 - lookback);
|
|
25204
|
+
for (let i2 = sink.line - 2; i2 >= lo; i2--) {
|
|
25205
|
+
const ln = sourceLines[i2] ?? "";
|
|
25206
|
+
if (PY_XML_PARSER_HARDENED_RE.test(ln)) return false;
|
|
25207
|
+
if (/^\s*def\s+\w+\s*\(/.test(ln)) break;
|
|
25208
|
+
}
|
|
25209
|
+
return true;
|
|
25210
|
+
});
|
|
25211
|
+
}
|
|
25212
|
+
if (language === "python") {
|
|
25213
|
+
const sourceLines = ctx.code.split("\n");
|
|
25214
|
+
filtered = filtered.filter((sink) => {
|
|
25215
|
+
if (sink.type !== "sql_injection") return true;
|
|
25216
|
+
const method = sink.method ?? "";
|
|
25217
|
+
if (!PY_SQL_EXEC_METHODS.has(method)) return true;
|
|
25218
|
+
const sinkLineText = sourceLines[sink.line - 1] ?? "";
|
|
25219
|
+
const fstrMatch = sinkLineText.match(/\bf"((?:[^"\\]|\\.)*)"|\bf'((?:[^'\\]|\\.)*)'/);
|
|
25220
|
+
if (!fstrMatch) return true;
|
|
25221
|
+
const fstrBody = fstrMatch[1] ?? fstrMatch[2] ?? "";
|
|
25222
|
+
const { literals, interps } = splitPyFstringTokens(fstrBody);
|
|
25223
|
+
if (interps.length === 0) return true;
|
|
25224
|
+
if (!literals.some((lit) => PY_BIND_PLACEHOLDER_RE.test(lit))) return true;
|
|
25225
|
+
let sawHelperWithGuard = false;
|
|
25226
|
+
for (const interp of interps) {
|
|
25227
|
+
const t = interp.trim();
|
|
25228
|
+
if (t === "") return true;
|
|
25229
|
+
if (/^-?\d+$/.test(t) || /^"(?:[^"\\]|\\.)*"$/.test(t) || /^'(?:[^'\\]|\\.)*'$/.test(t)) {
|
|
25230
|
+
continue;
|
|
25231
|
+
}
|
|
25232
|
+
let helperName = null;
|
|
25233
|
+
const directCall = t.match(/^([A-Za-z_]\w*)\s*\(/);
|
|
25234
|
+
if (directCall) {
|
|
25235
|
+
helperName = directCall[1] ?? null;
|
|
25236
|
+
} else if (/^[A-Za-z_]\w*$/.test(t)) {
|
|
25237
|
+
helperName = findPythonAssignedHelperCall(t, sink.line, sourceLines);
|
|
25238
|
+
if (helperName === null) return true;
|
|
25239
|
+
} else {
|
|
25240
|
+
return true;
|
|
25241
|
+
}
|
|
25242
|
+
const body2 = findPythonFunctionBody(helperName, sourceLines);
|
|
25243
|
+
if (body2 && pythonBodyHasInlineRegexAllowlistRaise(body2)) {
|
|
25244
|
+
sawHelperWithGuard = true;
|
|
25245
|
+
}
|
|
25246
|
+
}
|
|
25247
|
+
if (!sawHelperWithGuard) return true;
|
|
25248
|
+
return false;
|
|
25249
|
+
});
|
|
25250
|
+
}
|
|
25128
25251
|
return { sources, sinks: filtered, sanitizers };
|
|
25129
25252
|
}
|
|
25130
25253
|
};
|
|
25254
|
+
function escapeRegex(s) {
|
|
25255
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
25256
|
+
}
|
|
25257
|
+
var RESERVED_KEYWORDS = /* @__PURE__ */ new Set([
|
|
25258
|
+
// JS/TS
|
|
25259
|
+
"if",
|
|
25260
|
+
"else",
|
|
25261
|
+
"return",
|
|
25262
|
+
"const",
|
|
25263
|
+
"let",
|
|
25264
|
+
"var",
|
|
25265
|
+
"function",
|
|
25266
|
+
"class",
|
|
25267
|
+
"new",
|
|
25268
|
+
"this",
|
|
25269
|
+
"for",
|
|
25270
|
+
"while",
|
|
25271
|
+
"do",
|
|
25272
|
+
"break",
|
|
25273
|
+
"continue",
|
|
25274
|
+
"switch",
|
|
25275
|
+
"case",
|
|
25276
|
+
"default",
|
|
25277
|
+
"try",
|
|
25278
|
+
"catch",
|
|
25279
|
+
"finally",
|
|
25280
|
+
"throw",
|
|
25281
|
+
"typeof",
|
|
25282
|
+
"instanceof",
|
|
25283
|
+
"in",
|
|
25284
|
+
"of",
|
|
25285
|
+
"await",
|
|
25286
|
+
"async",
|
|
25287
|
+
"import",
|
|
25288
|
+
"export",
|
|
25289
|
+
"from",
|
|
25290
|
+
"as",
|
|
25291
|
+
"void",
|
|
25292
|
+
"null",
|
|
25293
|
+
"undefined",
|
|
25294
|
+
"true",
|
|
25295
|
+
"false",
|
|
25296
|
+
"super",
|
|
25297
|
+
"extends",
|
|
25298
|
+
"static",
|
|
25299
|
+
// Python
|
|
25300
|
+
"def",
|
|
25301
|
+
"lambda",
|
|
25302
|
+
"yield",
|
|
25303
|
+
"pass",
|
|
25304
|
+
"with",
|
|
25305
|
+
"global",
|
|
25306
|
+
"nonlocal",
|
|
25307
|
+
"and",
|
|
25308
|
+
"or",
|
|
25309
|
+
"not",
|
|
25310
|
+
"is",
|
|
25311
|
+
"None",
|
|
25312
|
+
"True",
|
|
25313
|
+
"False",
|
|
25314
|
+
"raise",
|
|
25315
|
+
"except",
|
|
25316
|
+
"elif",
|
|
25317
|
+
"self",
|
|
25318
|
+
"cls",
|
|
25319
|
+
"print",
|
|
25320
|
+
// Common stdlib roots that show up everywhere and would balloon scans.
|
|
25321
|
+
"console",
|
|
25322
|
+
"req",
|
|
25323
|
+
"res",
|
|
25324
|
+
"request",
|
|
25325
|
+
"response"
|
|
25326
|
+
]);
|
|
25327
|
+
function extractSourceIdentifiers(text) {
|
|
25328
|
+
const out2 = [];
|
|
25329
|
+
const re = /\b([A-Za-z_]\w+)\b/g;
|
|
25330
|
+
let m;
|
|
25331
|
+
while ((m = re.exec(text)) !== null) {
|
|
25332
|
+
const name2 = m[1];
|
|
25333
|
+
if (!RESERVED_KEYWORDS.has(name2)) out2.push(name2);
|
|
25334
|
+
}
|
|
25335
|
+
return out2;
|
|
25336
|
+
}
|
|
25337
|
+
function isAssignedFromSanitizerPattern(sourceLines, sinkLine, varName, sanitizerPatterns, lookback = 30) {
|
|
25338
|
+
const escapedVar = escapeRegex(varName);
|
|
25339
|
+
const assignRe = new RegExp(
|
|
25340
|
+
`(?:\\b(?:const|let|var|final|String)\\s+)?\\b${escapedVar}\\s*=\\s*(.+)`
|
|
25341
|
+
);
|
|
25342
|
+
const fnBoundaryRe = /^\s*(?:def\s+\w+\s*\(|function\s+\w+\s*\(|\w+\s*[:=]\s*(?:async\s+)?\(?[^)]*\)?\s*=>)/;
|
|
25343
|
+
const lo = Math.max(0, sinkLine - 1 - lookback);
|
|
25344
|
+
for (let i2 = sinkLine - 2; i2 >= lo; i2--) {
|
|
25345
|
+
const ln = sourceLines[i2] ?? "";
|
|
25346
|
+
const m = ln.match(assignRe);
|
|
25347
|
+
if (m) {
|
|
25348
|
+
const rhs = (m[1] ?? "").trim();
|
|
25349
|
+
return sanitizerPatterns.some((p) => p.test(rhs));
|
|
25350
|
+
}
|
|
25351
|
+
if (fnBoundaryRe.test(ln)) break;
|
|
25352
|
+
}
|
|
25353
|
+
return false;
|
|
25354
|
+
}
|
|
25355
|
+
function findPythonLdapStripWrappers(sourceLines) {
|
|
25356
|
+
const out2 = [];
|
|
25357
|
+
for (let i2 = 0; i2 < sourceLines.length; i2++) {
|
|
25358
|
+
const defMatch = sourceLines[i2].match(PY_DEF_RE);
|
|
25359
|
+
if (!defMatch) continue;
|
|
25360
|
+
const fnName = defMatch[1];
|
|
25361
|
+
const param = defMatch[2];
|
|
25362
|
+
for (let j = i2 + 1; j < Math.min(i2 + 4, sourceLines.length); j++) {
|
|
25363
|
+
const body2 = sourceLines[j];
|
|
25364
|
+
if (/^\s*$/.test(body2) || /^\s*#/.test(body2)) continue;
|
|
25365
|
+
const ret = body2.match(PY_LDAP_STRIP_RETURN_RE);
|
|
25366
|
+
if (!ret) break;
|
|
25367
|
+
const charClass = ret[1];
|
|
25368
|
+
const argName = ret[2];
|
|
25369
|
+
if (argName !== param) break;
|
|
25370
|
+
const metaCount = PY_LDAP_METACHARS.filter((c) => charClass.includes(c)).length;
|
|
25371
|
+
if (metaCount >= 3) out2.push(fnName);
|
|
25372
|
+
break;
|
|
25373
|
+
}
|
|
25374
|
+
}
|
|
25375
|
+
return out2;
|
|
25376
|
+
}
|
|
25377
|
+
function splitPyFstringTokens(body2) {
|
|
25378
|
+
const literals = [];
|
|
25379
|
+
const interps = [];
|
|
25380
|
+
let i2 = 0;
|
|
25381
|
+
let cur = "";
|
|
25382
|
+
while (i2 < body2.length) {
|
|
25383
|
+
const ch = body2[i2] ?? "";
|
|
25384
|
+
if (ch === "{" && body2[i2 + 1] === "{") {
|
|
25385
|
+
cur += "{";
|
|
25386
|
+
i2 += 2;
|
|
25387
|
+
continue;
|
|
25388
|
+
}
|
|
25389
|
+
if (ch === "}" && body2[i2 + 1] === "}") {
|
|
25390
|
+
cur += "}";
|
|
25391
|
+
i2 += 2;
|
|
25392
|
+
continue;
|
|
25393
|
+
}
|
|
25394
|
+
if (ch === "{") {
|
|
25395
|
+
literals.push(cur);
|
|
25396
|
+
cur = "";
|
|
25397
|
+
i2++;
|
|
25398
|
+
let depth = 1;
|
|
25399
|
+
let expr = "";
|
|
25400
|
+
while (i2 < body2.length && depth > 0) {
|
|
25401
|
+
const ch2 = body2[i2] ?? "";
|
|
25402
|
+
if (ch2 === "{") {
|
|
25403
|
+
depth++;
|
|
25404
|
+
expr += ch2;
|
|
25405
|
+
i2++;
|
|
25406
|
+
continue;
|
|
25407
|
+
}
|
|
25408
|
+
if (ch2 === "}") {
|
|
25409
|
+
depth--;
|
|
25410
|
+
if (depth === 0) {
|
|
25411
|
+
i2++;
|
|
25412
|
+
break;
|
|
25413
|
+
}
|
|
25414
|
+
expr += ch2;
|
|
25415
|
+
i2++;
|
|
25416
|
+
continue;
|
|
25417
|
+
}
|
|
25418
|
+
expr += ch2;
|
|
25419
|
+
i2++;
|
|
25420
|
+
}
|
|
25421
|
+
const colonIdx = expr.indexOf(":");
|
|
25422
|
+
const interp = colonIdx >= 0 ? expr.substring(0, colonIdx) : expr;
|
|
25423
|
+
interps.push(interp);
|
|
25424
|
+
continue;
|
|
25425
|
+
}
|
|
25426
|
+
cur += ch;
|
|
25427
|
+
i2++;
|
|
25428
|
+
}
|
|
25429
|
+
literals.push(cur);
|
|
25430
|
+
return { literals, interps };
|
|
25431
|
+
}
|
|
25432
|
+
function findPythonAssignedHelperCall(varName, sinkLine, sourceLines) {
|
|
25433
|
+
if (!/^[A-Za-z_]\w*$/.test(varName)) return null;
|
|
25434
|
+
const lo = Math.max(0, sinkLine - 31);
|
|
25435
|
+
const assignRe = new RegExp(
|
|
25436
|
+
`^\\s*${varName}\\s*=\\s*([A-Za-z_]\\w*)\\s*\\(`
|
|
25437
|
+
);
|
|
25438
|
+
for (let i2 = sinkLine - 2; i2 >= lo; i2--) {
|
|
25439
|
+
const ln = sourceLines[i2] ?? "";
|
|
25440
|
+
const m = ln.match(assignRe);
|
|
25441
|
+
if (m) return m[1] ?? null;
|
|
25442
|
+
}
|
|
25443
|
+
return null;
|
|
25444
|
+
}
|
|
25445
|
+
function findPythonFunctionBody(funcName, sourceLines) {
|
|
25446
|
+
if (!/^[A-Za-z_]\w*$/.test(funcName)) return null;
|
|
25447
|
+
const sigRe = new RegExp(`^(\\s*)def\\s+${funcName}\\s*\\(`);
|
|
25448
|
+
for (let i2 = 0; i2 < sourceLines.length; i2++) {
|
|
25449
|
+
const ln = sourceLines[i2] ?? "";
|
|
25450
|
+
const m = ln.match(sigRe);
|
|
25451
|
+
if (!m) continue;
|
|
25452
|
+
const baseIndent = (m[1] ?? "").length;
|
|
25453
|
+
const body2 = [];
|
|
25454
|
+
for (let j = i2 + 1; j < sourceLines.length; j++) {
|
|
25455
|
+
const ln2 = sourceLines[j] ?? "";
|
|
25456
|
+
if (ln2.trim() === "") {
|
|
25457
|
+
body2.push(ln2);
|
|
25458
|
+
continue;
|
|
25459
|
+
}
|
|
25460
|
+
const indentMatch = ln2.match(/^(\s*)/);
|
|
25461
|
+
const indent = (indentMatch?.[1] ?? "").length;
|
|
25462
|
+
if (indent <= baseIndent) break;
|
|
25463
|
+
body2.push(ln2);
|
|
25464
|
+
}
|
|
25465
|
+
return body2;
|
|
25466
|
+
}
|
|
25467
|
+
return null;
|
|
25468
|
+
}
|
|
25469
|
+
function pythonBodyHasInlineRegexAllowlistRaise(bodyLines) {
|
|
25470
|
+
const stripped = bodyLines.map((ln) => ln.replace(/#.*$/, ""));
|
|
25471
|
+
const text = stripped.join("\n");
|
|
25472
|
+
if (!/\braise\s+/.test(text)) return false;
|
|
25473
|
+
PY_INLINE_FULLMATCH_RE.lastIndex = 0;
|
|
25474
|
+
let m;
|
|
25475
|
+
while ((m = PY_INLINE_FULLMATCH_RE.exec(text)) !== null) {
|
|
25476
|
+
if (isImplicitlyAnchoredAllowlistRegex(m[1] ?? "")) return true;
|
|
25477
|
+
}
|
|
25478
|
+
PY_INLINE_MATCH_ANCHORED_RE.lastIndex = 0;
|
|
25479
|
+
while ((m = PY_INLINE_MATCH_ANCHORED_RE.exec(text)) !== null) {
|
|
25480
|
+
if (isImplicitlyAnchoredAllowlistRegex(m[1] ?? "")) return true;
|
|
25481
|
+
}
|
|
25482
|
+
return false;
|
|
25483
|
+
}
|
|
25131
25484
|
function evalArithmetic(input) {
|
|
25132
25485
|
let pos = 0;
|
|
25133
25486
|
const len = input.length;
|
|
@@ -27171,7 +27524,7 @@ var NullDerefPass = class {
|
|
|
27171
27524
|
};
|
|
27172
27525
|
|
|
27173
27526
|
// src/analysis/passes/resource-leak-pass.ts
|
|
27174
|
-
function
|
|
27527
|
+
function escapeRegex2(s) {
|
|
27175
27528
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
27176
27529
|
}
|
|
27177
27530
|
var FACTORY_METHOD_NAME_RE = /^(?:open|create|new|get|make|build)[A-Z]/;
|
|
@@ -27334,7 +27687,7 @@ var ResourceLeakPass = class {
|
|
|
27334
27687
|
* ownership to the caller (caller is responsible for close).
|
|
27335
27688
|
*/
|
|
27336
27689
|
isReturnedToCaller(lines, variable, fromLine, toLine) {
|
|
27337
|
-
const varRe = new RegExp(`\\breturn\\b[^;]*\\b${
|
|
27690
|
+
const varRe = new RegExp(`\\breturn\\b[^;]*\\b${escapeRegex2(variable)}\\b`);
|
|
27338
27691
|
for (let l = fromLine; l <= toLine && l <= lines.length; l++) {
|
|
27339
27692
|
if (varRe.test(lines[l - 1] ?? "")) return true;
|
|
27340
27693
|
}
|
|
@@ -27347,7 +27700,7 @@ var ResourceLeakPass = class {
|
|
|
27347
27700
|
*/
|
|
27348
27701
|
fieldStoredName(lines, variable, openLine, toLine) {
|
|
27349
27702
|
const thisAssignRe = new RegExp(
|
|
27350
|
-
`\\bthis\\s*\\.\\s*(\\w+)\\s*=\\s*${
|
|
27703
|
+
`\\bthis\\s*\\.\\s*(\\w+)\\s*=\\s*${escapeRegex2(variable)}\\b`
|
|
27351
27704
|
);
|
|
27352
27705
|
for (let l = openLine; l <= toLine && l <= lines.length; l++) {
|
|
27353
27706
|
const m = thisAssignRe.exec(lines[l - 1] ?? "");
|
|
@@ -11577,21 +11577,32 @@ var DEFAULT_SINKS = [
|
|
|
11577
11577
|
{ method: "from_string", class: "Template", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11578
11578
|
{ method: "Template", class: "jinja2", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11579
11579
|
{ method: "Template", class: "mako", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11580
|
-
// Python Log Injection
|
|
11581
|
-
|
|
11582
|
-
|
|
11583
|
-
|
|
11584
|
-
|
|
11585
|
-
|
|
11580
|
+
// Python Log Injection — cognium-dev #193: positions [0, 1, 2, 3, 4]
|
|
11581
|
+
// cover both the format string (arg 0) and Python `logging`'s positional
|
|
11582
|
+
// *args (1..N), which get rendered into the log line via `%` substitution
|
|
11583
|
+
// (e.g. `log.warning("user=%s", user)` taints via arg 1). Five-arg cap
|
|
11584
|
+
// matches the established explicit-enumeration pattern used elsewhere in
|
|
11585
|
+
// this file.
|
|
11586
|
+
{ method: "info", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11587
|
+
{ method: "warning", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11588
|
+
{ method: "warn", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11589
|
+
{ method: "error", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11590
|
+
{ method: "debug", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11591
|
+
{ method: "critical", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11592
|
+
{ method: "fatal", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11593
|
+
{ method: "exception", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11594
|
+
{ method: "log", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [1, 2, 3, 4] },
|
|
11586
11595
|
// Python `logging` module top-level functions (e.g. logging.info(...))
|
|
11587
11596
|
// — cognium-dev#104 Sprint 22: OOP fixtures use `import logging; logging.info(self.msg)`.
|
|
11588
|
-
{ method: "info", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0] },
|
|
11589
|
-
{ method: "warning", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0] },
|
|
11590
|
-
{ method: "
|
|
11591
|
-
{ method: "
|
|
11592
|
-
{ method: "
|
|
11593
|
-
{ method: "
|
|
11594
|
-
{ method: "
|
|
11597
|
+
{ method: "info", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11598
|
+
{ method: "warning", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11599
|
+
{ method: "warn", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11600
|
+
{ method: "error", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11601
|
+
{ method: "debug", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11602
|
+
{ method: "critical", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11603
|
+
{ method: "fatal", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11604
|
+
{ method: "log", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [1, 2, 3, 4] },
|
|
11605
|
+
{ method: "exception", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11595
11606
|
// =========================================================================
|
|
11596
11607
|
// Java CWE-Bench Enhancement Patterns (Collection/Builder)
|
|
11597
11608
|
// =========================================================================
|
|
@@ -13154,6 +13165,9 @@ function receiverMightBeClass(receiver, className) {
|
|
|
13154
13165
|
return true;
|
|
13155
13166
|
}
|
|
13156
13167
|
}
|
|
13168
|
+
if (className === "Logger" && /\.getLogger\(.*\)$/.test(receiver)) {
|
|
13169
|
+
return true;
|
|
13170
|
+
}
|
|
13157
13171
|
}
|
|
13158
13172
|
if (receiver.includes("::") && receiver.endsWith(")")) {
|
|
13159
13173
|
const scopedMatch = receiver.match(/^(\w+)::(\w+)\(.*\)$/);
|
|
@@ -11511,21 +11511,32 @@ var DEFAULT_SINKS = [
|
|
|
11511
11511
|
{ method: "from_string", class: "Template", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11512
11512
|
{ method: "Template", class: "jinja2", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11513
11513
|
{ method: "Template", class: "mako", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11514
|
-
// Python Log Injection
|
|
11515
|
-
|
|
11516
|
-
|
|
11517
|
-
|
|
11518
|
-
|
|
11519
|
-
|
|
11514
|
+
// Python Log Injection — cognium-dev #193: positions [0, 1, 2, 3, 4]
|
|
11515
|
+
// cover both the format string (arg 0) and Python `logging`'s positional
|
|
11516
|
+
// *args (1..N), which get rendered into the log line via `%` substitution
|
|
11517
|
+
// (e.g. `log.warning("user=%s", user)` taints via arg 1). Five-arg cap
|
|
11518
|
+
// matches the established explicit-enumeration pattern used elsewhere in
|
|
11519
|
+
// this file.
|
|
11520
|
+
{ method: "info", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11521
|
+
{ method: "warning", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11522
|
+
{ method: "warn", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11523
|
+
{ method: "error", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11524
|
+
{ method: "debug", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11525
|
+
{ method: "critical", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11526
|
+
{ method: "fatal", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11527
|
+
{ method: "exception", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11528
|
+
{ method: "log", class: "logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [1, 2, 3, 4] },
|
|
11520
11529
|
// Python `logging` module top-level functions (e.g. logging.info(...))
|
|
11521
11530
|
// — cognium-dev#104 Sprint 22: OOP fixtures use `import logging; logging.info(self.msg)`.
|
|
11522
|
-
{ method: "info", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0] },
|
|
11523
|
-
{ method: "warning", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0] },
|
|
11524
|
-
{ method: "
|
|
11525
|
-
{ method: "
|
|
11526
|
-
{ method: "
|
|
11527
|
-
{ method: "
|
|
11528
|
-
{ method: "
|
|
11531
|
+
{ method: "info", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11532
|
+
{ method: "warning", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11533
|
+
{ method: "warn", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11534
|
+
{ method: "error", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11535
|
+
{ method: "debug", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11536
|
+
{ method: "critical", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11537
|
+
{ method: "fatal", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11538
|
+
{ method: "log", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [1, 2, 3, 4] },
|
|
11539
|
+
{ method: "exception", class: "logging", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3, 4] },
|
|
11529
11540
|
// =========================================================================
|
|
11530
11541
|
// Java CWE-Bench Enhancement Patterns (Collection/Builder)
|
|
11531
11542
|
// =========================================================================
|
|
@@ -13088,6 +13099,9 @@ function receiverMightBeClass(receiver, className) {
|
|
|
13088
13099
|
return true;
|
|
13089
13100
|
}
|
|
13090
13101
|
}
|
|
13102
|
+
if (className === "Logger" && /\.getLogger\(.*\)$/.test(receiver)) {
|
|
13103
|
+
return true;
|
|
13104
|
+
}
|
|
13091
13105
|
}
|
|
13092
13106
|
if (receiver.includes("::") && receiver.endsWith(")")) {
|
|
13093
13107
|
const scopedMatch = receiver.match(/^(\w+)::(\w+)\(.*\)$/);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "circle-ir",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.110.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",
|