circle-ir 3.174.0 → 3.176.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/open_redirect.yaml +21 -0
- package/configs/sinks/trust_boundary.yaml +18 -0
- package/dist/analysis/arg-type-resolver.d.ts +90 -0
- package/dist/analysis/arg-type-resolver.d.ts.map +1 -0
- package/dist/analysis/arg-type-resolver.js +174 -0
- package/dist/analysis/arg-type-resolver.js.map +1 -0
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +100 -1
- 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 +38 -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 +42 -5
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/browser/circle-ir.js +181 -7
- package/dist/core/circle-ir-core.cjs +152 -7
- package/dist/core/circle-ir-core.js +152 -7
- package/package.json +1 -1
|
@@ -10610,6 +10610,66 @@ var DEFAULT_SOURCES = [
|
|
|
10610
10610
|
{ method: "decode", class: "JWT", type: "http_header", severity: "high", return_tainted: true, languages: ["java"] },
|
|
10611
10611
|
{ method: "ParseUnverified", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["go"] }
|
|
10612
10612
|
];
|
|
10613
|
+
var OPEN_REDIRECT_FRAMEWORK_SINKS = [
|
|
10614
|
+
// --- Python: django / starlette / fastapi -------------------------------
|
|
10615
|
+
{ method: "HttpResponseRedirect", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["python"] },
|
|
10616
|
+
{ method: "HttpResponsePermanentRedirect", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["python"] },
|
|
10617
|
+
{ method: "RedirectResponse", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["python"] },
|
|
10618
|
+
// --- JS/TS: koa / fastify / express / next.js ---------------------------
|
|
10619
|
+
{ method: "redirect", class: "Context", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
10620
|
+
{ method: "redirect", class: "FastifyReply", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
10621
|
+
// Express: res.location(url) sets Location header without redirect status,
|
|
10622
|
+
// still an open-redirect vector when combined with a downstream redirect.
|
|
10623
|
+
{ method: "location", class: "Response", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
10624
|
+
// Next.js App Router: NextResponse.redirect(url) — static method matched
|
|
10625
|
+
// by receiver-class name.
|
|
10626
|
+
{ method: "redirect", class: "NextResponse", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
10627
|
+
// --- Java: Spring MVC RedirectView + JAX-RS Response --------------------
|
|
10628
|
+
{ method: "RedirectView", class: "RedirectView", type: "open_redirect", cwe: "CWE-601", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10629
|
+
{ method: "setUrl", class: "RedirectView", type: "open_redirect", cwe: "CWE-601", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10630
|
+
// JAX-RS Response builder — static factory methods that take a target URI.
|
|
10631
|
+
{ method: "seeOther", class: "Response", type: "open_redirect", cwe: "CWE-601", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10632
|
+
{ method: "temporaryRedirect", class: "Response", type: "open_redirect", cwe: "CWE-601", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10633
|
+
// --- Go: gin / echo / fiber ---------------------------------------------
|
|
10634
|
+
// gin/echo: c.Redirect(302, url) — status at arg[0], url at arg[1].
|
|
10635
|
+
// fiber: c.Redirect(url) — url at arg[0].
|
|
10636
|
+
// These entries fire once Go local-receiver type resolution lands
|
|
10637
|
+
// (see taint-matcher.ts:2137 receiverMightBeClass — currently returns
|
|
10638
|
+
// false for `c` against 'Context'/'Ctx'). Until then, the
|
|
10639
|
+
// external_taint_escape fallback preserves recall on these call sites.
|
|
10640
|
+
// net/http.Redirect (class 'http') is unaffected — declares class 'http'
|
|
10641
|
+
// and matches via package receiver.
|
|
10642
|
+
{ method: "Redirect", class: "Context", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
10643
|
+
{ method: "Redirect", class: "Ctx", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["go"] }
|
|
10644
|
+
];
|
|
10645
|
+
var TRUST_BOUNDARY_FRAMEWORK_SINKS = [
|
|
10646
|
+
// --- Python: Django cache write -----------------------------------------
|
|
10647
|
+
{ method: "set", class: "cache", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["python"] },
|
|
10648
|
+
{ method: "set_many", class: "cache", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [0], languages: ["python"] },
|
|
10649
|
+
// --- JS/TS: client-side Storage + Express cookie ------------------------
|
|
10650
|
+
// localStorage / sessionStorage — browser Storage.setItem(key, value).
|
|
10651
|
+
{ method: "setItem", class: "Storage", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["javascript", "typescript"] },
|
|
10652
|
+
// Express: res.cookie(name, value, opts) — cookie-jar write.
|
|
10653
|
+
{ method: "cookie", class: "Response", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["javascript", "typescript"] },
|
|
10654
|
+
// --- Java: Cookie / SecurityContext / System.setProperty ----------------
|
|
10655
|
+
// `new Cookie(name, value)` — constructor with tainted value crosses the
|
|
10656
|
+
// client-server trust boundary via Set-Cookie.
|
|
10657
|
+
{ method: "Cookie", class: "Cookie", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["java"] },
|
|
10658
|
+
{ method: "setValue", class: "Cookie", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [0], languages: ["java"] },
|
|
10659
|
+
// Spring Security: SecurityContext.setAuthentication(userSuppliedAuth) —
|
|
10660
|
+
// installs an untrusted principal into the request-scoped security
|
|
10661
|
+
// context; downstream authz decisions become attacker-controlled.
|
|
10662
|
+
{ method: "setAuthentication", class: "SecurityContext", type: "trust_boundary", cwe: "CWE-501", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10663
|
+
// JVM-wide System property write with tainted value pollutes process
|
|
10664
|
+
// state observable to every thread.
|
|
10665
|
+
{ method: "setProperty", class: "System", type: "trust_boundary", cwe: "CWE-501", severity: "high", arg_positions: [1], languages: ["java"] },
|
|
10666
|
+
// --- Go: http.SetCookie / gin.SetCookie ---------------------------------
|
|
10667
|
+
// Package-level `http.SetCookie(w, &http.Cookie{Value: tainted})` — sink
|
|
10668
|
+
// on arg[1] (the *Cookie) catches struct-literal composite tainting.
|
|
10669
|
+
{ method: "SetCookie", class: "http", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
10670
|
+
// gin: c.SetCookie(name, value, maxAge, path, domain, secure, httpOnly).
|
|
10671
|
+
{ method: "SetCookie", class: "Context", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] }
|
|
10672
|
+
];
|
|
10613
10673
|
var DEFAULT_SINKS = [
|
|
10614
10674
|
// SQL Injection (CWE-89)
|
|
10615
10675
|
{ method: "executeQuery", class: "Statement", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
|
|
@@ -11971,7 +12031,10 @@ var DEFAULT_SINKS = [
|
|
|
11971
12031
|
{ method: "parse", class: "Uri", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
11972
12032
|
// Rust Open Redirect
|
|
11973
12033
|
{ method: "redirect", class: "HttpResponse", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0] },
|
|
11974
|
-
|
|
12034
|
+
// Actix/warp `Redirect::to(url)` — scoped to Rust to avoid collision with
|
|
12035
|
+
// Go's `c.Redirect(status, url)` on gin/echo, which lives further down in
|
|
12036
|
+
// the OPEN_REDIRECT_FRAMEWORK_SINKS block with `arg_positions: [0, 1]`.
|
|
12037
|
+
{ method: "Redirect", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["rust"] },
|
|
11975
12038
|
{ method: "see_other", class: "Redirect", type: "open_redirect", cwe: "CWE-601", severity: "high", arg_positions: [0] },
|
|
11976
12039
|
{ method: "to", class: "Redirect", type: "open_redirect", cwe: "CWE-601", severity: "high", arg_positions: [0] },
|
|
11977
12040
|
{ method: "temporary", class: "Redirect", type: "open_redirect", cwe: "CWE-601", severity: "high", arg_positions: [0] },
|
|
@@ -12255,7 +12318,14 @@ var DEFAULT_SINKS = [
|
|
|
12255
12318
|
// --- Go: langchaingo ----------------------------------------------------
|
|
12256
12319
|
{ method: "Call", class: "LLM", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
|
|
12257
12320
|
{ method: "Generate", class: "LLM", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
|
|
12258
|
-
{ method: "GenerateContent", class: "Model", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] }
|
|
12321
|
+
{ method: "GenerateContent", class: "Model", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
|
|
12322
|
+
// cognium-dev #240 ship 1 — extended framework sinks for open_redirect
|
|
12323
|
+
// (CWE-601) and trust_boundary (CWE-501). Definitions extracted above
|
|
12324
|
+
// as OPEN_REDIRECT_FRAMEWORK_SINKS / TRUST_BOUNDARY_FRAMEWORK_SINKS to
|
|
12325
|
+
// keep the DEFAULT_SINKS literal within TypeScript's union-type
|
|
12326
|
+
// inference complexity limit (TS2590). See ~lines 661-752.
|
|
12327
|
+
...OPEN_REDIRECT_FRAMEWORK_SINKS,
|
|
12328
|
+
...TRUST_BOUNDARY_FRAMEWORK_SINKS
|
|
12259
12329
|
];
|
|
12260
12330
|
var DEFAULT_SANITIZERS = [
|
|
12261
12331
|
// SQL Injection - proper parameter binding sanitizes input
|
|
@@ -12598,6 +12668,64 @@ function getDefaultConfig() {
|
|
|
12598
12668
|
};
|
|
12599
12669
|
}
|
|
12600
12670
|
|
|
12671
|
+
// src/analysis/arg-type-resolver.ts
|
|
12672
|
+
function findEnclosingType(callInMethod, types) {
|
|
12673
|
+
if (!callInMethod) return null;
|
|
12674
|
+
for (const t of types) {
|
|
12675
|
+
for (const m of t.methods) {
|
|
12676
|
+
if (m.name === callInMethod) return t;
|
|
12677
|
+
}
|
|
12678
|
+
}
|
|
12679
|
+
return null;
|
|
12680
|
+
}
|
|
12681
|
+
function resolveIdentifierType(name2, callInMethod, types) {
|
|
12682
|
+
if (!name2) return null;
|
|
12683
|
+
const enclosing = findEnclosingType(callInMethod, types);
|
|
12684
|
+
if (enclosing) {
|
|
12685
|
+
for (const m of enclosing.methods) {
|
|
12686
|
+
if (m.name !== callInMethod) continue;
|
|
12687
|
+
for (const p of m.parameters) {
|
|
12688
|
+
if (p.name === name2 && p.type) return p.type;
|
|
12689
|
+
}
|
|
12690
|
+
}
|
|
12691
|
+
for (const f of enclosing.fields) {
|
|
12692
|
+
if (f.name === name2 && f.type) return f.type;
|
|
12693
|
+
}
|
|
12694
|
+
return null;
|
|
12695
|
+
}
|
|
12696
|
+
for (const t of types) {
|
|
12697
|
+
for (const m of t.methods) {
|
|
12698
|
+
for (const p of m.parameters) {
|
|
12699
|
+
if (p.name === name2 && p.type) return p.type;
|
|
12700
|
+
}
|
|
12701
|
+
}
|
|
12702
|
+
for (const f of t.fields) {
|
|
12703
|
+
if (f.name === name2 && f.type) return f.type;
|
|
12704
|
+
}
|
|
12705
|
+
}
|
|
12706
|
+
return null;
|
|
12707
|
+
}
|
|
12708
|
+
function resolveCallReturnType(calleeName, callInMethod, types) {
|
|
12709
|
+
if (!calleeName) return null;
|
|
12710
|
+
const enclosing = findEnclosingType(callInMethod, types);
|
|
12711
|
+
if (enclosing) {
|
|
12712
|
+
for (const m of enclosing.methods) {
|
|
12713
|
+
if (m.name === calleeName && m.return_type) return m.return_type;
|
|
12714
|
+
}
|
|
12715
|
+
}
|
|
12716
|
+
for (const t of types) {
|
|
12717
|
+
for (const m of t.methods) {
|
|
12718
|
+
if (m.name === calleeName && m.return_type) return m.return_type;
|
|
12719
|
+
}
|
|
12720
|
+
}
|
|
12721
|
+
return null;
|
|
12722
|
+
}
|
|
12723
|
+
function isBoundedClassType(rawType) {
|
|
12724
|
+
if (!rawType) return false;
|
|
12725
|
+
const stripped = rawType.trim().replace(/^(?:java\.lang\.)+/, "");
|
|
12726
|
+
return /^Class(?:\s*<[\s\S]*>)?$/.test(stripped);
|
|
12727
|
+
}
|
|
12728
|
+
|
|
12601
12729
|
// src/analysis/taint-matcher.ts
|
|
12602
12730
|
var PYTHON_TAINTED_PATTERNS = [
|
|
12603
12731
|
{ pattern: /\brequest\.args\b/, sourceType: "http_param" },
|
|
@@ -12623,7 +12751,7 @@ function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy,
|
|
|
12623
12751
|
const sources = findSources(calls, types, config.sources, sourceLines, language);
|
|
12624
12752
|
let sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
|
|
12625
12753
|
sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
|
|
12626
|
-
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines);
|
|
12754
|
+
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
|
|
12627
12755
|
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
|
|
12628
12756
|
return { sources, sinks, sanitizers };
|
|
12629
12757
|
}
|
|
@@ -13201,13 +13329,30 @@ function isSafeRustCommandCall(call, pattern, language) {
|
|
|
13201
13329
|
return false;
|
|
13202
13330
|
}
|
|
13203
13331
|
var CLASS_LITERAL_RE = /^(?:[A-Za-z_][\w]*\.)*[A-Z][\w]*(?:\[\])*\.class$/;
|
|
13204
|
-
function argIsClassLiteral(call, position) {
|
|
13332
|
+
function argIsClassLiteral(call, position, types) {
|
|
13205
13333
|
const arg = call.arguments.find((a) => a.position === position);
|
|
13206
13334
|
if (!arg) return false;
|
|
13207
13335
|
const expr = (arg.literal ?? arg.expression ?? "").trim();
|
|
13208
13336
|
if (!expr) return false;
|
|
13209
13337
|
if (CLASS_LITERAL_RE.test(expr)) return true;
|
|
13210
|
-
|
|
13338
|
+
if (TYPE_TOKEN_RE.test(expr)) return true;
|
|
13339
|
+
if (!types || types.length === 0) return false;
|
|
13340
|
+
const argExpr = (arg.expression ?? "").trim();
|
|
13341
|
+
const varName = arg.variable ?? "";
|
|
13342
|
+
const isBareIdentifier = varName !== "" && argExpr === varName && !/[(.]/.test(argExpr);
|
|
13343
|
+
if (isBareIdentifier) {
|
|
13344
|
+
const t = resolveIdentifierType(varName, call.in_method, types);
|
|
13345
|
+
if (isBoundedClassType(t)) return true;
|
|
13346
|
+
}
|
|
13347
|
+
const isCallExpr = varName !== "" && new RegExp(`^${escapeRe(varName)}\\s*\\(`).test(argExpr);
|
|
13348
|
+
if (isCallExpr) {
|
|
13349
|
+
const t = resolveCallReturnType(varName, call.in_method, types);
|
|
13350
|
+
if (isBoundedClassType(t)) return true;
|
|
13351
|
+
}
|
|
13352
|
+
return false;
|
|
13353
|
+
}
|
|
13354
|
+
function escapeRe(s) {
|
|
13355
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
13211
13356
|
}
|
|
13212
13357
|
var TYPE_TOKEN_RE = /^new\s+(?:TypeReference|TypeToken)\s*<[\s\S]*>\s*\(\s*\)\s*\{\s*\}$/;
|
|
13213
13358
|
function argIsStringLiteral(call, position) {
|
|
@@ -13368,7 +13513,7 @@ function isSafeJinjaRenderCall(call, pattern, language, sourceLines) {
|
|
|
13368
13513
|
}
|
|
13369
13514
|
return false;
|
|
13370
13515
|
}
|
|
13371
|
-
function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
13516
|
+
function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types) {
|
|
13372
13517
|
const sinkMap = /* @__PURE__ */ new Map();
|
|
13373
13518
|
for (const call of calls) {
|
|
13374
13519
|
for (const pattern of patterns) {
|
|
@@ -13388,7 +13533,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
13388
13533
|
if (isSafeJSChildProcessCall(call, pattern, language)) {
|
|
13389
13534
|
continue;
|
|
13390
13535
|
}
|
|
13391
|
-
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
|
|
13536
|
+
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at, types)) {
|
|
13392
13537
|
continue;
|
|
13393
13538
|
}
|
|
13394
13539
|
if (pattern.safe_if_string_literal_at !== void 0 && argIsStringLiteral(call, pattern.safe_if_string_literal_at)) {
|
|
@@ -10544,6 +10544,66 @@ var DEFAULT_SOURCES = [
|
|
|
10544
10544
|
{ method: "decode", class: "JWT", type: "http_header", severity: "high", return_tainted: true, languages: ["java"] },
|
|
10545
10545
|
{ method: "ParseUnverified", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["go"] }
|
|
10546
10546
|
];
|
|
10547
|
+
var OPEN_REDIRECT_FRAMEWORK_SINKS = [
|
|
10548
|
+
// --- Python: django / starlette / fastapi -------------------------------
|
|
10549
|
+
{ method: "HttpResponseRedirect", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["python"] },
|
|
10550
|
+
{ method: "HttpResponsePermanentRedirect", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["python"] },
|
|
10551
|
+
{ method: "RedirectResponse", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["python"] },
|
|
10552
|
+
// --- JS/TS: koa / fastify / express / next.js ---------------------------
|
|
10553
|
+
{ method: "redirect", class: "Context", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
10554
|
+
{ method: "redirect", class: "FastifyReply", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
10555
|
+
// Express: res.location(url) sets Location header without redirect status,
|
|
10556
|
+
// still an open-redirect vector when combined with a downstream redirect.
|
|
10557
|
+
{ method: "location", class: "Response", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
10558
|
+
// Next.js App Router: NextResponse.redirect(url) — static method matched
|
|
10559
|
+
// by receiver-class name.
|
|
10560
|
+
{ method: "redirect", class: "NextResponse", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
10561
|
+
// --- Java: Spring MVC RedirectView + JAX-RS Response --------------------
|
|
10562
|
+
{ method: "RedirectView", class: "RedirectView", type: "open_redirect", cwe: "CWE-601", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10563
|
+
{ method: "setUrl", class: "RedirectView", type: "open_redirect", cwe: "CWE-601", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10564
|
+
// JAX-RS Response builder — static factory methods that take a target URI.
|
|
10565
|
+
{ method: "seeOther", class: "Response", type: "open_redirect", cwe: "CWE-601", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10566
|
+
{ method: "temporaryRedirect", class: "Response", type: "open_redirect", cwe: "CWE-601", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10567
|
+
// --- Go: gin / echo / fiber ---------------------------------------------
|
|
10568
|
+
// gin/echo: c.Redirect(302, url) — status at arg[0], url at arg[1].
|
|
10569
|
+
// fiber: c.Redirect(url) — url at arg[0].
|
|
10570
|
+
// These entries fire once Go local-receiver type resolution lands
|
|
10571
|
+
// (see taint-matcher.ts:2137 receiverMightBeClass — currently returns
|
|
10572
|
+
// false for `c` against 'Context'/'Ctx'). Until then, the
|
|
10573
|
+
// external_taint_escape fallback preserves recall on these call sites.
|
|
10574
|
+
// net/http.Redirect (class 'http') is unaffected — declares class 'http'
|
|
10575
|
+
// and matches via package receiver.
|
|
10576
|
+
{ method: "Redirect", class: "Context", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
10577
|
+
{ method: "Redirect", class: "Ctx", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["go"] }
|
|
10578
|
+
];
|
|
10579
|
+
var TRUST_BOUNDARY_FRAMEWORK_SINKS = [
|
|
10580
|
+
// --- Python: Django cache write -----------------------------------------
|
|
10581
|
+
{ method: "set", class: "cache", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["python"] },
|
|
10582
|
+
{ method: "set_many", class: "cache", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [0], languages: ["python"] },
|
|
10583
|
+
// --- JS/TS: client-side Storage + Express cookie ------------------------
|
|
10584
|
+
// localStorage / sessionStorage — browser Storage.setItem(key, value).
|
|
10585
|
+
{ method: "setItem", class: "Storage", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["javascript", "typescript"] },
|
|
10586
|
+
// Express: res.cookie(name, value, opts) — cookie-jar write.
|
|
10587
|
+
{ method: "cookie", class: "Response", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["javascript", "typescript"] },
|
|
10588
|
+
// --- Java: Cookie / SecurityContext / System.setProperty ----------------
|
|
10589
|
+
// `new Cookie(name, value)` — constructor with tainted value crosses the
|
|
10590
|
+
// client-server trust boundary via Set-Cookie.
|
|
10591
|
+
{ method: "Cookie", class: "Cookie", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["java"] },
|
|
10592
|
+
{ method: "setValue", class: "Cookie", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [0], languages: ["java"] },
|
|
10593
|
+
// Spring Security: SecurityContext.setAuthentication(userSuppliedAuth) —
|
|
10594
|
+
// installs an untrusted principal into the request-scoped security
|
|
10595
|
+
// context; downstream authz decisions become attacker-controlled.
|
|
10596
|
+
{ method: "setAuthentication", class: "SecurityContext", type: "trust_boundary", cwe: "CWE-501", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10597
|
+
// JVM-wide System property write with tainted value pollutes process
|
|
10598
|
+
// state observable to every thread.
|
|
10599
|
+
{ method: "setProperty", class: "System", type: "trust_boundary", cwe: "CWE-501", severity: "high", arg_positions: [1], languages: ["java"] },
|
|
10600
|
+
// --- Go: http.SetCookie / gin.SetCookie ---------------------------------
|
|
10601
|
+
// Package-level `http.SetCookie(w, &http.Cookie{Value: tainted})` — sink
|
|
10602
|
+
// on arg[1] (the *Cookie) catches struct-literal composite tainting.
|
|
10603
|
+
{ method: "SetCookie", class: "http", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
10604
|
+
// gin: c.SetCookie(name, value, maxAge, path, domain, secure, httpOnly).
|
|
10605
|
+
{ method: "SetCookie", class: "Context", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] }
|
|
10606
|
+
];
|
|
10547
10607
|
var DEFAULT_SINKS = [
|
|
10548
10608
|
// SQL Injection (CWE-89)
|
|
10549
10609
|
{ method: "executeQuery", class: "Statement", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
|
|
@@ -11905,7 +11965,10 @@ var DEFAULT_SINKS = [
|
|
|
11905
11965
|
{ method: "parse", class: "Uri", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
11906
11966
|
// Rust Open Redirect
|
|
11907
11967
|
{ method: "redirect", class: "HttpResponse", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0] },
|
|
11908
|
-
|
|
11968
|
+
// Actix/warp `Redirect::to(url)` — scoped to Rust to avoid collision with
|
|
11969
|
+
// Go's `c.Redirect(status, url)` on gin/echo, which lives further down in
|
|
11970
|
+
// the OPEN_REDIRECT_FRAMEWORK_SINKS block with `arg_positions: [0, 1]`.
|
|
11971
|
+
{ method: "Redirect", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["rust"] },
|
|
11909
11972
|
{ method: "see_other", class: "Redirect", type: "open_redirect", cwe: "CWE-601", severity: "high", arg_positions: [0] },
|
|
11910
11973
|
{ method: "to", class: "Redirect", type: "open_redirect", cwe: "CWE-601", severity: "high", arg_positions: [0] },
|
|
11911
11974
|
{ method: "temporary", class: "Redirect", type: "open_redirect", cwe: "CWE-601", severity: "high", arg_positions: [0] },
|
|
@@ -12189,7 +12252,14 @@ var DEFAULT_SINKS = [
|
|
|
12189
12252
|
// --- Go: langchaingo ----------------------------------------------------
|
|
12190
12253
|
{ method: "Call", class: "LLM", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
|
|
12191
12254
|
{ method: "Generate", class: "LLM", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
|
|
12192
|
-
{ method: "GenerateContent", class: "Model", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] }
|
|
12255
|
+
{ method: "GenerateContent", class: "Model", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
|
|
12256
|
+
// cognium-dev #240 ship 1 — extended framework sinks for open_redirect
|
|
12257
|
+
// (CWE-601) and trust_boundary (CWE-501). Definitions extracted above
|
|
12258
|
+
// as OPEN_REDIRECT_FRAMEWORK_SINKS / TRUST_BOUNDARY_FRAMEWORK_SINKS to
|
|
12259
|
+
// keep the DEFAULT_SINKS literal within TypeScript's union-type
|
|
12260
|
+
// inference complexity limit (TS2590). See ~lines 661-752.
|
|
12261
|
+
...OPEN_REDIRECT_FRAMEWORK_SINKS,
|
|
12262
|
+
...TRUST_BOUNDARY_FRAMEWORK_SINKS
|
|
12193
12263
|
];
|
|
12194
12264
|
var DEFAULT_SANITIZERS = [
|
|
12195
12265
|
// SQL Injection - proper parameter binding sanitizes input
|
|
@@ -12532,6 +12602,64 @@ function getDefaultConfig() {
|
|
|
12532
12602
|
};
|
|
12533
12603
|
}
|
|
12534
12604
|
|
|
12605
|
+
// src/analysis/arg-type-resolver.ts
|
|
12606
|
+
function findEnclosingType(callInMethod, types) {
|
|
12607
|
+
if (!callInMethod) return null;
|
|
12608
|
+
for (const t of types) {
|
|
12609
|
+
for (const m of t.methods) {
|
|
12610
|
+
if (m.name === callInMethod) return t;
|
|
12611
|
+
}
|
|
12612
|
+
}
|
|
12613
|
+
return null;
|
|
12614
|
+
}
|
|
12615
|
+
function resolveIdentifierType(name2, callInMethod, types) {
|
|
12616
|
+
if (!name2) return null;
|
|
12617
|
+
const enclosing = findEnclosingType(callInMethod, types);
|
|
12618
|
+
if (enclosing) {
|
|
12619
|
+
for (const m of enclosing.methods) {
|
|
12620
|
+
if (m.name !== callInMethod) continue;
|
|
12621
|
+
for (const p of m.parameters) {
|
|
12622
|
+
if (p.name === name2 && p.type) return p.type;
|
|
12623
|
+
}
|
|
12624
|
+
}
|
|
12625
|
+
for (const f of enclosing.fields) {
|
|
12626
|
+
if (f.name === name2 && f.type) return f.type;
|
|
12627
|
+
}
|
|
12628
|
+
return null;
|
|
12629
|
+
}
|
|
12630
|
+
for (const t of types) {
|
|
12631
|
+
for (const m of t.methods) {
|
|
12632
|
+
for (const p of m.parameters) {
|
|
12633
|
+
if (p.name === name2 && p.type) return p.type;
|
|
12634
|
+
}
|
|
12635
|
+
}
|
|
12636
|
+
for (const f of t.fields) {
|
|
12637
|
+
if (f.name === name2 && f.type) return f.type;
|
|
12638
|
+
}
|
|
12639
|
+
}
|
|
12640
|
+
return null;
|
|
12641
|
+
}
|
|
12642
|
+
function resolveCallReturnType(calleeName, callInMethod, types) {
|
|
12643
|
+
if (!calleeName) return null;
|
|
12644
|
+
const enclosing = findEnclosingType(callInMethod, types);
|
|
12645
|
+
if (enclosing) {
|
|
12646
|
+
for (const m of enclosing.methods) {
|
|
12647
|
+
if (m.name === calleeName && m.return_type) return m.return_type;
|
|
12648
|
+
}
|
|
12649
|
+
}
|
|
12650
|
+
for (const t of types) {
|
|
12651
|
+
for (const m of t.methods) {
|
|
12652
|
+
if (m.name === calleeName && m.return_type) return m.return_type;
|
|
12653
|
+
}
|
|
12654
|
+
}
|
|
12655
|
+
return null;
|
|
12656
|
+
}
|
|
12657
|
+
function isBoundedClassType(rawType) {
|
|
12658
|
+
if (!rawType) return false;
|
|
12659
|
+
const stripped = rawType.trim().replace(/^(?:java\.lang\.)+/, "");
|
|
12660
|
+
return /^Class(?:\s*<[\s\S]*>)?$/.test(stripped);
|
|
12661
|
+
}
|
|
12662
|
+
|
|
12535
12663
|
// src/analysis/taint-matcher.ts
|
|
12536
12664
|
var PYTHON_TAINTED_PATTERNS = [
|
|
12537
12665
|
{ pattern: /\brequest\.args\b/, sourceType: "http_param" },
|
|
@@ -12557,7 +12685,7 @@ function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy,
|
|
|
12557
12685
|
const sources = findSources(calls, types, config.sources, sourceLines, language);
|
|
12558
12686
|
let sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
|
|
12559
12687
|
sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
|
|
12560
|
-
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines);
|
|
12688
|
+
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
|
|
12561
12689
|
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
|
|
12562
12690
|
return { sources, sinks, sanitizers };
|
|
12563
12691
|
}
|
|
@@ -13135,13 +13263,30 @@ function isSafeRustCommandCall(call, pattern, language) {
|
|
|
13135
13263
|
return false;
|
|
13136
13264
|
}
|
|
13137
13265
|
var CLASS_LITERAL_RE = /^(?:[A-Za-z_][\w]*\.)*[A-Z][\w]*(?:\[\])*\.class$/;
|
|
13138
|
-
function argIsClassLiteral(call, position) {
|
|
13266
|
+
function argIsClassLiteral(call, position, types) {
|
|
13139
13267
|
const arg = call.arguments.find((a) => a.position === position);
|
|
13140
13268
|
if (!arg) return false;
|
|
13141
13269
|
const expr = (arg.literal ?? arg.expression ?? "").trim();
|
|
13142
13270
|
if (!expr) return false;
|
|
13143
13271
|
if (CLASS_LITERAL_RE.test(expr)) return true;
|
|
13144
|
-
|
|
13272
|
+
if (TYPE_TOKEN_RE.test(expr)) return true;
|
|
13273
|
+
if (!types || types.length === 0) return false;
|
|
13274
|
+
const argExpr = (arg.expression ?? "").trim();
|
|
13275
|
+
const varName = arg.variable ?? "";
|
|
13276
|
+
const isBareIdentifier = varName !== "" && argExpr === varName && !/[(.]/.test(argExpr);
|
|
13277
|
+
if (isBareIdentifier) {
|
|
13278
|
+
const t = resolveIdentifierType(varName, call.in_method, types);
|
|
13279
|
+
if (isBoundedClassType(t)) return true;
|
|
13280
|
+
}
|
|
13281
|
+
const isCallExpr = varName !== "" && new RegExp(`^${escapeRe(varName)}\\s*\\(`).test(argExpr);
|
|
13282
|
+
if (isCallExpr) {
|
|
13283
|
+
const t = resolveCallReturnType(varName, call.in_method, types);
|
|
13284
|
+
if (isBoundedClassType(t)) return true;
|
|
13285
|
+
}
|
|
13286
|
+
return false;
|
|
13287
|
+
}
|
|
13288
|
+
function escapeRe(s) {
|
|
13289
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
13145
13290
|
}
|
|
13146
13291
|
var TYPE_TOKEN_RE = /^new\s+(?:TypeReference|TypeToken)\s*<[\s\S]*>\s*\(\s*\)\s*\{\s*\}$/;
|
|
13147
13292
|
function argIsStringLiteral(call, position) {
|
|
@@ -13302,7 +13447,7 @@ function isSafeJinjaRenderCall(call, pattern, language, sourceLines) {
|
|
|
13302
13447
|
}
|
|
13303
13448
|
return false;
|
|
13304
13449
|
}
|
|
13305
|
-
function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
13450
|
+
function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types) {
|
|
13306
13451
|
const sinkMap = /* @__PURE__ */ new Map();
|
|
13307
13452
|
for (const call of calls) {
|
|
13308
13453
|
for (const pattern of patterns) {
|
|
@@ -13322,7 +13467,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
13322
13467
|
if (isSafeJSChildProcessCall(call, pattern, language)) {
|
|
13323
13468
|
continue;
|
|
13324
13469
|
}
|
|
13325
|
-
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
|
|
13470
|
+
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at, types)) {
|
|
13326
13471
|
continue;
|
|
13327
13472
|
}
|
|
13328
13473
|
if (pattern.safe_if_string_literal_at !== void 0 && argIsStringLiteral(call, pattern.safe_if_string_literal_at)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "circle-ir",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.176.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",
|