circle-ir 3.18.7 → 3.19.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.
@@ -9619,6 +9619,38 @@ var DEFAULT_SINKS = [
9619
9619
  { method: "processRequest", class: "Broker", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9620
9620
  // DolphinScheduler
9621
9621
  { method: "execute", class: "TaskExecuteThread", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9622
+ // Apache Commons JEXL (JEXL expression injection)
9623
+ { method: "createExpression", class: "JexlEngine", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9624
+ { method: "createScript", class: "JexlEngine", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9625
+ { method: "evaluate", class: "JexlExpression", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [] },
9626
+ { method: "execute", class: "JexlScript", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [] },
9627
+ // Janino expression evaluator (Calcite/Flink/Drill)
9628
+ { method: "createFastEvaluator", class: "ExpressionEvaluator", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9629
+ { method: "cook", class: "ExpressionEvaluator", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9630
+ { method: "cook", class: "ScriptEvaluator", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9631
+ { method: "cook", class: "ClassBodyEvaluator", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9632
+ { method: "cook", class: "SimpleCompiler", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9633
+ // Apache Camel Simple language (CVE-2018-8041 and similar)
9634
+ { method: "createExpression", class: "SimpleLanguage", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9635
+ { method: "createPredicate", class: "SimpleLanguage", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9636
+ // Thymeleaf StandardExpression (CVE-2023-38286 and similar)
9637
+ { method: "parseExpression", class: "StandardExpressionParser", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [1] },
9638
+ { method: "getValue", class: "StandardExpression", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [] },
9639
+ // FreeMarker direct template construction (CVE-2022-26336 and similar)
9640
+ { method: "Template", class: "Template", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [1] },
9641
+ // new Template(name, tainted)
9642
+ { method: "getTemplate", class: "Configuration", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
9643
+ // Jinjava (Java Jinja template engine)
9644
+ { method: "render", class: "Jinjava", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
9645
+ { method: "renderForResult", class: "Jinjava", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
9646
+ // Spring Cloud Function RoutingFunction (CVE-2022-22963)
9647
+ { method: "getRequestedBeanName", class: "RoutingFunction", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [] },
9648
+ // Kotlin reflection (RCE via reflective construction)
9649
+ { method: "createInstance", class: "KClass", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [] },
9650
+ { method: "callBy", class: "KFunction", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
9651
+ // Struts 2 deep injection (CVE-2017-5638 and descendants)
9652
+ { method: "translateVariables", class: "TextParseUtil", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9653
+ { method: "evaluate", class: "StrutsResultSupport", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9622
9654
  // Deserialization (CWE-502)
9623
9655
  { method: "readObject", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [] },
9624
9656
  { method: "readUnshared", class: "ObjectInputStream", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [] },
@@ -10140,6 +10172,17 @@ var DEFAULT_SANITIZERS = [
10140
10172
  { method: "encodeForHTML", removes: ["xss"] },
10141
10173
  { method: "escapeXml", removes: ["xss"] },
10142
10174
  { method: "htmlEscape", removes: ["xss"] },
10175
+ { method: "escapeHtml4", removes: ["xss"] },
10176
+ // Apache Commons StringEscapeUtils
10177
+ { method: "escapeHtml3", removes: ["xss"] },
10178
+ // Apache Commons StringEscapeUtils
10179
+ { method: "htmlSpecialChars", removes: ["xss"] },
10180
+ // PHP-style / common wrapper
10181
+ { method: "forHtml", class: "Encode", removes: ["xss"] },
10182
+ // OWASP Java Encoder
10183
+ { method: "forHtmlContent", class: "Encode", removes: ["xss"] },
10184
+ { method: "forHtmlAttribute", class: "Encode", removes: ["xss"] },
10185
+ { method: "forJavaScript", class: "Encode", removes: ["xss"] },
10143
10186
  { method: "encode_text", removes: ["xss"] },
10144
10187
  // Rust html_escape crate
10145
10188
  { method: "encode_safe", removes: ["xss"] },
@@ -10149,6 +10192,11 @@ var DEFAULT_SANITIZERS = [
10149
10192
  { method: "encodeForJavaScript", removes: ["xss"] },
10150
10193
  { method: "encodeForCSS", removes: ["xss"] },
10151
10194
  { method: "encodeForURL", removes: ["xss", "ssrf"] },
10195
+ // URL encoding wrapper aliases (common patterns in benchmarks and real-world code)
10196
+ { method: "encodeURL", removes: ["xss", "ssrf"] },
10197
+ { method: "urlEncode", removes: ["xss", "ssrf"] },
10198
+ { method: "escapeUrl", removes: ["xss", "ssrf"] },
10199
+ { method: "escapeURL", removes: ["xss", "ssrf"] },
10152
10200
  // Path Traversal
10153
10201
  { method: "normalize", class: "Path", removes: ["path_traversal"] },
10154
10202
  { method: "getCanonicalPath", class: "File", removes: ["path_traversal"] },
@@ -10287,6 +10335,93 @@ function getDefaultConfig() {
10287
10335
  sanitizers: DEFAULT_SANITIZERS
10288
10336
  };
10289
10337
  }
10338
+ var DEFAULT_HEADER_RULES = [
10339
+ // -------------------------------------------------------------------------
10340
+ // Clickjacking (CWE-1021)
10341
+ // -------------------------------------------------------------------------
10342
+ {
10343
+ rule_id: "missing-x-frame-options",
10344
+ cwe: "CWE-1021",
10345
+ level: "warning",
10346
+ severity: "medium",
10347
+ header: "X-Frame-Options",
10348
+ kind: "missing",
10349
+ requiresHandler: true,
10350
+ message: "HTTP handler does not set X-Frame-Options \u2014 vulnerable to clickjacking",
10351
+ fix: "Set response.setHeader('X-Frame-Options', 'DENY') or use a CSP frame-ancestors directive",
10352
+ note: "Defense against UI redress / clickjacking attacks"
10353
+ },
10354
+ {
10355
+ rule_id: "x-frame-options-allow-from",
10356
+ cwe: "CWE-1021",
10357
+ level: "warning",
10358
+ severity: "medium",
10359
+ header: "X-Frame-Options",
10360
+ kind: "weak-value",
10361
+ valuePattern: /^allow-from\b/i,
10362
+ message: "X-Frame-Options: ALLOW-FROM is deprecated and unsupported by modern browsers",
10363
+ fix: "Use CSP frame-ancestors directive instead: Content-Security-Policy: frame-ancestors 'self'"
10364
+ },
10365
+ {
10366
+ rule_id: "missing-csp-frame-ancestors",
10367
+ cwe: "CWE-1021",
10368
+ level: "note",
10369
+ severity: "low",
10370
+ header: "Content-Security-Policy",
10371
+ kind: "missing",
10372
+ requiresHandler: true,
10373
+ message: "HTTP handler does not set Content-Security-Policy \u2014 frame-ancestors unset",
10374
+ fix: "Set Content-Security-Policy: frame-ancestors 'self' for defense-in-depth clickjacking protection",
10375
+ note: "Informational; paired with missing-x-frame-options"
10376
+ },
10377
+ // -------------------------------------------------------------------------
10378
+ // CORS Misconfiguration (CWE-346, CWE-942)
10379
+ // -------------------------------------------------------------------------
10380
+ {
10381
+ rule_id: "cors-wildcard-origin",
10382
+ cwe: "CWE-942",
10383
+ level: "error",
10384
+ severity: "high",
10385
+ header: "Access-Control-Allow-Origin",
10386
+ kind: "weak-value",
10387
+ valuePattern: /^\*$/,
10388
+ message: "Access-Control-Allow-Origin: '*' permits cross-origin requests from any site",
10389
+ fix: "Restrict to a specific trusted origin or use an allowlist"
10390
+ },
10391
+ {
10392
+ rule_id: "cors-null-origin",
10393
+ cwe: "CWE-346",
10394
+ level: "error",
10395
+ severity: "high",
10396
+ header: "Access-Control-Allow-Origin",
10397
+ kind: "weak-value",
10398
+ valuePattern: /^null$/i,
10399
+ message: "Access-Control-Allow-Origin: 'null' is exploitable via sandboxed iframes and data: URIs",
10400
+ fix: "Restrict to a specific trusted origin"
10401
+ },
10402
+ {
10403
+ rule_id: "cors-http-origin",
10404
+ cwe: "CWE-346",
10405
+ level: "warning",
10406
+ severity: "medium",
10407
+ header: "Access-Control-Allow-Origin",
10408
+ kind: "weak-value",
10409
+ valuePattern: /^http:\/\//i,
10410
+ message: "Access-Control-Allow-Origin uses insecure http:// scheme",
10411
+ fix: "Use https:// for the allowed origin"
10412
+ },
10413
+ {
10414
+ rule_id: "cors-reflected-origin",
10415
+ cwe: "CWE-346",
10416
+ level: "error",
10417
+ severity: "high",
10418
+ header: "Access-Control-Allow-Origin",
10419
+ kind: "unsafe-value",
10420
+ message: "Access-Control-Allow-Origin set to a dynamic value \u2014 possible origin reflection",
10421
+ fix: "Validate the Origin request header against an allowlist before echoing it back",
10422
+ note: "Fires when the value is not a string literal (likely reflected from request)"
10423
+ }
10424
+ ];
10290
10425
 
10291
10426
  // src/analysis/taint-matcher.ts
10292
10427
  var PYTHON_TAINTED_PATTERNS = [
@@ -18408,7 +18543,14 @@ var JS_TAINTED_PATTERNS = [
18408
18543
  { pattern: /\bwindow\.name\b/, type: "dom_input" },
18409
18544
  { pattern: /\bdocument\.URL\b/, type: "http_path" },
18410
18545
  { pattern: /\bdocument\.documentURI\b/, type: "http_path" },
18411
- { pattern: /\blocation\.pathname\b/, type: "http_path" }
18546
+ { pattern: /\blocation\.pathname\b/, type: "http_path" },
18547
+ // DOM propagation globals - deprecated/obscure but still exploitable as taint conduits.
18548
+ // Writing attacker-controlled data here and reading it back preserves taint (DOMPropagation pattern).
18549
+ { pattern: /\bwindow\.status\b/, type: "dom_input" },
18550
+ { pattern: /\bdocument\.title\b/, type: "dom_input" },
18551
+ { pattern: /\bhistory\.state\b/, type: "dom_input" },
18552
+ { pattern: /\blocalStorage\.getItem\b/, type: "dom_input" },
18553
+ { pattern: /\bsessionStorage\.getItem\b/, type: "dom_input" }
18412
18554
  ];
18413
18555
  var PYTHON_TAINTED_PATTERNS2 = [
18414
18556
  { pattern: /\brequest\.args\b/, type: "http_param" },
@@ -22749,6 +22891,154 @@ var NamingConventionPass = class {
22749
22891
  }
22750
22892
  };
22751
22893
 
22894
+ // src/analysis/passes/security-headers-pass.ts
22895
+ var HEADER_WRITE_METHODS = /* @__PURE__ */ new Set([
22896
+ "setHeader",
22897
+ "addHeader",
22898
+ // Java + Node
22899
+ "set",
22900
+ "header",
22901
+ // Express res.set / res.header
22902
+ "insert_header",
22903
+ // Actix Web HttpResponse
22904
+ "insert"
22905
+ // Rust HeaderMap.insert (best-effort)
22906
+ ]);
22907
+ var HANDLER_ANNOTATION_RE = /\b(Controller|RestController|RequestMapping|GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping|RequestBody|RequestParam|PathVariable|route|blueprint|api_view|get|post|put|delete|patch|head|options)\b/i;
22908
+ var JS_ROUTER_RECEIVERS = /* @__PURE__ */ new Set(["app", "router", "server", "route"]);
22909
+ var JS_ROUTE_METHODS = /* @__PURE__ */ new Set([
22910
+ "get",
22911
+ "post",
22912
+ "put",
22913
+ "delete",
22914
+ "patch",
22915
+ "all",
22916
+ "use",
22917
+ "head",
22918
+ "options"
22919
+ ]);
22920
+ var SecurityHeadersPass = class {
22921
+ name = "security-headers";
22922
+ category = "security";
22923
+ rules;
22924
+ constructor(options = {}) {
22925
+ this.rules = options.rules ?? DEFAULT_HEADER_RULES;
22926
+ }
22927
+ run(ctx) {
22928
+ const { graph } = ctx;
22929
+ const file = graph.ir.meta.file;
22930
+ const calls = graph.ir.calls;
22931
+ const writtenHeaders = /* @__PURE__ */ new Map();
22932
+ for (const call of calls) {
22933
+ if (!HEADER_WRITE_METHODS.has(call.method_name)) continue;
22934
+ if (call.arguments.length < 1) continue;
22935
+ const nameLiteral = literalOf(call.arguments[0]);
22936
+ if (nameLiteral === null) continue;
22937
+ const key = nameLiteral.toLowerCase();
22938
+ let list = writtenHeaders.get(key);
22939
+ if (!list) {
22940
+ list = [];
22941
+ writtenHeaders.set(key, list);
22942
+ }
22943
+ list.push(call);
22944
+ }
22945
+ const hasHandler = detectHandler(graph, calls);
22946
+ for (const rule of this.rules) {
22947
+ const headerKey = rule.header.toLowerCase();
22948
+ const writes = writtenHeaders.get(headerKey) ?? [];
22949
+ if (rule.kind === "missing") {
22950
+ if (writes.length > 0) continue;
22951
+ if (rule.requiresHandler !== false && !hasHandler) continue;
22952
+ ctx.addFinding({
22953
+ id: `${rule.rule_id}-${file}`,
22954
+ pass: this.name,
22955
+ category: this.category,
22956
+ rule_id: rule.rule_id,
22957
+ cwe: rule.cwe,
22958
+ severity: rule.severity,
22959
+ level: rule.level,
22960
+ message: rule.message,
22961
+ file,
22962
+ line: 1,
22963
+ fix: rule.fix
22964
+ });
22965
+ continue;
22966
+ }
22967
+ for (const call of writes) {
22968
+ const valueArg = call.arguments[1];
22969
+ if (!valueArg) continue;
22970
+ const valueLiteral = literalOf(valueArg);
22971
+ if (rule.kind === "weak-value") {
22972
+ if (valueLiteral === null) continue;
22973
+ if (!rule.valuePattern) continue;
22974
+ if (!rule.valuePattern.test(valueLiteral)) continue;
22975
+ } else {
22976
+ if (valueLiteral !== null) continue;
22977
+ }
22978
+ ctx.addFinding({
22979
+ id: `${rule.rule_id}-${file}-${call.location.line}`,
22980
+ pass: this.name,
22981
+ category: this.category,
22982
+ rule_id: rule.rule_id,
22983
+ cwe: rule.cwe,
22984
+ severity: rule.severity,
22985
+ level: rule.level,
22986
+ message: rule.message,
22987
+ file,
22988
+ line: call.location.line,
22989
+ fix: rule.fix,
22990
+ snippet: valueLiteral !== null ? `${rule.header}: ${valueLiteral}` : `${rule.header}: ${valueArg.expression}`,
22991
+ evidence: {
22992
+ header: rule.header,
22993
+ value: valueLiteral,
22994
+ expression: valueArg.expression,
22995
+ kind: rule.kind
22996
+ }
22997
+ });
22998
+ }
22999
+ }
23000
+ return { hasHandler, writtenHeaders };
23001
+ }
23002
+ };
23003
+ function literalOf(arg) {
23004
+ if (arg.literal !== null && arg.literal !== void 0 && arg.literal !== "") {
23005
+ return stripQuotes2(arg.literal);
23006
+ }
23007
+ const expr = arg.expression.trim();
23008
+ if (expr.startsWith('"') && expr.endsWith('"') || expr.startsWith("'") && expr.endsWith("'") || expr.startsWith("`") && expr.endsWith("`")) {
23009
+ if (expr.startsWith("`") && expr.includes("${")) return null;
23010
+ return expr.slice(1, -1);
23011
+ }
23012
+ return null;
23013
+ }
23014
+ function stripQuotes2(s) {
23015
+ if (s.length < 2) return s;
23016
+ const first = s[0];
23017
+ const last = s[s.length - 1];
23018
+ if (first === '"' && last === '"' || first === "'" && last === "'" || first === "`" && last === "`") {
23019
+ return s.slice(1, -1);
23020
+ }
23021
+ return s;
23022
+ }
23023
+ function detectHandler(graph, calls) {
23024
+ for (const type of graph.ir.types) {
23025
+ if (type.annotations.some((a) => HANDLER_ANNOTATION_RE.test(a))) return true;
23026
+ for (const method of type.methods) {
23027
+ if (method.annotations.some((a) => HANDLER_ANNOTATION_RE.test(a))) return true;
23028
+ }
23029
+ }
23030
+ for (const call of calls) {
23031
+ if (!JS_ROUTE_METHODS.has(call.method_name)) continue;
23032
+ if (!call.receiver) continue;
23033
+ if (!JS_ROUTER_RECEIVERS.has(call.receiver)) continue;
23034
+ const first = call.arguments[0];
23035
+ if (!first) continue;
23036
+ const literal = literalOf(first);
23037
+ if (literal !== null && literal.startsWith("/")) return true;
23038
+ }
23039
+ return false;
23040
+ }
23041
+
22752
23042
  // src/analysis/metrics/passes/size-metrics-pass.ts
22753
23043
  var SizeMetricsPass = class {
22754
23044
  name = "size-metrics";
@@ -23607,6 +23897,7 @@ async function analyze(code, filePath, language, options = {}) {
23607
23897
  if (!disabledPasses.has("missing-stream")) pipeline.add(new MissingStreamPass());
23608
23898
  if (!disabledPasses.has("god-class")) pipeline.add(new GodClassPass());
23609
23899
  if (!disabledPasses.has("naming-convention")) pipeline.add(new NamingConventionPass(passOpts.namingConvention));
23900
+ if (!disabledPasses.has("security-headers")) pipeline.add(new SecurityHeadersPass(passOpts.securityHeaders));
23610
23901
  const { results, findings } = pipeline.run(graph, code, language, config);
23611
23902
  const sinkFilter = results.get("sink-filter");
23612
23903
  const interProc = results.get("interprocedural");
@@ -9732,6 +9732,38 @@ var DEFAULT_SINKS = [
9732
9732
  { method: "processRequest", class: "Broker", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9733
9733
  // DolphinScheduler
9734
9734
  { method: "execute", class: "TaskExecuteThread", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9735
+ // Apache Commons JEXL (JEXL expression injection)
9736
+ { method: "createExpression", class: "JexlEngine", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9737
+ { method: "createScript", class: "JexlEngine", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9738
+ { method: "evaluate", class: "JexlExpression", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [] },
9739
+ { method: "execute", class: "JexlScript", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [] },
9740
+ // Janino expression evaluator (Calcite/Flink/Drill)
9741
+ { method: "createFastEvaluator", class: "ExpressionEvaluator", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9742
+ { method: "cook", class: "ExpressionEvaluator", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9743
+ { method: "cook", class: "ScriptEvaluator", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9744
+ { method: "cook", class: "ClassBodyEvaluator", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9745
+ { method: "cook", class: "SimpleCompiler", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9746
+ // Apache Camel Simple language (CVE-2018-8041 and similar)
9747
+ { method: "createExpression", class: "SimpleLanguage", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9748
+ { method: "createPredicate", class: "SimpleLanguage", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9749
+ // Thymeleaf StandardExpression (CVE-2023-38286 and similar)
9750
+ { method: "parseExpression", class: "StandardExpressionParser", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [1] },
9751
+ { method: "getValue", class: "StandardExpression", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [] },
9752
+ // FreeMarker direct template construction (CVE-2022-26336 and similar)
9753
+ { method: "Template", class: "Template", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [1] },
9754
+ // new Template(name, tainted)
9755
+ { method: "getTemplate", class: "Configuration", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
9756
+ // Jinjava (Java Jinja template engine)
9757
+ { method: "render", class: "Jinjava", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
9758
+ { method: "renderForResult", class: "Jinjava", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
9759
+ // Spring Cloud Function RoutingFunction (CVE-2022-22963)
9760
+ { method: "getRequestedBeanName", class: "RoutingFunction", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [] },
9761
+ // Kotlin reflection (RCE via reflective construction)
9762
+ { method: "createInstance", class: "KClass", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [] },
9763
+ { method: "callBy", class: "KFunction", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
9764
+ // Struts 2 deep injection (CVE-2017-5638 and descendants)
9765
+ { method: "translateVariables", class: "TextParseUtil", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9766
+ { method: "evaluate", class: "StrutsResultSupport", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9735
9767
  // Deserialization (CWE-502)
9736
9768
  { method: "readObject", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [] },
9737
9769
  { method: "readUnshared", class: "ObjectInputStream", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [] },
@@ -10253,6 +10285,17 @@ var DEFAULT_SANITIZERS = [
10253
10285
  { method: "encodeForHTML", removes: ["xss"] },
10254
10286
  { method: "escapeXml", removes: ["xss"] },
10255
10287
  { method: "htmlEscape", removes: ["xss"] },
10288
+ { method: "escapeHtml4", removes: ["xss"] },
10289
+ // Apache Commons StringEscapeUtils
10290
+ { method: "escapeHtml3", removes: ["xss"] },
10291
+ // Apache Commons StringEscapeUtils
10292
+ { method: "htmlSpecialChars", removes: ["xss"] },
10293
+ // PHP-style / common wrapper
10294
+ { method: "forHtml", class: "Encode", removes: ["xss"] },
10295
+ // OWASP Java Encoder
10296
+ { method: "forHtmlContent", class: "Encode", removes: ["xss"] },
10297
+ { method: "forHtmlAttribute", class: "Encode", removes: ["xss"] },
10298
+ { method: "forJavaScript", class: "Encode", removes: ["xss"] },
10256
10299
  { method: "encode_text", removes: ["xss"] },
10257
10300
  // Rust html_escape crate
10258
10301
  { method: "encode_safe", removes: ["xss"] },
@@ -10262,6 +10305,11 @@ var DEFAULT_SANITIZERS = [
10262
10305
  { method: "encodeForJavaScript", removes: ["xss"] },
10263
10306
  { method: "encodeForCSS", removes: ["xss"] },
10264
10307
  { method: "encodeForURL", removes: ["xss", "ssrf"] },
10308
+ // URL encoding wrapper aliases (common patterns in benchmarks and real-world code)
10309
+ { method: "encodeURL", removes: ["xss", "ssrf"] },
10310
+ { method: "urlEncode", removes: ["xss", "ssrf"] },
10311
+ { method: "escapeUrl", removes: ["xss", "ssrf"] },
10312
+ { method: "escapeURL", removes: ["xss", "ssrf"] },
10265
10313
  // Path Traversal
10266
10314
  { method: "normalize", class: "Path", removes: ["path_traversal"] },
10267
10315
  { method: "getCanonicalPath", class: "File", removes: ["path_traversal"] },
@@ -9667,6 +9667,38 @@ var DEFAULT_SINKS = [
9667
9667
  { method: "processRequest", class: "Broker", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9668
9668
  // DolphinScheduler
9669
9669
  { method: "execute", class: "TaskExecuteThread", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9670
+ // Apache Commons JEXL (JEXL expression injection)
9671
+ { method: "createExpression", class: "JexlEngine", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9672
+ { method: "createScript", class: "JexlEngine", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9673
+ { method: "evaluate", class: "JexlExpression", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [] },
9674
+ { method: "execute", class: "JexlScript", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [] },
9675
+ // Janino expression evaluator (Calcite/Flink/Drill)
9676
+ { method: "createFastEvaluator", class: "ExpressionEvaluator", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9677
+ { method: "cook", class: "ExpressionEvaluator", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9678
+ { method: "cook", class: "ScriptEvaluator", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9679
+ { method: "cook", class: "ClassBodyEvaluator", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9680
+ { method: "cook", class: "SimpleCompiler", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9681
+ // Apache Camel Simple language (CVE-2018-8041 and similar)
9682
+ { method: "createExpression", class: "SimpleLanguage", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9683
+ { method: "createPredicate", class: "SimpleLanguage", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9684
+ // Thymeleaf StandardExpression (CVE-2023-38286 and similar)
9685
+ { method: "parseExpression", class: "StandardExpressionParser", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [1] },
9686
+ { method: "getValue", class: "StandardExpression", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [] },
9687
+ // FreeMarker direct template construction (CVE-2022-26336 and similar)
9688
+ { method: "Template", class: "Template", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [1] },
9689
+ // new Template(name, tainted)
9690
+ { method: "getTemplate", class: "Configuration", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
9691
+ // Jinjava (Java Jinja template engine)
9692
+ { method: "render", class: "Jinjava", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
9693
+ { method: "renderForResult", class: "Jinjava", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
9694
+ // Spring Cloud Function RoutingFunction (CVE-2022-22963)
9695
+ { method: "getRequestedBeanName", class: "RoutingFunction", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [] },
9696
+ // Kotlin reflection (RCE via reflective construction)
9697
+ { method: "createInstance", class: "KClass", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [] },
9698
+ { method: "callBy", class: "KFunction", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
9699
+ // Struts 2 deep injection (CVE-2017-5638 and descendants)
9700
+ { method: "translateVariables", class: "TextParseUtil", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9701
+ { method: "evaluate", class: "StrutsResultSupport", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
9670
9702
  // Deserialization (CWE-502)
9671
9703
  { method: "readObject", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [] },
9672
9704
  { method: "readUnshared", class: "ObjectInputStream", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [] },
@@ -10188,6 +10220,17 @@ var DEFAULT_SANITIZERS = [
10188
10220
  { method: "encodeForHTML", removes: ["xss"] },
10189
10221
  { method: "escapeXml", removes: ["xss"] },
10190
10222
  { method: "htmlEscape", removes: ["xss"] },
10223
+ { method: "escapeHtml4", removes: ["xss"] },
10224
+ // Apache Commons StringEscapeUtils
10225
+ { method: "escapeHtml3", removes: ["xss"] },
10226
+ // Apache Commons StringEscapeUtils
10227
+ { method: "htmlSpecialChars", removes: ["xss"] },
10228
+ // PHP-style / common wrapper
10229
+ { method: "forHtml", class: "Encode", removes: ["xss"] },
10230
+ // OWASP Java Encoder
10231
+ { method: "forHtmlContent", class: "Encode", removes: ["xss"] },
10232
+ { method: "forHtmlAttribute", class: "Encode", removes: ["xss"] },
10233
+ { method: "forJavaScript", class: "Encode", removes: ["xss"] },
10191
10234
  { method: "encode_text", removes: ["xss"] },
10192
10235
  // Rust html_escape crate
10193
10236
  { method: "encode_safe", removes: ["xss"] },
@@ -10197,6 +10240,11 @@ var DEFAULT_SANITIZERS = [
10197
10240
  { method: "encodeForJavaScript", removes: ["xss"] },
10198
10241
  { method: "encodeForCSS", removes: ["xss"] },
10199
10242
  { method: "encodeForURL", removes: ["xss", "ssrf"] },
10243
+ // URL encoding wrapper aliases (common patterns in benchmarks and real-world code)
10244
+ { method: "encodeURL", removes: ["xss", "ssrf"] },
10245
+ { method: "urlEncode", removes: ["xss", "ssrf"] },
10246
+ { method: "escapeUrl", removes: ["xss", "ssrf"] },
10247
+ { method: "escapeURL", removes: ["xss", "ssrf"] },
10200
10248
  // Path Traversal
10201
10249
  { method: "normalize", class: "Path", removes: ["path_traversal"] },
10202
10250
  { method: "getCanonicalPath", class: "File", removes: ["path_traversal"] },
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Types for YAML configuration files (configs/sources/, configs/sinks/)
3
3
  */
4
- import type { Severity, SinkType, SourceType } from './index.js';
4
+ import type { SarifLevel, Severity, SinkType, SourceType } from './index.js';
5
5
  export interface SourceConfig {
6
6
  sources: SourcePattern[];
7
7
  }
@@ -43,3 +43,47 @@ export interface TaintConfig {
43
43
  sinks: SinkPattern[];
44
44
  sanitizers: SanitizerPattern[];
45
45
  }
46
+ /**
47
+ * A rule evaluated by SecurityHeadersPass against HTTP response header
48
+ * writes (setHeader/addHeader) and handler presence. Emits SastFindings
49
+ * without going through the taint source→sink machinery, since headers
50
+ * are a call-site literal inspection problem, not a data-flow problem.
51
+ */
52
+ export interface HeaderRule {
53
+ /** Rule id (matches docs/PASSES.md rule_id column). */
54
+ rule_id: string;
55
+ /** CWE identifier (e.g. 'CWE-1021', 'CWE-346', 'CWE-942'). */
56
+ cwe: string;
57
+ /** SARIF level: 'error' | 'warning' | 'note' | 'none'. */
58
+ level: SarifLevel;
59
+ /** Severity bucket: 'critical' | 'high' | 'medium' | 'low'. */
60
+ severity: Severity;
61
+ /** HTTP response header this rule applies to (case-insensitive). */
62
+ header: string;
63
+ /**
64
+ * Rule kind:
65
+ * - 'missing' → file has an HTTP handler but never writes this header
66
+ * - 'weak-value' → header written with a value matching `matcher`
67
+ * (e.g. 'ALLOW-FROM', 'null', 'http://…')
68
+ * - 'unsafe-value' → value is dynamic / reflected (not a string literal)
69
+ */
70
+ kind: 'missing' | 'weak-value' | 'unsafe-value';
71
+ /**
72
+ * Value pattern for 'weak-value' rules. Matched against the literal
73
+ * second argument of setHeader/addHeader (case-insensitive).
74
+ */
75
+ valuePattern?: RegExp;
76
+ /**
77
+ * If true (the default for kind='missing'), the rule only fires when
78
+ * the file contains at least one HTTP handler (annotated controller
79
+ * method, Express/Koa route, Rust extractor, etc.). Prevents noise on
80
+ * library code, configuration files, and tests.
81
+ */
82
+ requiresHandler?: boolean;
83
+ /** Human-readable message (header name interpolated with ${header}). */
84
+ message: string;
85
+ /** Suggested fix rendered into SastFinding.fix. */
86
+ fix?: string;
87
+ /** Optional note for PASSES.md / debugging. */
88
+ note?: string;
89
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "circle-ir",
3
- "version": "3.18.7",
3
+ "version": "3.19.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",