circle-ir 3.111.0 → 3.113.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 +54 -0
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/passes/insecure-cookie-pass.d.ts +2 -0
- package/dist/analysis/passes/insecure-cookie-pass.d.ts.map +1 -1
- package/dist/analysis/passes/insecure-cookie-pass.js +107 -5
- package/dist/analysis/passes/insecure-cookie-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +21 -6
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/browser/circle-ir.js +128 -4
- package/dist/core/circle-ir-core.cjs +60 -1
- package/dist/core/circle-ir-core.js +60 -1
- package/package.json +1 -1
|
@@ -11948,8 +11948,27 @@ var DEFAULT_SINKS = [
|
|
|
11948
11948
|
// SQL query calls are covered by class-specific patterns above (Connection, Pool, Client, JdbcTemplate)
|
|
11949
11949
|
// Note: `raw` is shared with Python (Django ORM) — scoped to JS+TS to avoid leaking.
|
|
11950
11950
|
{ method: "raw", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11951
|
+
// sqlite3 (npm) — Database/Statement methods. The JS plugin resolves
|
|
11952
|
+
// `const db = new sqlite3.Database(...); db.all(sql)` to the resolution
|
|
11953
|
+
// target `Connection.all`, so class-scoped patterns matching `Connection`
|
|
11954
|
+
// hit (see #186 Sprint 55 matcher extension consulting call.resolution.target).
|
|
11955
|
+
// `exec`/`run`/`all`/`get`/`each` follow the same shape.
|
|
11956
|
+
{ method: "all", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
11957
|
+
{ method: "run", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
11958
|
+
{ method: "each", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
11959
|
+
{ method: "get", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
11960
|
+
{ method: "exec", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
11951
11961
|
// Browser DOM XSS sinks
|
|
11952
11962
|
{ method: "setAttribute", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
|
|
11963
|
+
// Angular DomSanitizer.bypassSecurityTrust* — CWE-79 (#184 Sprint 55).
|
|
11964
|
+
// These methods explicitly bypass Angular's built-in sanitizer; passing
|
|
11965
|
+
// tainted strings re-introduces DOM-injection risk. Distinctive method
|
|
11966
|
+
// names — classless + language-scoped is safe.
|
|
11967
|
+
{ method: "bypassSecurityTrustHtml", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11968
|
+
{ method: "bypassSecurityTrustScript", type: "xss", cwe: "CWE-79", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11969
|
+
{ method: "bypassSecurityTrustStyle", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11970
|
+
{ method: "bypassSecurityTrustUrl", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11971
|
+
{ method: "bypassSecurityTrustResourceUrl", type: "xss", cwe: "CWE-79", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11953
11972
|
// Express.js XSS (response methods)
|
|
11954
11973
|
{ method: "send", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
11955
11974
|
{ method: "write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
@@ -11962,6 +11981,19 @@ var DEFAULT_SINKS = [
|
|
|
11962
11981
|
{ method: "runInContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11963
11982
|
{ method: "runInNewContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11964
11983
|
{ method: "runInThisContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11984
|
+
// `new vm.Script(taint)` — Node core `vm` module compiles strings. The
|
|
11985
|
+
// JS plugin emits method_name = 'vm.Script' for the constructor call;
|
|
11986
|
+
// the matcher's dotted-simple-name fallback (taint-matcher.ts:1664) lets
|
|
11987
|
+
// pattern.method = 'Script' hit on method_name = 'vm.Script'. The
|
|
11988
|
+
// `class: 'constructor'` short-circuit accepts the no-receiver shape.
|
|
11989
|
+
// (#188 Sprint 55)
|
|
11990
|
+
{ method: "Script", class: "constructor", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11991
|
+
// `setImmediate(taintedString)` — like setTimeout/setInterval, Node will
|
|
11992
|
+
// evaluate the first argument when it is a string. The callback-shape
|
|
11993
|
+
// suppression in taint-matcher.ts:1342 already covers setTimeout/
|
|
11994
|
+
// setInterval; the Sprint 55 fix extends that gate to setImmediate too.
|
|
11995
|
+
// (#188 Sprint 55)
|
|
11996
|
+
{ method: "setImmediate", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11965
11997
|
// protobufjs Root.parse(schemaText) compiles a textual schema into JS at runtime;
|
|
11966
11998
|
// tainted schema → code execution (CVE-2026-41242). Issue #94.
|
|
11967
11999
|
{ method: "parse", class: "protobuf", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
@@ -12024,6 +12056,14 @@ var DEFAULT_SINKS = [
|
|
|
12024
12056
|
// got library
|
|
12025
12057
|
{ method: "get", class: "got", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
12026
12058
|
{ method: "post", class: "got", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
12059
|
+
// got/request npm packages default-export a callable function:
|
|
12060
|
+
// const got = require('got'); got(req.query.url)
|
|
12061
|
+
// const request = require('request'); request(req.query.url, cb)
|
|
12062
|
+
// The classless method names are distinctive enough (`got`, `request`) that
|
|
12063
|
+
// the FP risk is acceptable; both are scoped to JS/TS so they don't leak
|
|
12064
|
+
// into other plugins. (#185 Sprint 55)
|
|
12065
|
+
{ method: "got", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12066
|
+
{ method: "request", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
12027
12067
|
// superagent
|
|
12028
12068
|
{ method: "get", class: "superagent", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
12029
12069
|
{ method: "post", class: "superagent", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
@@ -12100,6 +12140,11 @@ var DEFAULT_SINKS = [
|
|
|
12100
12140
|
{ method: "exec", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
12101
12141
|
{ method: "compile", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
12102
12142
|
{ method: "__import__", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
12143
|
+
// Python dynamic import — `importlib.import_module(taint)` parallels Java's
|
|
12144
|
+
// `Class.forName`. The bare `__import__` entry above also matches the
|
|
12145
|
+
// `importlib.__import__` form because the sink-matcher is class-agnostic
|
|
12146
|
+
// when a classless entry exists. Sprint 56 #183.
|
|
12147
|
+
{ method: "import_module", class: "importlib", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
12103
12148
|
// Python Deserialization — language-scoped so the lowercase `yaml` / `pickle`
|
|
12104
12149
|
// module names don't collide with Java locals named `yaml` (SnakeYAML usage).
|
|
12105
12150
|
{ method: "loads", class: "pickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
@@ -12357,6 +12402,15 @@ var DEFAULT_SINKS = [
|
|
|
12357
12402
|
// Standard library logging
|
|
12358
12403
|
{ method: "println!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2] },
|
|
12359
12404
|
{ method: "eprintln!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2] },
|
|
12405
|
+
// log:: namespaced forms — the Rust macro extractor preserves the full
|
|
12406
|
+
// path prefix in `method_name` (`log::info!`), so the bare entries above
|
|
12407
|
+
// only match the imported form `use log::info; info!(...)`. Sprint 56 #182 Slice A.
|
|
12408
|
+
{ method: "log::info!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2], languages: ["rust"] },
|
|
12409
|
+
{ method: "log::warn!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2], languages: ["rust"] },
|
|
12410
|
+
{ method: "log::error!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2], languages: ["rust"] },
|
|
12411
|
+
{ method: "log::debug!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2], languages: ["rust"] },
|
|
12412
|
+
{ method: "log::trace!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2], languages: ["rust"] },
|
|
12413
|
+
{ method: "log::log!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2], languages: ["rust"] },
|
|
12360
12414
|
// Rust sqlx SQL Injection
|
|
12361
12415
|
{ method: "query", class: "sqlx", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
|
|
12362
12416
|
{ method: "query_as", class: "sqlx", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
|
|
@@ -13538,7 +13592,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
13538
13592
|
}
|
|
13539
13593
|
}
|
|
13540
13594
|
}
|
|
13541
|
-
if (pattern.type === "code_injection" && (call.method_name === "setInterval" || call.method_name === "setTimeout")) {
|
|
13595
|
+
if (pattern.type === "code_injection" && (call.method_name === "setInterval" || call.method_name === "setTimeout" || call.method_name === "setImmediate")) {
|
|
13542
13596
|
const firstArg = call.arguments.find((a) => a.position === 0);
|
|
13543
13597
|
if (firstArg && isFunctionCallbackArgument(firstArg)) {
|
|
13544
13598
|
continue;
|
|
@@ -13855,6 +13909,11 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
|
|
|
13855
13909
|
if (typeHierarchy && typeHierarchy.couldBeType(call.receiver, pattern.class)) {
|
|
13856
13910
|
return true;
|
|
13857
13911
|
}
|
|
13912
|
+
const resolvedTarget = call.resolution?.target;
|
|
13913
|
+
const expectedClassMethodTail = `${pattern.class}.${pattern.method}`;
|
|
13914
|
+
if (resolvedTarget && (resolvedTarget === expectedClassMethodTail || resolvedTarget.endsWith("." + expectedClassMethodTail))) {
|
|
13915
|
+
return true;
|
|
13916
|
+
}
|
|
13858
13917
|
if (pattern.allow_unresolved_receiver && !call.receiver_type && !call.receiver_type_fqn && call.receiver.includes(".")) {
|
|
13859
13918
|
return true;
|
|
13860
13919
|
}
|
|
@@ -31280,6 +31339,9 @@ var PY_SECURE_TRUE_RE = /\bsecure\s*=\s*True\b/;
|
|
|
31280
31339
|
var PY_HTTPONLY_TRUE_RE = /\bhttponly\s*=\s*True\b/i;
|
|
31281
31340
|
var JAVA_SET_SECURE_TRUE_RE = /\.setSecure\s*\(\s*true\s*\)/;
|
|
31282
31341
|
var JAVA_SET_HTTPONLY_TRUE_RE = /\.setHttpOnly\s*\(\s*true\s*\)/;
|
|
31342
|
+
var GO_SECURE_TRUE_RE = /\bSecure\s*:\s*true\b/;
|
|
31343
|
+
var GO_HTTPONLY_TRUE_RE = /\bHttpOnly\s*:\s*true\b/;
|
|
31344
|
+
var RUST_SET_COOKIE_MACRO_RE = /(format!|write!|writeln!)\s*\(([^()]*Set-Cookie[^()]*)\)/gis;
|
|
31283
31345
|
var InsecureCookiePass = class {
|
|
31284
31346
|
name = "insecure-cookie";
|
|
31285
31347
|
category = "security";
|
|
@@ -31310,6 +31372,19 @@ var InsecureCookiePass = class {
|
|
|
31310
31372
|
insecureCookies.push(det);
|
|
31311
31373
|
this.emit(ctx, file, det, "java");
|
|
31312
31374
|
}
|
|
31375
|
+
} else if (language === "go") {
|
|
31376
|
+
for (const call of graph.ir.calls) {
|
|
31377
|
+
const det = this.detectGo(call);
|
|
31378
|
+
if (!det) continue;
|
|
31379
|
+
insecureCookies.push(det);
|
|
31380
|
+
this.emit(ctx, file, det, "go");
|
|
31381
|
+
}
|
|
31382
|
+
} else if (language === "rust") {
|
|
31383
|
+
const dets = this.detectRustSetCookieFormat(code);
|
|
31384
|
+
for (const det of dets) {
|
|
31385
|
+
insecureCookies.push(det);
|
|
31386
|
+
this.emit(ctx, file, det, "rust");
|
|
31387
|
+
}
|
|
31313
31388
|
}
|
|
31314
31389
|
return { insecureCookies };
|
|
31315
31390
|
}
|
|
@@ -31369,11 +31444,60 @@ var InsecureCookiePass = class {
|
|
|
31369
31444
|
optionsPresent: false
|
|
31370
31445
|
};
|
|
31371
31446
|
}
|
|
31447
|
+
// ---------------- Go ----------------
|
|
31448
|
+
detectGo(call) {
|
|
31449
|
+
if (call.method_name !== "SetCookie") return null;
|
|
31450
|
+
if ((call.receiver ?? "") !== "http") return null;
|
|
31451
|
+
if (call.arguments.length < 2) return null;
|
|
31452
|
+
const cookieArg = call.arguments.find((a) => a.position === 1);
|
|
31453
|
+
const cookieExpr = (cookieArg?.expression ?? "").trim();
|
|
31454
|
+
if (!cookieExpr.includes("{") || !cookieExpr.includes("}")) return null;
|
|
31455
|
+
const missingSecure = !GO_SECURE_TRUE_RE.test(cookieExpr);
|
|
31456
|
+
const missingHttpOnly = !GO_HTTPONLY_TRUE_RE.test(cookieExpr);
|
|
31457
|
+
if (!missingSecure && !missingHttpOnly) return null;
|
|
31458
|
+
return {
|
|
31459
|
+
line: call.location.line,
|
|
31460
|
+
receiver: "http.SetCookie",
|
|
31461
|
+
missingSecure,
|
|
31462
|
+
missingHttpOnly,
|
|
31463
|
+
optionsPresent: true
|
|
31464
|
+
};
|
|
31465
|
+
}
|
|
31466
|
+
// ---------------- Rust ----------------
|
|
31467
|
+
detectRustSetCookieFormat(code) {
|
|
31468
|
+
const out2 = [];
|
|
31469
|
+
const re = new RegExp(RUST_SET_COOKIE_MACRO_RE.source, RUST_SET_COOKIE_MACRO_RE.flags);
|
|
31470
|
+
let m;
|
|
31471
|
+
while ((m = re.exec(code)) !== null) {
|
|
31472
|
+
const macro = m[1] ?? "";
|
|
31473
|
+
const body2 = m[2] ?? "";
|
|
31474
|
+
const missingSecure = !/\bSecure\b/.test(body2);
|
|
31475
|
+
const missingHttpOnly = !/\bHttpOnly\b/.test(body2);
|
|
31476
|
+
if (!missingSecure && !missingHttpOnly) continue;
|
|
31477
|
+
const line = code.slice(0, m.index).split("\n").length;
|
|
31478
|
+
out2.push({
|
|
31479
|
+
line,
|
|
31480
|
+
receiver: macro,
|
|
31481
|
+
missingSecure,
|
|
31482
|
+
missingHttpOnly,
|
|
31483
|
+
optionsPresent: true
|
|
31484
|
+
});
|
|
31485
|
+
}
|
|
31486
|
+
return out2;
|
|
31487
|
+
}
|
|
31372
31488
|
emit(ctx, file, det, flavor) {
|
|
31373
31489
|
const missing = [];
|
|
31374
|
-
if (det.missingSecure)
|
|
31375
|
-
|
|
31376
|
-
|
|
31490
|
+
if (det.missingSecure) {
|
|
31491
|
+
missing.push(
|
|
31492
|
+
flavor === "js" ? "`secure: true`" : flavor === "python" ? "`secure=True`" : flavor === "java" ? "`setSecure(true)`" : flavor === "go" ? "`Secure: true`" : "`Secure` attribute"
|
|
31493
|
+
);
|
|
31494
|
+
}
|
|
31495
|
+
if (det.missingHttpOnly) {
|
|
31496
|
+
missing.push(
|
|
31497
|
+
flavor === "js" ? "`httpOnly: true`" : flavor === "python" ? "`httponly=True`" : flavor === "java" ? "`setHttpOnly(true)`" : flavor === "go" ? "`HttpOnly: true`" : "`HttpOnly` attribute"
|
|
31498
|
+
);
|
|
31499
|
+
}
|
|
31500
|
+
const fix = flavor === "js" ? 'Pass `{ secure: true, httpOnly: true, sameSite: "lax" }` as the third argument to `res.cookie()`.' : flavor === "python" ? 'Pass `secure=True, httponly=True, samesite="Lax"` to `response.set_cookie(...)`.' : flavor === "java" ? "After constructing the cookie, call `cookie.setSecure(true)` and `cookie.setHttpOnly(true)` before adding it to the response." : flavor === "go" ? "Set `Secure: true` and `HttpOnly: true` on the `http.Cookie` struct literal passed to `http.SetCookie`." : "Append `; Secure; HttpOnly` to the `Set-Cookie` header string.";
|
|
31377
31501
|
ctx.addFinding({
|
|
31378
31502
|
id: `${this.name}-${file}-${det.line}`,
|
|
31379
31503
|
pass: this.name,
|
|
@@ -11330,8 +11330,27 @@ var DEFAULT_SINKS = [
|
|
|
11330
11330
|
// SQL query calls are covered by class-specific patterns above (Connection, Pool, Client, JdbcTemplate)
|
|
11331
11331
|
// Note: `raw` is shared with Python (Django ORM) — scoped to JS+TS to avoid leaking.
|
|
11332
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 },
|
|
11333
11343
|
// Browser DOM XSS sinks
|
|
11334
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"] },
|
|
11335
11354
|
// Express.js XSS (response methods)
|
|
11336
11355
|
{ method: "send", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
11337
11356
|
{ method: "write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
@@ -11344,6 +11363,19 @@ var DEFAULT_SINKS = [
|
|
|
11344
11363
|
{ method: "runInContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11345
11364
|
{ method: "runInNewContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11346
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"] },
|
|
11347
11379
|
// protobufjs Root.parse(schemaText) compiles a textual schema into JS at runtime;
|
|
11348
11380
|
// tainted schema → code execution (CVE-2026-41242). Issue #94.
|
|
11349
11381
|
{ method: "parse", class: "protobuf", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
@@ -11406,6 +11438,14 @@ var DEFAULT_SINKS = [
|
|
|
11406
11438
|
// got library
|
|
11407
11439
|
{ method: "get", class: "got", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
11408
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"] },
|
|
11409
11449
|
// superagent
|
|
11410
11450
|
{ method: "get", class: "superagent", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
11411
11451
|
{ method: "post", class: "superagent", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
@@ -11482,6 +11522,11 @@ var DEFAULT_SINKS = [
|
|
|
11482
11522
|
{ method: "exec", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11483
11523
|
{ method: "compile", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
11484
11524
|
{ method: "__import__", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
11525
|
+
// Python dynamic import — `importlib.import_module(taint)` parallels Java's
|
|
11526
|
+
// `Class.forName`. The bare `__import__` entry above also matches the
|
|
11527
|
+
// `importlib.__import__` form because the sink-matcher is class-agnostic
|
|
11528
|
+
// when a classless entry exists. Sprint 56 #183.
|
|
11529
|
+
{ method: "import_module", class: "importlib", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
11485
11530
|
// Python Deserialization — language-scoped so the lowercase `yaml` / `pickle`
|
|
11486
11531
|
// module names don't collide with Java locals named `yaml` (SnakeYAML usage).
|
|
11487
11532
|
{ method: "loads", class: "pickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
@@ -11739,6 +11784,15 @@ var DEFAULT_SINKS = [
|
|
|
11739
11784
|
// Standard library logging
|
|
11740
11785
|
{ method: "println!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2] },
|
|
11741
11786
|
{ method: "eprintln!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2] },
|
|
11787
|
+
// log:: namespaced forms — the Rust macro extractor preserves the full
|
|
11788
|
+
// path prefix in `method_name` (`log::info!`), so the bare entries above
|
|
11789
|
+
// only match the imported form `use log::info; info!(...)`. Sprint 56 #182 Slice A.
|
|
11790
|
+
{ method: "log::info!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2], languages: ["rust"] },
|
|
11791
|
+
{ method: "log::warn!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2], languages: ["rust"] },
|
|
11792
|
+
{ method: "log::error!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2], languages: ["rust"] },
|
|
11793
|
+
{ method: "log::debug!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2], languages: ["rust"] },
|
|
11794
|
+
{ method: "log::trace!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2], languages: ["rust"] },
|
|
11795
|
+
{ method: "log::log!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2], languages: ["rust"] },
|
|
11742
11796
|
// Rust sqlx SQL Injection
|
|
11743
11797
|
{ method: "query", class: "sqlx", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
|
|
11744
11798
|
{ method: "query_as", class: "sqlx", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
|
|
@@ -12833,7 +12887,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12833
12887
|
}
|
|
12834
12888
|
}
|
|
12835
12889
|
}
|
|
12836
|
-
if (pattern.type === "code_injection" && (call.method_name === "setInterval" || call.method_name === "setTimeout")) {
|
|
12890
|
+
if (pattern.type === "code_injection" && (call.method_name === "setInterval" || call.method_name === "setTimeout" || call.method_name === "setImmediate")) {
|
|
12837
12891
|
const firstArg = call.arguments.find((a) => a.position === 0);
|
|
12838
12892
|
if (firstArg && isFunctionCallbackArgument(firstArg)) {
|
|
12839
12893
|
continue;
|
|
@@ -13150,6 +13204,11 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
|
|
|
13150
13204
|
if (typeHierarchy && typeHierarchy.couldBeType(call.receiver, pattern.class)) {
|
|
13151
13205
|
return true;
|
|
13152
13206
|
}
|
|
13207
|
+
const resolvedTarget = call.resolution?.target;
|
|
13208
|
+
const expectedClassMethodTail = `${pattern.class}.${pattern.method}`;
|
|
13209
|
+
if (resolvedTarget && (resolvedTarget === expectedClassMethodTail || resolvedTarget.endsWith("." + expectedClassMethodTail))) {
|
|
13210
|
+
return true;
|
|
13211
|
+
}
|
|
13153
13212
|
if (pattern.allow_unresolved_receiver && !call.receiver_type && !call.receiver_type_fqn && call.receiver.includes(".")) {
|
|
13154
13213
|
return true;
|
|
13155
13214
|
}
|
|
@@ -11264,8 +11264,27 @@ var DEFAULT_SINKS = [
|
|
|
11264
11264
|
// SQL query calls are covered by class-specific patterns above (Connection, Pool, Client, JdbcTemplate)
|
|
11265
11265
|
// Note: `raw` is shared with Python (Django ORM) — scoped to JS+TS to avoid leaking.
|
|
11266
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 },
|
|
11267
11277
|
// Browser DOM XSS sinks
|
|
11268
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"] },
|
|
11269
11288
|
// Express.js XSS (response methods)
|
|
11270
11289
|
{ method: "send", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
11271
11290
|
{ method: "write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
@@ -11278,6 +11297,19 @@ var DEFAULT_SINKS = [
|
|
|
11278
11297
|
{ method: "runInContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11279
11298
|
{ method: "runInNewContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
11280
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"] },
|
|
11281
11313
|
// protobufjs Root.parse(schemaText) compiles a textual schema into JS at runtime;
|
|
11282
11314
|
// tainted schema → code execution (CVE-2026-41242). Issue #94.
|
|
11283
11315
|
{ method: "parse", class: "protobuf", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
@@ -11340,6 +11372,14 @@ var DEFAULT_SINKS = [
|
|
|
11340
11372
|
// got library
|
|
11341
11373
|
{ method: "get", class: "got", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
11342
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"] },
|
|
11343
11383
|
// superagent
|
|
11344
11384
|
{ method: "get", class: "superagent", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
11345
11385
|
{ method: "post", class: "superagent", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
@@ -11416,6 +11456,11 @@ var DEFAULT_SINKS = [
|
|
|
11416
11456
|
{ method: "exec", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11417
11457
|
{ method: "compile", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
11418
11458
|
{ method: "__import__", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
11459
|
+
// Python dynamic import — `importlib.import_module(taint)` parallels Java's
|
|
11460
|
+
// `Class.forName`. The bare `__import__` entry above also matches the
|
|
11461
|
+
// `importlib.__import__` form because the sink-matcher is class-agnostic
|
|
11462
|
+
// when a classless entry exists. Sprint 56 #183.
|
|
11463
|
+
{ method: "import_module", class: "importlib", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
11419
11464
|
// Python Deserialization — language-scoped so the lowercase `yaml` / `pickle`
|
|
11420
11465
|
// module names don't collide with Java locals named `yaml` (SnakeYAML usage).
|
|
11421
11466
|
{ method: "loads", class: "pickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
@@ -11673,6 +11718,15 @@ var DEFAULT_SINKS = [
|
|
|
11673
11718
|
// Standard library logging
|
|
11674
11719
|
{ method: "println!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2] },
|
|
11675
11720
|
{ method: "eprintln!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2] },
|
|
11721
|
+
// log:: namespaced forms — the Rust macro extractor preserves the full
|
|
11722
|
+
// path prefix in `method_name` (`log::info!`), so the bare entries above
|
|
11723
|
+
// only match the imported form `use log::info; info!(...)`. Sprint 56 #182 Slice A.
|
|
11724
|
+
{ method: "log::info!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2], languages: ["rust"] },
|
|
11725
|
+
{ method: "log::warn!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2], languages: ["rust"] },
|
|
11726
|
+
{ method: "log::error!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2], languages: ["rust"] },
|
|
11727
|
+
{ method: "log::debug!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2], languages: ["rust"] },
|
|
11728
|
+
{ method: "log::trace!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2], languages: ["rust"] },
|
|
11729
|
+
{ method: "log::log!", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2], languages: ["rust"] },
|
|
11676
11730
|
// Rust sqlx SQL Injection
|
|
11677
11731
|
{ method: "query", class: "sqlx", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
|
|
11678
11732
|
{ method: "query_as", class: "sqlx", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
|
|
@@ -12767,7 +12821,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12767
12821
|
}
|
|
12768
12822
|
}
|
|
12769
12823
|
}
|
|
12770
|
-
if (pattern.type === "code_injection" && (call.method_name === "setInterval" || call.method_name === "setTimeout")) {
|
|
12824
|
+
if (pattern.type === "code_injection" && (call.method_name === "setInterval" || call.method_name === "setTimeout" || call.method_name === "setImmediate")) {
|
|
12771
12825
|
const firstArg = call.arguments.find((a) => a.position === 0);
|
|
12772
12826
|
if (firstArg && isFunctionCallbackArgument(firstArg)) {
|
|
12773
12827
|
continue;
|
|
@@ -13084,6 +13138,11 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
|
|
|
13084
13138
|
if (typeHierarchy && typeHierarchy.couldBeType(call.receiver, pattern.class)) {
|
|
13085
13139
|
return true;
|
|
13086
13140
|
}
|
|
13141
|
+
const resolvedTarget = call.resolution?.target;
|
|
13142
|
+
const expectedClassMethodTail = `${pattern.class}.${pattern.method}`;
|
|
13143
|
+
if (resolvedTarget && (resolvedTarget === expectedClassMethodTail || resolvedTarget.endsWith("." + expectedClassMethodTail))) {
|
|
13144
|
+
return true;
|
|
13145
|
+
}
|
|
13087
13146
|
if (pattern.allow_unresolved_receiver && !call.receiver_type && !call.receiver_type_fqn && call.receiver.includes(".")) {
|
|
13088
13147
|
return true;
|
|
13089
13148
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "circle-ir",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.113.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",
|