cognium-dev 4.14.0 → 4.14.2

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 +346 -68
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -7421,6 +7421,17 @@ function extractGoCallInfo(node) {
7421
7421
  if (operand.type === "identifier") {
7422
7422
  const resolved = resolveGoLocalReceiverType(opText, node);
7423
7423
  receiver = resolved !== null ? resolved : opText;
7424
+ } else if (operand.type === "selector_expression" && methodName === "Get") {
7425
+ const fieldNode = operand.childForFieldName("field");
7426
+ const inner = operand.childForFieldName("operand");
7427
+ const fieldName = fieldNode ? getNodeText(fieldNode) : "";
7428
+ const innerName = inner && inner.type === "identifier" ? getNodeText(inner) : null;
7429
+ const innerType = innerName !== null ? resolveGoLocalReceiverType(innerName, node) : null;
7430
+ if (innerType === "Request" && (fieldName === "PostForm" || fieldName === "Form")) {
7431
+ receiver = fieldName === "PostForm" ? "RequestPostForm" : "RequestForm";
7432
+ } else {
7433
+ receiver = opText;
7434
+ }
7424
7435
  } else {
7425
7436
  receiver = opText;
7426
7437
  }
@@ -9641,6 +9652,38 @@ function extractFieldDefs(body2, startId) {
9641
9652
  function extractJSParameterDefs(params, startId) {
9642
9653
  const defs = [];
9643
9654
  let id = startId;
9655
+ const addPatternDefs = (patternNode, line) => {
9656
+ if (patternNode.type === "identifier" || patternNode.type === "shorthand_property_identifier_pattern") {
9657
+ defs.push({
9658
+ id: id++,
9659
+ variable: getNodeText(patternNode),
9660
+ line,
9661
+ kind: "param"
9662
+ });
9663
+ return;
9664
+ }
9665
+ if (patternNode.type === "rest_pattern" || patternNode.type === "rest_element") {
9666
+ const nameNode = patternNode.namedChildCount > 0 ? patternNode.namedChild(0) : null;
9667
+ if (!nameNode)
9668
+ return;
9669
+ addPatternDefs(nameNode, line);
9670
+ return;
9671
+ }
9672
+ if (patternNode.type === "assignment_pattern") {
9673
+ const leftNode = patternNode.childForFieldName("left");
9674
+ if (leftNode)
9675
+ addPatternDefs(leftNode, line);
9676
+ return;
9677
+ }
9678
+ if (patternNode.type === "object_pattern" || patternNode.type === "array_pattern") {
9679
+ const destructDefs = extractDestructuringDefs(patternNode, id);
9680
+ for (const def of destructDefs) {
9681
+ def.kind = "param";
9682
+ defs.push(def);
9683
+ id++;
9684
+ }
9685
+ }
9686
+ };
9644
9687
  for (let i2 = 0;i2 < params.childCount; i2++) {
9645
9688
  const param = params.child(i2);
9646
9689
  if (!param)
@@ -9690,14 +9733,8 @@ function extractJSParameterDefs(params, startId) {
9690
9733
  }
9691
9734
  } else if (param.type === "required_parameter" || param.type === "optional_parameter") {
9692
9735
  const patternNode = param.childForFieldName("pattern");
9693
- if (patternNode && patternNode.type === "identifier") {
9694
- defs.push({
9695
- id: id++,
9696
- variable: getNodeText(patternNode),
9697
- line: param.startPosition.row + 1,
9698
- kind: "param"
9699
- });
9700
- }
9736
+ if (patternNode)
9737
+ addPatternDefs(patternNode, param.startPosition.row + 1);
9701
9738
  }
9702
9739
  }
9703
9740
  return defs;
@@ -12022,7 +12059,11 @@ var DEFAULT_SINKS = [
12022
12059
  { method: "setExpireTimeSeconds", class: "StaticFilesConfiguration", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
12023
12060
  { method: "configureJarCase", class: "StaticFilesConfiguration", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
12024
12061
  { method: "toString", class: "StringUtils", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
12025
- { method: "getResource", class: "ServletResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
12062
+ { method: "getResource", class: "ServletResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
12063
+ { method: "getResource", class: "ResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
12064
+ { method: "getResource", class: "PathResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
12065
+ { method: "getResource", class: "FileResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
12066
+ { method: "getResource", class: "ClassPathResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
12026
12067
  { method: "deleteObject", class: "FileSystemBucket", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
12027
12068
  { method: "regularExpressionMatcher", class: "Validators", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
12028
12069
  { method: "checkMessage", class: "Validators", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
@@ -13808,7 +13849,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
13808
13849
  continue;
13809
13850
  for (const param of method.parameters) {
13810
13851
  const hasCSharpBindingAttr = language === "csharp" && param.annotations.some((a) => CSHARP_BINDING_ATTRS.has(a));
13811
- const isTaintable = hasCSharpBindingAttr ? true : param.type ? isInterproceduralTaintableType(param.type, language) : true;
13852
+ const isDestructureBinding = param.name.startsWith("{") || param.name.startsWith("[");
13853
+ const isTaintable = hasCSharpBindingAttr ? true : isDestructureBinding ? true : param.type ? isInterproceduralTaintableType(param.type, language) : true;
13812
13854
  if (isTaintable) {
13813
13855
  const paramLine = param.line ?? method.start_line;
13814
13856
  sources.push({
@@ -14022,7 +14064,8 @@ function isInterproceduralTaintableType(typeName, language) {
14022
14064
  function isParameterizedQueryCall(call, pattern) {
14023
14065
  if (pattern.type !== "sql_injection")
14024
14066
  return false;
14025
- const queryPos = pattern.arg_positions.length > 0 ? Math.min(...pattern.arg_positions) : 0;
14067
+ const positions = pattern.arg_positions ?? [];
14068
+ const queryPos = positions.length > 0 ? Math.min(...positions) : 0;
14026
14069
  const queryArg = call.arguments.find((a) => a.position === queryPos);
14027
14070
  if (queryArg) {
14028
14071
  const queryText = queryArg.literal ?? queryArg.expression ?? "";
@@ -18264,6 +18307,9 @@ function buildSanitizersByLine(sanitizers) {
18264
18307
  }
18265
18308
  return out2;
18266
18309
  }
18310
+ function concatOutsideStrings(expression) {
18311
+ return expression.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, " ").includes("+");
18312
+ }
18267
18313
  function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanitizers, sanitizersArg) {
18268
18314
  let graph;
18269
18315
  let sources;
@@ -18295,6 +18341,7 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
18295
18341
  }
18296
18342
  const taintedVars = [];
18297
18343
  const flows = [];
18344
+ const javaConcat = graph.ir.meta.language === "java";
18298
18345
  const reachableSinks = new Map;
18299
18346
  const defsByLine = graph.defsByLine;
18300
18347
  const usesByLine = graph.usesByLine;
@@ -18327,12 +18374,14 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
18327
18374
  continue;
18328
18375
  }
18329
18376
  }
18330
- const candidateUses = arg.variable ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
18377
+ const expr = typeof arg.expression === "string" ? arg.expression : "";
18378
+ const compoundConcat = javaConcat && !!arg.variable && expr !== arg.variable && concatOutsideStrings(expr);
18379
+ const candidateUses = arg.variable && !compoundConcat ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
18331
18380
  {
18332
18381
  for (const use of candidateUses) {
18333
18382
  if (use.def_id !== null) {
18334
18383
  if (allTaintedDefIds.has(use.def_id)) {
18335
- if (!arg.variable) {
18384
+ if (!arg.variable || compoundConcat) {
18336
18385
  if (typeof arg.expression !== "string" || arg.expression.length === 0)
18337
18386
  continue;
18338
18387
  const re = new RegExp(`(?:^|[^A-Za-z0-9_$])${use.variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^A-Za-z0-9_$]|$)`);
@@ -18341,7 +18390,17 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
18341
18390
  }
18342
18391
  const taintInfo = taintByDefId.get(use.def_id);
18343
18392
  if (taintInfo) {
18344
- const isSanitized = checkSanitized(taintInfo.line, sink.line, sink.type, sanitizersByLine, {
18393
+ let sanMap = sanitizersByLine;
18394
+ if (identifierOutsideSanitizerCalls(use.variable, callsAtSink, sanitizersByLine.get(sink.line) ?? [], sink.type)) {
18395
+ const stripped = new Map(sanitizersByLine);
18396
+ const kept = (sanitizersByLine.get(sink.line) ?? []).filter((s) => !sanitizerCoversSink(s, sink.type));
18397
+ if (kept.length === 0)
18398
+ stripped.delete(sink.line);
18399
+ else
18400
+ stripped.set(sink.line, kept);
18401
+ sanMap = stripped;
18402
+ }
18403
+ const isSanitized = checkSanitized(taintInfo.line, sink.line, sink.type, sanMap, {
18345
18404
  startDefId: use.def_id,
18346
18405
  chainsByToDef: graph.chainsByToDef,
18347
18406
  defById
@@ -18487,6 +18546,97 @@ var KNOWN_SINK_TYPES = new Set([
18487
18546
  "crlf",
18488
18547
  "mass_assignment"
18489
18548
  ]);
18549
+ function readBalancedArg(text, openParen) {
18550
+ let depth = 0;
18551
+ let quote = null;
18552
+ let escaped = false;
18553
+ for (let i2 = openParen;i2 < text.length; i2++) {
18554
+ const c = text[i2];
18555
+ if (quote) {
18556
+ if (escaped) {
18557
+ escaped = false;
18558
+ continue;
18559
+ }
18560
+ if (c === "\\") {
18561
+ escaped = true;
18562
+ continue;
18563
+ }
18564
+ if (c === quote)
18565
+ quote = null;
18566
+ continue;
18567
+ }
18568
+ if (c === '"' || c === "'") {
18569
+ quote = c;
18570
+ continue;
18571
+ }
18572
+ if (c === "(")
18573
+ depth++;
18574
+ else if (c === ")") {
18575
+ depth--;
18576
+ if (depth === 0)
18577
+ return text.slice(openParen + 1, i2);
18578
+ }
18579
+ }
18580
+ return null;
18581
+ }
18582
+ function sanitizerCallRefs(sanitizers, sinkType) {
18583
+ const refs = [];
18584
+ for (const san of sanitizers) {
18585
+ if (!sanitizerCoversSink(san, sinkType))
18586
+ continue;
18587
+ const m = /(?:(\w+)\.)?(\w+)\(\)/.exec(san.method);
18588
+ if (!m?.[2])
18589
+ continue;
18590
+ refs.push({ receiver: m[1], methodName: m[2] });
18591
+ }
18592
+ return refs;
18593
+ }
18594
+ function maskSanitizerCalls(expr, refs) {
18595
+ let out2 = expr;
18596
+ for (const ref of refs) {
18597
+ const needles = ref.receiver ? [`${ref.receiver}.${ref.methodName}`, ref.methodName] : [ref.methodName];
18598
+ for (const needle of needles) {
18599
+ const re = new RegExp(`\\b${needle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\(`);
18600
+ for (let guard = 0;guard < 8; guard++) {
18601
+ const m = re.exec(out2);
18602
+ if (!m)
18603
+ break;
18604
+ const open = m.index + m[0].length - 1;
18605
+ const arg = readBalancedArg(out2, open);
18606
+ if (arg === null)
18607
+ break;
18608
+ out2 = out2.slice(0, m.index) + " " + out2.slice(open + arg.length + 2);
18609
+ }
18610
+ }
18611
+ }
18612
+ return out2;
18613
+ }
18614
+ function blankStringLiterals(expr) {
18615
+ return expr.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
18616
+ }
18617
+ function identifierOutsideSanitizerCalls(variable, calls, sanitizers, sinkType) {
18618
+ if (!/^[A-Za-z_$][\w$]*$/.test(variable))
18619
+ return false;
18620
+ const refs = sanitizerCallRefs(sanitizers, sinkType);
18621
+ if (refs.length === 0)
18622
+ return false;
18623
+ const re = new RegExp(`(?:^|[^A-Za-z0-9_$])${variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^A-Za-z0-9_$]|$)`);
18624
+ for (const call of calls) {
18625
+ if (refs.some((r) => r.methodName === call.method_name))
18626
+ continue;
18627
+ for (const arg of call.arguments) {
18628
+ const expr = arg.expression ?? "";
18629
+ if (!expr)
18630
+ continue;
18631
+ const masked = maskSanitizerCalls(expr, refs);
18632
+ if (masked === expr)
18633
+ continue;
18634
+ if (re.test(blankStringLiterals(masked)))
18635
+ return true;
18636
+ }
18637
+ }
18638
+ return false;
18639
+ }
18490
18640
  function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
18491
18641
  const isKnownSinkType = KNOWN_SINK_TYPES.has(sinkType);
18492
18642
  const sanitizersAtTarget = sanitizersByLine.get(toLine);
@@ -24407,6 +24557,22 @@ class GoPlugin extends BaseLanguagePlugin {
24407
24557
  confidence: 0.9,
24408
24558
  returnTainted: true
24409
24559
  },
24560
+ {
24561
+ method: "Get",
24562
+ class: "RequestPostForm",
24563
+ type: "http_body",
24564
+ severity: "high",
24565
+ confidence: 0.9,
24566
+ returnTainted: true
24567
+ },
24568
+ {
24569
+ method: "Get",
24570
+ class: "RequestForm",
24571
+ type: "http_param",
24572
+ severity: "high",
24573
+ confidence: 0.9,
24574
+ returnTainted: true
24575
+ },
24410
24576
  {
24411
24577
  method: "GetRawData",
24412
24578
  class: "Context",
@@ -26305,6 +26471,7 @@ class LanguageSourcesPass {
26305
26471
  additionalSources.push(...findGoMultipartUploadSources(code, language));
26306
26472
  additionalSources.push(...findGoGrpcRequestSources(code, language));
26307
26473
  additionalSources.push(...findGoRequestBodySources(code, language));
26474
+ additionalSources.push(...findGoFormIndexSources(code, language));
26308
26475
  const jsDOMSinks = findJavaScriptDOMSinks(code, language);
26309
26476
  for (const s of jsDOMSinks) {
26310
26477
  const alreadyExists = additionalSinks.some((x) => x.line === s.line && x.cwe === s.cwe);
@@ -27111,6 +27278,48 @@ function findGoGrpcRequestSources(sourceCode, language) {
27111
27278
  }
27112
27279
  return sources;
27113
27280
  }
27281
+ function findGoFormIndexSources(sourceCode, language) {
27282
+ if (language !== "go")
27283
+ return [];
27284
+ if (!/\.(?:PostForm|Form|MultipartForm)\b/.test(sourceCode))
27285
+ return [];
27286
+ const lines = sourceCode.split(`
27287
+ `);
27288
+ const reqNames = new Set;
27289
+ const paramRe = /([A-Za-z_]\w*)\s+\*http\.Request\b/g;
27290
+ for (const line of lines) {
27291
+ let m;
27292
+ paramRe.lastIndex = 0;
27293
+ while ((m = paramRe.exec(line)) !== null)
27294
+ reqNames.add(m[1]);
27295
+ }
27296
+ if (reqNames.size === 0)
27297
+ return [];
27298
+ const alt = [...reqNames].map((n) => n.replace(/\$/g, "\\$")).join("|");
27299
+ const indexRe = new RegExp(`^\\s*(?:var\\s+)?([A-Za-z_]\\w*)\\s*(?:,\\s*\\w+\\s*)?:?=\\s*(?:${alt})\\s*\\.\\s*(?:(PostForm|Form)|MultipartForm\\s*\\.\\s*Value)\\s*\\[`);
27300
+ const sources = [];
27301
+ for (let i2 = 0;i2 < lines.length; i2++) {
27302
+ const line = lines[i2];
27303
+ if (/^\s*\/\//.test(line))
27304
+ continue;
27305
+ const m = indexRe.exec(line);
27306
+ if (!m)
27307
+ continue;
27308
+ const variable = m[1];
27309
+ if (variable === "_" || variable === "")
27310
+ continue;
27311
+ const kind = m[2];
27312
+ sources.push({
27313
+ type: kind === "Form" ? "http_param" : "http_body",
27314
+ location: "HTTP request form index",
27315
+ severity: "high",
27316
+ line: i2 + 1,
27317
+ confidence: 0.9,
27318
+ variable
27319
+ });
27320
+ }
27321
+ return sources;
27322
+ }
27114
27323
  function findGoRequestBodySources(sourceCode, language) {
27115
27324
  if (language !== "go")
27116
27325
  return [];
@@ -35320,7 +35529,7 @@ class SinkFilterPass {
35320
35529
  let filtered = sinks.filter((sink) => !constProp.unreachableLines.has(sink.line));
35321
35530
  filtered = filterCleanArraySinks(filtered, calls, constProp.taintedArrayElements, constProp.symbols);
35322
35531
  filtered = filterCleanVariableSinks(filtered, calls, constProp.tainted, constProp.symbols, dfg, constProp.sanitizedVars, constProp.synchronizedLines, language);
35323
- filtered = filterSanitizedSinks(filtered, sanitizers, calls);
35532
+ filtered = filterSanitizedSinks(filtered, sanitizers, calls, sources);
35324
35533
  if (language === "python") {
35325
35534
  const { pyTaintedVars, pySanitizedVars } = langSources;
35326
35535
  const sourceLines = ctx.code.split(`
@@ -35390,8 +35599,13 @@ class SinkFilterPass {
35390
35599
  if (sink.type !== "xss")
35391
35600
  return true;
35392
35601
  const sinkLineText = sourceLines[sink.line - 1] ?? "";
35393
- if (JS_XSS_SANITIZERS.some((p) => p.test(sinkLineText)))
35394
- return false;
35602
+ if (JS_XSS_SANITIZERS.some((p) => p.test(sinkLineText))) {
35603
+ const callsAtSink = callsByLine.get(sink.line) ?? [];
35604
+ const covering = sanitizers.filter((s) => s.line === sink.line && sanitizerCoversSink(s, sink.type));
35605
+ const rawOperand = sources.some((s) => !!s.variable && identifierOutsideSanitizerCalls(s.variable, callsAtSink, covering, sink.type));
35606
+ if (!rawOperand)
35607
+ return false;
35608
+ }
35395
35609
  const assignmentMatch = sinkLineText.match(/\.(?:innerHTML|outerHTML)\s*=\s*(.+)/);
35396
35610
  if (assignmentMatch) {
35397
35611
  const rhs = assignmentMatch[1].trim().replace(/;$/, "").trim();
@@ -36477,6 +36691,22 @@ function evaluateSimpleExpression(expr, symbols) {
36477
36691
  return String(Math.floor(result));
36478
36692
  return expr;
36479
36693
  }
36694
+ var ARGUMENT_IDENT_SKIP = new Set(["new", "true", "false", "null", "this", "super"]);
36695
+ function isStringConcat(expression) {
36696
+ return expression.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, " ").includes("+");
36697
+ }
36698
+ function argumentIdentifiers(expression) {
36699
+ const stripped = expression.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, " ");
36700
+ const found = stripped.match(/[A-Za-z_$][A-Za-z0-9_$]*/g) ?? [];
36701
+ const out2 = [];
36702
+ for (const id of found) {
36703
+ if (ARGUMENT_IDENT_SKIP.has(id))
36704
+ continue;
36705
+ if (!out2.includes(id))
36706
+ out2.push(id);
36707
+ }
36708
+ return out2;
36709
+ }
36480
36710
  function isStringLiteralExpression(expr) {
36481
36711
  const trimmed = expr.trim();
36482
36712
  if (trimmed.length < 2)
@@ -36555,22 +36785,30 @@ function filterCleanVariableSinks(sinks, calls, taintedVars, symbols, dfg, sanit
36555
36785
  if (language === "bash" && arg.expression === call.method_name && !arg.variable && arg.literal == null)
36556
36786
  continue;
36557
36787
  if (arg.variable && !arg.expression?.includes("[")) {
36558
- const varName = arg.variable;
36559
- const scopedName = methodName ? `${methodName}:${varName}` : varName;
36560
- if (fieldNames.has(varName) && !isInSynchronizedBlock) {
36561
- allArgsAreClean = false;
36562
- continue;
36788
+ const expr = arg.expression ?? "";
36789
+ const concatNames = language === "java" && isStringConcat(expr) ? argumentIdentifiers(expr) : [];
36790
+ const names = concatNames.length > 0 ? concatNames : [arg.variable];
36791
+ let argClean = true;
36792
+ for (const varName of names) {
36793
+ const scopedName = methodName ? `${methodName}:${varName}` : varName;
36794
+ if (fieldNames.has(varName) && !isInSynchronizedBlock) {
36795
+ argClean = false;
36796
+ break;
36797
+ }
36798
+ if (sanitizedVars?.has(scopedName) || sanitizedVars?.has(varName))
36799
+ continue;
36800
+ if (taintedVars.has(scopedName) || taintedVars.has(varName)) {
36801
+ argClean = false;
36802
+ break;
36803
+ }
36804
+ const symbolValue = symbols.get(scopedName) ?? symbols.get(varName);
36805
+ if (symbolValue && symbolValue.type !== "unknown")
36806
+ continue;
36807
+ argClean = false;
36808
+ break;
36563
36809
  }
36564
- if (sanitizedVars?.has(scopedName) || sanitizedVars?.has(varName))
36565
- continue;
36566
- if (taintedVars.has(scopedName) || taintedVars.has(varName)) {
36810
+ if (!argClean)
36567
36811
  allArgsAreClean = false;
36568
- continue;
36569
- }
36570
- const symbolValue = symbols.get(scopedName) ?? symbols.get(varName);
36571
- if (symbolValue && symbolValue.type !== "unknown")
36572
- continue;
36573
- allArgsAreClean = false;
36574
36812
  } else {
36575
36813
  if (arg.literal != null)
36576
36814
  continue;
@@ -36585,9 +36823,14 @@ function filterCleanVariableSinks(sinks, calls, taintedVars, symbols, dfg, sanit
36585
36823
  return true;
36586
36824
  });
36587
36825
  }
36588
- function filterSanitizedSinks(sinks, sanitizers, calls) {
36826
+ function filterSanitizedSinks(sinks, sanitizers, calls, sources) {
36589
36827
  if (!sanitizers || sanitizers.length === 0)
36590
36828
  return sinks;
36829
+ const sourceVars = [];
36830
+ for (const source of sources ?? []) {
36831
+ if (source.variable && /^[A-Za-z_$][\w$]*$/.test(source.variable))
36832
+ sourceVars.push(source.variable);
36833
+ }
36591
36834
  const sanitizersByLine = new Map;
36592
36835
  for (const san of sanitizers) {
36593
36836
  const existing = sanitizersByLine.get(san.line) ?? [];
@@ -36604,23 +36847,30 @@ function filterSanitizedSinks(sinks, sanitizers, calls) {
36604
36847
  const lineSanitizers = sanitizersByLine.get(sink.line);
36605
36848
  if (!lineSanitizers || lineSanitizers.length === 0)
36606
36849
  return true;
36607
- for (const san of lineSanitizers) {
36608
- if (sanitizerCoversSink(san, sink.type)) {
36609
- const lineCalls = callsByLine.get(sink.line) ?? [];
36610
- for (const call of lineCalls) {
36611
- for (const arg of call.arguments) {
36612
- const expr = arg.expression || "";
36613
- const sanMethodMatch = san.method.match(/(?:(\w+)\.)?(\w+)\(\)/);
36614
- if (sanMethodMatch) {
36615
- const sanMethodName = sanMethodMatch[2];
36616
- const sanClassName = sanMethodMatch[1];
36617
- if (sanClassName) {
36618
- if (expr.includes(`${sanClassName}.${sanMethodName}(`))
36619
- return false;
36620
- } else if (expr.includes(`${sanMethodName}(`)) {
36621
- return false;
36622
- }
36623
- }
36850
+ const lineCalls = callsByLine.get(sink.line) ?? [];
36851
+ const covering = lineSanitizers.filter((san) => sanitizerCoversSink(san, sink.type));
36852
+ if (covering.length === 0)
36853
+ return true;
36854
+ for (const variable of sourceVars) {
36855
+ if (identifierOutsideSanitizerCalls(variable, lineCalls, covering, sink.type))
36856
+ return true;
36857
+ }
36858
+ for (const san of covering) {
36859
+ const sanMethodMatch = san.method.match(/(?:(\w+)\.)?(\w+)\(\)/);
36860
+ if (!sanMethodMatch)
36861
+ continue;
36862
+ const sanMethodName = sanMethodMatch[2];
36863
+ const sanClassName = sanMethodMatch[1];
36864
+ for (const call of lineCalls) {
36865
+ if (call.method_name === sanMethodName)
36866
+ continue;
36867
+ for (const arg of call.arguments) {
36868
+ const expr = arg.expression || "";
36869
+ if (sanClassName) {
36870
+ if (expr.includes(`${sanClassName}.${sanMethodName}(`))
36871
+ return false;
36872
+ } else if (expr.includes(`${sanMethodName}(`)) {
36873
+ return false;
36624
36874
  }
36625
36875
  }
36626
36876
  }
@@ -38066,9 +38316,14 @@ class TaintPropagationPass {
38066
38316
  const sansAtSink = sanitizersByLine.get(f.sink_line);
38067
38317
  if (sansAtSink && sansAtSink.length > 0) {
38068
38318
  for (const san of sansAtSink) {
38069
- if (sanitizerCoversSink(san, f.sink_type)) {
38070
- return false;
38319
+ if (!sanitizerCoversSink(san, f.sink_type))
38320
+ continue;
38321
+ const sinkVar2 = f.path.length > 0 ? f.path[f.path.length - 1].variable : undefined;
38322
+ const callsAtSink = graph.callsByLine.get(f.sink_line) ?? [];
38323
+ if (sinkVar2 && identifierOutsideSanitizerCalls(sinkVar2, callsAtSink, sansAtSink, f.sink_type)) {
38324
+ continue;
38071
38325
  }
38326
+ return false;
38072
38327
  }
38073
38328
  }
38074
38329
  const usesAtSink = graph.usesByLine.get(f.sink_line);
@@ -40549,10 +40804,15 @@ class InterproceduralPass {
40549
40804
  const sansAtSink = sanitizersByLine.get(f.sink_line);
40550
40805
  if (!sansAtSink || sansAtSink.length === 0)
40551
40806
  return true;
40807
+ const callsAtSink = graph.callsByLine.get(f.sink_line) ?? [];
40808
+ const sinkVar = f.path.length > 0 ? f.path[f.path.length - 1]?.variable : undefined;
40552
40809
  for (const san of sansAtSink) {
40553
- if (sanitizerCoversSink(san, f.sink_type)) {
40554
- return false;
40810
+ if (!sanitizerCoversSink(san, f.sink_type))
40811
+ continue;
40812
+ if (sinkVar && identifierOutsideSanitizerCalls(sinkVar, callsAtSink, sansAtSink, f.sink_type)) {
40813
+ continue;
40555
40814
  }
40815
+ return false;
40556
40816
  }
40557
40817
  return true;
40558
40818
  });
@@ -44988,6 +45248,13 @@ var PROVIDER_PATTERNS = [
44988
45248
  level: "error",
44989
45249
  fix: "Revoke the GitHub refresh token and store secrets outside source control."
44990
45250
  },
45251
+ {
45252
+ name: "GitHub fine-grained personal access token",
45253
+ regex: /\bgithub_pat_[A-Za-z0-9]{22}_[A-Za-z0-9]{59}\b/,
45254
+ severity: "critical",
45255
+ level: "error",
45256
+ fix: "Revoke the fine-grained personal access token at https://github.com/settings/tokens and store secrets outside source control."
45257
+ },
44991
45258
  {
44992
45259
  name: "GitLab personal access token",
44993
45260
  regex: /\bglpat-[A-Za-z0-9_-]{20,}\b/,
@@ -45012,9 +45279,9 @@ var PROVIDER_PATTERNS = [
45012
45279
  {
45013
45280
  name: "Stripe live publishable key",
45014
45281
  regex: /\bpk_live_[A-Za-z0-9]{24,}\b/,
45015
- severity: "high",
45016
- level: "warning",
45017
- fix: "Publishable keys are not secret but should still not be checked in to back-end source files; verify front-end vs back-end context."
45282
+ severity: "low",
45283
+ level: "note",
45284
+ fix: "Publishable keys are not secret. Prefer loading them from configuration rather than a backend source file."
45018
45285
  },
45019
45286
  {
45020
45287
  name: "Stripe test secret key",
@@ -45037,6 +45304,13 @@ var PROVIDER_PATTERNS = [
45037
45304
  level: "error",
45038
45305
  fix: "Revoke the OpenAI key at https://platform.openai.com/api-keys and load from environment."
45039
45306
  },
45307
+ {
45308
+ name: "OpenAI project API key",
45309
+ regex: /\bsk-proj-[A-Za-z0-9_-]{48,}\b/,
45310
+ severity: "critical",
45311
+ level: "error",
45312
+ fix: "Revoke the OpenAI project key at https://platform.openai.com/api-keys and load from environment."
45313
+ },
45040
45314
  {
45041
45315
  name: "Anthropic API key",
45042
45316
  regex: /\bsk-ant-[A-Za-z0-9_-]{90,}\b/,
@@ -45374,7 +45648,7 @@ class ScanSecretsPass {
45374
45648
  const m = pattern.regex.exec(lineText);
45375
45649
  if (!m)
45376
45650
  continue;
45377
- if ((pattern.name === "Stripe live restricted key" || pattern.name === "Stripe test secret key" || pattern.name === "Stripe test restricted key" || pattern.name === "GitLab personal access token") && PLACEHOLDER_RE.test(m[0])) {
45651
+ if ((pattern.name === "Stripe live restricted key" || pattern.name === "Stripe test secret key" || pattern.name === "Stripe test restricted key" || pattern.name === "GitLab personal access token" || pattern.name === "OpenAI project API key" || pattern.name === "GitHub fine-grained personal access token") && PLACEHOLDER_RE.test(m[0])) {
45378
45652
  continue;
45379
45653
  }
45380
45654
  if (pattern.name === "PEM private key" && !pemHasInlineBody(lines, i2)) {
@@ -45810,8 +46084,6 @@ var PY_SET_COOKIE_RECEIVERS = new Set([
45810
46084
  ]);
45811
46085
  var PY_SECURE_TRUE_RE = /\bsecure\s*=\s*True\b/;
45812
46086
  var PY_HTTPONLY_TRUE_RE = /\bhttponly\s*=\s*True\b/i;
45813
- var JAVA_SET_SECURE_TRUE_RE = /\.setSecure\s*\(\s*true\s*\)/;
45814
- var JAVA_SET_HTTPONLY_TRUE_RE = /\.setHttpOnly\s*\(\s*true\s*\)/;
45815
46087
  var GO_SECURE_TRUE_RE = /\bSecure\s*:\s*true\b/;
45816
46088
  var GO_HTTPONLY_TRUE_RE = /\bHttpOnly\s*:\s*true\b/;
45817
46089
  var RUST_SET_COOKIE_MACRO_RE = /(format!|write!|writeln!)\s*\(([^()]*Set-Cookie[^()]*)\)/gis;
@@ -45843,10 +46115,8 @@ class InsecureCookiePass {
45843
46115
  this.emit(ctx, file, det, "python");
45844
46116
  }
45845
46117
  } else if (language === "java") {
45846
- const hasSetSecureTrue = JAVA_SET_SECURE_TRUE_RE.test(code);
45847
- const hasSetHttpOnlyTrue = JAVA_SET_HTTPONLY_TRUE_RE.test(code);
45848
46118
  for (const call of graph.ir.calls) {
45849
- const det = this.detectJavaCookieCtor(call, hasSetSecureTrue, hasSetHttpOnlyTrue);
46119
+ const det = this.detectJavaCookieCtor(call, code);
45850
46120
  if (!det)
45851
46121
  continue;
45852
46122
  insecureCookies.push(det);
@@ -45932,7 +46202,15 @@ class InsecureCookiePass {
45932
46202
  optionsPresent: call.arguments.length >= 2
45933
46203
  };
45934
46204
  }
45935
- detectJavaCookieCtor(call, hasSetSecureTrue, hasSetHttpOnlyTrue) {
46205
+ javaCookieSetterTrue(code, call, setter) {
46206
+ const line = code.split(`
46207
+ `)[(call.location.line ?? 1) - 1] ?? "";
46208
+ const assign = /\b([A-Za-z_]\w*)\s*=\s*new\s+(?:[\w.]+\.)?Cookie\b/.exec(line);
46209
+ const varName = assign?.[1];
46210
+ const re = varName ? new RegExp(`\\b${varName}\\.${setter}\\s*\\(\\s*true\\s*\\)`) : new RegExp(`\\.${setter}\\s*\\(\\s*true\\s*\\)`);
46211
+ return re.test(varName ? code : line);
46212
+ }
46213
+ detectJavaCookieCtor(call, code) {
45936
46214
  const method = call.method_name ?? "";
45937
46215
  const isCookieCtor = method === "Cookie" || method.endsWith(".Cookie");
45938
46216
  if (!isCookieCtor)
@@ -45942,8 +46220,8 @@ class InsecureCookiePass {
45942
46220
  return null;
45943
46221
  if (call.arguments.length < 2)
45944
46222
  return null;
45945
- const missingSecure = !hasSetSecureTrue;
45946
- const missingHttpOnly = !hasSetHttpOnlyTrue;
46223
+ const missingSecure = !this.javaCookieSetterTrue(code, call, "setSecure");
46224
+ const missingHttpOnly = !this.javaCookieSetterTrue(code, call, "setHttpOnly");
45947
46225
  if (!missingSecure && !missingHttpOnly)
45948
46226
  return null;
45949
46227
  return {
@@ -52098,7 +52376,7 @@ var colors = {
52098
52376
  };
52099
52377
 
52100
52378
  // src/version.ts
52101
- var version = "4.14.0";
52379
+ var version = "4.14.2";
52102
52380
 
52103
52381
  // src/formatters.ts
52104
52382
  var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
@@ -52665,8 +52943,8 @@ function generateSarifResults(results, crossFileData) {
52665
52943
 
52666
52944
  // src/build-info.ts
52667
52945
  var buildInfo = {
52668
- gitSha: "c44e104",
52669
- builtAt: "2026-10-11T06:19:30.837Z"
52946
+ gitSha: "b1967eb",
52947
+ builtAt: "2026-10-13T06:43:43.278Z"
52670
52948
  };
52671
52949
 
52672
52950
  // src/utils/args.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "4.14.0",
3
+ "version": "4.14.2",
4
4
  "description": "Static Application Security Testing CLI for detecting security vulnerabilities via taint tracking",
5
5
  "bin": {
6
6
  "cognium-dev": "dist/cli.js"
@@ -65,7 +65,7 @@
65
65
  },
66
66
  "dependencies": {
67
67
  "@cognium/project-profile-detect": "1.1.2",
68
- "circle-ir": "4.14.0"
68
+ "circle-ir": "4.14.2"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^26.6.4",