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.
- package/dist/cli.js +346 -68
- 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
|
|
9694
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
36559
|
-
const
|
|
36560
|
-
|
|
36561
|
-
|
|
36562
|
-
|
|
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 (
|
|
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
|
-
|
|
36608
|
-
|
|
36609
|
-
|
|
36610
|
-
|
|
36611
|
-
|
|
36612
|
-
|
|
36613
|
-
|
|
36614
|
-
|
|
36615
|
-
|
|
36616
|
-
|
|
36617
|
-
|
|
36618
|
-
|
|
36619
|
-
|
|
36620
|
-
|
|
36621
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: "
|
|
45016
|
-
level: "
|
|
45017
|
-
fix: "Publishable keys are not secret
|
|
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,
|
|
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
|
-
|
|
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 = !
|
|
45946
|
-
const missingHttpOnly = !
|
|
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.
|
|
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: "
|
|
52669
|
-
builtAt: "2026-10-
|
|
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.
|
|
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.
|
|
68
|
+
"circle-ir": "4.14.2"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/node": "^26.6.4",
|