cognium-dev 3.97.0 → 3.99.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.
Files changed (2) hide show
  1. package/dist/cli.js +70 -10
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -12245,6 +12245,27 @@ function isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines) {
12245
12245
  }
12246
12246
  return false;
12247
12247
  }
12248
+ var TEMPLATE_LITERAL_RECEIVER_RE = /^Template\(\s*(?:"[^"\\]*"|'[^'\\]*')\s*\)$/;
12249
+ function isSafeJinjaRenderCall(call, pattern, language) {
12250
+ if (language !== "python")
12251
+ return false;
12252
+ if (pattern.type !== "xss" && pattern.type !== "code_injection")
12253
+ return false;
12254
+ const method = call.method_name;
12255
+ if (method === "render_template_string") {
12256
+ const arg0 = call.arguments.find((a) => a.position === 0);
12257
+ return typeof arg0?.literal === "string";
12258
+ }
12259
+ if (method === "Template" || method === "from_string") {
12260
+ const arg0 = call.arguments.find((a) => a.position === 0);
12261
+ return typeof arg0?.literal === "string";
12262
+ }
12263
+ if (method === "render") {
12264
+ const receiver = (call.receiver ?? "").trim();
12265
+ return TEMPLATE_LITERAL_RECEIVER_RE.test(receiver);
12266
+ }
12267
+ return false;
12268
+ }
12248
12269
  function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
12249
12270
  const sinkMap = new Map;
12250
12271
  for (const call of calls) {
@@ -12286,6 +12307,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
12286
12307
  if (isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines)) {
12287
12308
  continue;
12288
12309
  }
12310
+ if (isSafeJinjaRenderCall(call, pattern, language)) {
12311
+ continue;
12312
+ }
12289
12313
  const location = formatCallLocation(call);
12290
12314
  const key = `${location}:${call.location.line}:${pattern.cwe}`;
12291
12315
  const confidence = calculateSinkConfidence(call, pattern);
@@ -22711,6 +22735,13 @@ function findPythonTrustBoundaryViolations(sourceCode, taintedVars) {
22711
22735
  }
22712
22736
  return violations;
22713
22737
  }
22738
+ var JINJA_LITERAL_ARG_RE_FRAG = `(?:"[^"\\\\]*"|'[^'\\\\]*')`;
22739
+ var JINJA_SAFE_RTS_RE = new RegExp(`^(?:flask\\.|jinja2\\.)?render_template_string\\s*\\(\\s*${JINJA_LITERAL_ARG_RE_FRAG}\\s*[,)]`);
22740
+ var JINJA_SAFE_TEMPLATE_RE = new RegExp(`^(?:jinja2\\.)?Template\\s*\\(\\s*${JINJA_LITERAL_ARG_RE_FRAG}\\s*\\)(?:\\s*\\.render\\s*\\(|\\s*$)`);
22741
+ function isSafeJinjaReturnExpr(expr) {
22742
+ const trimmed = expr.trim();
22743
+ return JINJA_SAFE_RTS_RE.test(trimmed) || JINJA_SAFE_TEMPLATE_RE.test(trimmed);
22744
+ }
22714
22745
  function findPythonReturnXSSSinks(sourceCode, taintedVars) {
22715
22746
  if (taintedVars.size === 0)
22716
22747
  return [];
@@ -22732,6 +22763,8 @@ function findPythonReturnXSSSinks(sourceCode, taintedVars) {
22732
22763
  const looksLikeHTML = expr.includes("<") || /['"]\s*\+/.test(expr) || /\+\s*['"]/.test(expr) || /f['"][^'"]*\{/.test(expr);
22733
22764
  if (!looksLikeHTML)
22734
22765
  continue;
22766
+ if (isSafeJinjaReturnExpr(expr))
22767
+ continue;
22735
22768
  sinks.push({ sinkLine: i2 + 1 });
22736
22769
  }
22737
22770
  return sinks;
@@ -23594,7 +23627,7 @@ class SinkFilterPass {
23594
23627
  if (["javascript", "typescript"].includes(language)) {
23595
23628
  const sourceLines = ctx.code.split(`
23596
23629
  `);
23597
- const guardPatterns = /\b(?:includes|startsWith|endsWith|indexOf|test|match)\s*\(/;
23630
+ const guardPatterns = /\b(?:includes|startsWith|endsWith|indexOf|test|match|has)\s*\(/;
23598
23631
  filtered = filtered.filter((sink) => {
23599
23632
  if (sink.type !== "open_redirect" && sink.type !== "crlf") {
23600
23633
  return true;
@@ -23614,6 +23647,9 @@ class SinkFilterPass {
23614
23647
  if (setHeaderMatch) {
23615
23648
  return false;
23616
23649
  }
23650
+ if (sink.method === "cookie" && sink.type === "crlf") {
23651
+ return false;
23652
+ }
23617
23653
  return true;
23618
23654
  });
23619
23655
  }
@@ -31655,15 +31691,34 @@ function isExceptionExpression(expr) {
31655
31691
  if (!expr)
31656
31692
  return false;
31657
31693
  const e = expr.trim();
31658
- return /\b(err|error|exc|exception|e|t|throwable)\.(stack|message|toString\(|getMessage\(|getStackTrace\(|getLocalizedMessage\(|getCause\()/i.test(e) || /\btraceback\.(format_exc|format_exception|print_exc)\b/i.test(e) || /\bdebug\.Stack\(\)/.test(e) || /\bstr\(\s*(err|error|exc|exception|e)\s*\)/i.test(e) || /\bString\(\s*(err|error|exc|exception|e)\s*\)/i.test(e);
31694
+ return /\b(err|error|exc|exception|e|t|throwable)\.(stack|toString\(|getStackTrace\(|getLocalizedMessage\(|getCause\()/i.test(e) || /\btraceback\.(format_exc|format_exception|print_exc)\b/i.test(e) || /\bdebug\.Stack\(\)/.test(e) || /\bstr\(\s*(err|error|exc|exception|e)\s*\)/i.test(e) || /\bString\(\s*(err|error|exc|exception|e)\s*\)/i.test(e);
31695
+ }
31696
+ var PY_FILE_HANDLE_OPEN_RE = /^\s*(?:with\s+open\s*\([^)]*\)\s+as\s+(\w+)\s*:|(\w+)\s*=\s*open\s*\()/;
31697
+ function isPythonFileHandle(receiver, callLine, sourceLines) {
31698
+ if (!receiver || !/^[A-Za-z_]\w*$/.test(receiver))
31699
+ return false;
31700
+ const start2 = Math.max(0, callLine - 11);
31701
+ for (let i2 = callLine - 2;i2 >= start2; i2--) {
31702
+ const ln = sourceLines[i2] ?? "";
31703
+ const m = ln.match(PY_FILE_HANDLE_OPEN_RE);
31704
+ if (!m)
31705
+ continue;
31706
+ const name2 = m[1] ?? m[2];
31707
+ if (name2 === receiver)
31708
+ return true;
31709
+ }
31710
+ return false;
31659
31711
  }
31660
31712
  function argIsException(arg) {
31661
31713
  if (!arg)
31662
31714
  return false;
31715
+ const expr = (arg.expression ?? "").trim();
31663
31716
  if (arg.variable && /^(err|error|exc|exception|e|t|throwable)$/i.test(arg.variable)) {
31664
- return true;
31717
+ if (!expr || expr === arg.variable)
31718
+ return true;
31719
+ return isExceptionExpression(expr);
31665
31720
  }
31666
- return isExceptionExpression(arg.expression);
31721
+ return isExceptionExpression(expr);
31667
31722
  }
31668
31723
  function detectJavaPrintStackTrace(call) {
31669
31724
  if (call.method_name !== "printStackTrace")
@@ -31680,13 +31735,16 @@ function detectJavaPrintStackTrace(call) {
31680
31735
  }
31681
31736
  return null;
31682
31737
  }
31683
- function detectResponseLeakCall(call) {
31738
+ function detectResponseLeakCall(call, language, sourceLines) {
31684
31739
  const method = call.method_name ?? "";
31685
31740
  const receiver = call.receiver ?? "";
31686
31741
  if (!RESPONSE_SEND_METHODS.has(method))
31687
31742
  return null;
31688
31743
  if (LOGGER_RECEIVER_RE.test(receiver))
31689
31744
  return null;
31745
+ if (language === "python" && (method === "write" || method === "writelines") && sourceLines && isPythonFileHandle(receiver, call.location.line, sourceLines)) {
31746
+ return null;
31747
+ }
31690
31748
  const recTail = receiver.split(".").pop() ?? receiver;
31691
31749
  const recHead = receiver.split(".")[0] ?? receiver;
31692
31750
  if (!RESPONSE_RECEIVER_RE.test(recTail) && !RESPONSE_RECEIVER_RE.test(recHead)) {
@@ -31731,6 +31789,8 @@ class InfoDisclosureStacktracePass {
31731
31789
  const { graph, language } = ctx;
31732
31790
  const file = graph.ir.meta.file;
31733
31791
  const findings = [];
31792
+ const sourceLines = ctx.code.split(`
31793
+ `);
31734
31794
  if (language === "python") {
31735
31795
  for (const f of detectPythonTracebackReturn(ctx)) {
31736
31796
  findings.push({ line: f.line, api: f.api, language });
@@ -31742,9 +31802,9 @@ class InfoDisclosureStacktracePass {
31742
31802
  if (language === "java") {
31743
31803
  api = detectJavaPrintStackTrace(call);
31744
31804
  if (!api)
31745
- api = detectResponseLeakCall(call);
31805
+ api = detectResponseLeakCall(call, language, sourceLines);
31746
31806
  } else if (language === "javascript" || language === "typescript") {
31747
- api = detectResponseLeakCall(call);
31807
+ api = detectResponseLeakCall(call, language, sourceLines);
31748
31808
  } else if (language === "go") {
31749
31809
  const method = call.method_name ?? "";
31750
31810
  const rec = call.receiver ?? "";
@@ -31765,10 +31825,10 @@ class InfoDisclosureStacktracePass {
31765
31825
  }
31766
31826
  }
31767
31827
  } else {
31768
- api = detectResponseLeakCall(call);
31828
+ api = detectResponseLeakCall(call, language, sourceLines);
31769
31829
  }
31770
31830
  } else if (language === "python") {
31771
- api = detectResponseLeakCall(call);
31831
+ api = detectResponseLeakCall(call, language, sourceLines);
31772
31832
  }
31773
31833
  if (!api)
31774
31834
  continue;
@@ -34708,7 +34768,7 @@ var colors = {
34708
34768
  };
34709
34769
 
34710
34770
  // src/version.ts
34711
- var version = "3.97.0";
34771
+ var version = "3.99.0";
34712
34772
 
34713
34773
  // src/formatters.ts
34714
34774
  var SINK_SEVERITY = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "3.97.0",
3
+ "version": "3.99.0",
4
4
  "description": "Static Application Security Testing CLI for detecting security vulnerabilities via taint tracking",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -65,7 +65,7 @@
65
65
  "registry": "https://registry.npmjs.org/"
66
66
  },
67
67
  "dependencies": {
68
- "circle-ir": "^3.97.0"
68
+ "circle-ir": "^3.99.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.5.0",