cognium-dev 3.191.0 → 3.193.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +111 -12
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -15947,22 +15947,36 @@ class CrossFileResolver {
|
|
|
15947
15947
|
if (source.type === "interprocedural_param")
|
|
15948
15948
|
continue;
|
|
15949
15949
|
const sourceVar = source.variable ?? this.getLocalDefVarAt(ir, source.line);
|
|
15950
|
+
const reachable = sourceVar ? this.collectTaintReachable(ir, sourceVar, source.line, filePath) : undefined;
|
|
15950
15951
|
for (const call of ir.calls) {
|
|
15951
15952
|
if (call.location.line < source.line)
|
|
15952
15953
|
continue;
|
|
15953
15954
|
const resolved = this.resolveCall(call, filePath);
|
|
15954
15955
|
if (!resolved || resolved.targetFile === filePath)
|
|
15955
15956
|
continue;
|
|
15956
|
-
if (
|
|
15957
|
-
|
|
15958
|
-
|
|
15959
|
-
|
|
15960
|
-
|
|
15961
|
-
|
|
15962
|
-
|
|
15963
|
-
|
|
15964
|
-
|
|
15957
|
+
if (reachable) {
|
|
15958
|
+
let matchedName;
|
|
15959
|
+
for (const arg of call.arguments) {
|
|
15960
|
+
if (arg.variable && reachable.names.has(arg.variable)) {
|
|
15961
|
+
matchedName = arg.variable;
|
|
15962
|
+
break;
|
|
15963
|
+
}
|
|
15964
|
+
if (arg.expression) {
|
|
15965
|
+
for (const v of reachable.names) {
|
|
15966
|
+
if (new RegExp(`\\b${v}\\b`).test(arg.expression)) {
|
|
15967
|
+
matchedName = v;
|
|
15968
|
+
break;
|
|
15969
|
+
}
|
|
15970
|
+
}
|
|
15971
|
+
if (matchedName)
|
|
15972
|
+
break;
|
|
15973
|
+
}
|
|
15974
|
+
}
|
|
15975
|
+
if (!matchedName)
|
|
15976
|
+
continue;
|
|
15977
|
+
if (this.taintClobbered(ir, call.location.line, matchedName, reachable.defIds)) {
|
|
15965
15978
|
continue;
|
|
15979
|
+
}
|
|
15966
15980
|
}
|
|
15967
15981
|
const targetIR = this.fileIRs.get(resolved.targetFile);
|
|
15968
15982
|
if (!targetIR || targetIR.taint.sinks.length === 0)
|
|
@@ -16023,7 +16037,8 @@ class CrossFileResolver {
|
|
|
16023
16037
|
file: callerFile,
|
|
16024
16038
|
line: src.line,
|
|
16025
16039
|
type: src.type,
|
|
16026
|
-
hopChain: [{ file: callerFile, line: src.line, method: method.name, kind: "source" }]
|
|
16040
|
+
hopChain: [{ file: callerFile, line: src.line, method: method.name, kind: "source" }],
|
|
16041
|
+
defIds: this.collectTaintReachable(callerIR, src.variable, src.line, callerFile).defIds
|
|
16027
16042
|
});
|
|
16028
16043
|
}
|
|
16029
16044
|
const callsInMethod = callerIdx.callsByMethod.get(method) ?? [];
|
|
@@ -16052,7 +16067,8 @@ class CrossFileResolver {
|
|
|
16052
16067
|
file: sourceFile,
|
|
16053
16068
|
line: sourceLine,
|
|
16054
16069
|
type: sourceType,
|
|
16055
|
-
hopChain: baseChain
|
|
16070
|
+
hopChain: baseChain,
|
|
16071
|
+
defIds: this.forwardReachableDefs(callerIR, [def.id], callerFile)
|
|
16056
16072
|
});
|
|
16057
16073
|
}
|
|
16058
16074
|
}
|
|
@@ -16065,6 +16081,9 @@ class CrossFileResolver {
|
|
|
16065
16081
|
const matched = this.matchTaintedArg(arg, tainted);
|
|
16066
16082
|
if (!matched)
|
|
16067
16083
|
continue;
|
|
16084
|
+
if (this.taintClobbered(callerIR, call.location.line, matched.var, matched.origin.defIds)) {
|
|
16085
|
+
continue;
|
|
16086
|
+
}
|
|
16068
16087
|
const calleeNode = methodIndex.get(resolved.targetMethod);
|
|
16069
16088
|
if (!calleeNode)
|
|
16070
16089
|
continue;
|
|
@@ -16398,6 +16417,62 @@ class CrossFileResolver {
|
|
|
16398
16417
|
}
|
|
16399
16418
|
return;
|
|
16400
16419
|
}
|
|
16420
|
+
collectTaintReachable(ir, sourceVar, sourceLine, filePath) {
|
|
16421
|
+
const defs = ir.dfg.defs;
|
|
16422
|
+
let seedDefs = defs.filter((d) => d.variable === sourceVar && d.line === sourceLine);
|
|
16423
|
+
if (seedDefs.length === 0) {
|
|
16424
|
+
const named = defs.filter((d) => d.variable === sourceVar).sort((a, b) => a.line - b.line);
|
|
16425
|
+
if (named.length > 0)
|
|
16426
|
+
seedDefs = [named[0]];
|
|
16427
|
+
}
|
|
16428
|
+
const defIds = this.forwardReachableDefs(ir, seedDefs.map((d) => d.id), filePath);
|
|
16429
|
+
const names = new Set([sourceVar]);
|
|
16430
|
+
for (const d of defs) {
|
|
16431
|
+
if (d.variable && defIds.has(d.id))
|
|
16432
|
+
names.add(d.variable);
|
|
16433
|
+
}
|
|
16434
|
+
return { names, defIds };
|
|
16435
|
+
}
|
|
16436
|
+
forwardReachableDefs(ir, seedDefIds, filePath) {
|
|
16437
|
+
const defIds = new Set(seedDefIds);
|
|
16438
|
+
const chains = ir.dfg.chains;
|
|
16439
|
+
if (!chains || chains.length === 0 || seedDefIds.length === 0)
|
|
16440
|
+
return defIds;
|
|
16441
|
+
const lineByDefId = new Map;
|
|
16442
|
+
for (const d of ir.dfg.defs)
|
|
16443
|
+
lineByDefId.set(d.id, d.line);
|
|
16444
|
+
const crossFileCallLines = new Set;
|
|
16445
|
+
for (const call of ir.calls) {
|
|
16446
|
+
const resolved = this.resolveCall(call, filePath);
|
|
16447
|
+
if (resolved && resolved.targetFile !== filePath) {
|
|
16448
|
+
crossFileCallLines.add(call.location.line);
|
|
16449
|
+
}
|
|
16450
|
+
}
|
|
16451
|
+
const frontier = [...seedDefIds];
|
|
16452
|
+
const visited = new Set(frontier);
|
|
16453
|
+
while (frontier.length > 0) {
|
|
16454
|
+
const cur = frontier.shift();
|
|
16455
|
+
for (const ch of chains) {
|
|
16456
|
+
if (ch.from_def !== cur || visited.has(ch.to_def))
|
|
16457
|
+
continue;
|
|
16458
|
+
visited.add(ch.to_def);
|
|
16459
|
+
const toLine = lineByDefId.get(ch.to_def);
|
|
16460
|
+
if (toLine !== undefined && crossFileCallLines.has(toLine))
|
|
16461
|
+
continue;
|
|
16462
|
+
defIds.add(ch.to_def);
|
|
16463
|
+
frontier.push(ch.to_def);
|
|
16464
|
+
}
|
|
16465
|
+
}
|
|
16466
|
+
return defIds;
|
|
16467
|
+
}
|
|
16468
|
+
taintClobbered(ir, callLine, varName, taintDefIds) {
|
|
16469
|
+
if (!taintDefIds || taintDefIds.size === 0)
|
|
16470
|
+
return false;
|
|
16471
|
+
const use = ir.dfg.uses.find((u) => u.line === callLine && u.variable === varName && u.def_id !== null);
|
|
16472
|
+
if (!use || use.def_id === null)
|
|
16473
|
+
return false;
|
|
16474
|
+
return !taintDefIds.has(use.def_id);
|
|
16475
|
+
}
|
|
16401
16476
|
findRealSourceLineInMethod(ir, method) {
|
|
16402
16477
|
for (const src of ir.taint.sources) {
|
|
16403
16478
|
if (src.type === "interprocedural_param")
|
|
@@ -24977,6 +25052,7 @@ class LanguageSourcesPass {
|
|
|
24977
25052
|
}
|
|
24978
25053
|
additionalSanitizers.push(...findBashRegexAllowlistSanitizers(code));
|
|
24979
25054
|
additionalSanitizers.push(...findBashRealpathPrefixGuardSanitizers(code));
|
|
25055
|
+
additionalSanitizers.push(...findBashShellQuoteSanitizers(code));
|
|
24980
25056
|
}
|
|
24981
25057
|
if (language === "go") {
|
|
24982
25058
|
additionalSanitizers.push(...findGoMapAllowlistGuardSanitizers(code));
|
|
@@ -26426,6 +26502,29 @@ function hasIntegrityVerifierAnywhere(lines) {
|
|
|
26426
26502
|
}
|
|
26427
26503
|
return false;
|
|
26428
26504
|
}
|
|
26505
|
+
function findBashShellQuoteSanitizers(code) {
|
|
26506
|
+
const sanitizers = [];
|
|
26507
|
+
const lines = code.split(`
|
|
26508
|
+
`);
|
|
26509
|
+
const PRINTF_Q_RE = /\$\(\s*printf\s+['"]%q['"]\s+["']?\$/;
|
|
26510
|
+
const AT_Q_RE = /\$\{[A-Za-z_][A-Za-z0-9_]*@Q\}/;
|
|
26511
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
26512
|
+
const line = lines[i2];
|
|
26513
|
+
if (line.trimStart().startsWith("#"))
|
|
26514
|
+
continue;
|
|
26515
|
+
if (!PRINTF_Q_RE.test(line) && !AT_Q_RE.test(line))
|
|
26516
|
+
continue;
|
|
26517
|
+
for (let l = i2 + 1;l <= lines.length; l++) {
|
|
26518
|
+
sanitizers.push({
|
|
26519
|
+
type: "shell_quote",
|
|
26520
|
+
method: PRINTF_Q_RE.test(line) ? "printf '%q'" : "${var@Q}",
|
|
26521
|
+
line: l,
|
|
26522
|
+
sanitizes: ["command_injection", "code_injection"]
|
|
26523
|
+
});
|
|
26524
|
+
}
|
|
26525
|
+
}
|
|
26526
|
+
return sanitizers;
|
|
26527
|
+
}
|
|
26429
26528
|
function findBashRegexAllowlistSanitizers(code) {
|
|
26430
26529
|
const sanitizers = [];
|
|
26431
26530
|
const lines = code.split(`
|
|
@@ -45980,7 +46079,7 @@ var colors = {
|
|
|
45980
46079
|
};
|
|
45981
46080
|
|
|
45982
46081
|
// src/version.ts
|
|
45983
|
-
var version = "3.
|
|
46082
|
+
var version = "3.193.0";
|
|
45984
46083
|
|
|
45985
46084
|
// src/formatters.ts
|
|
45986
46085
|
var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.193.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",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@cognium/project-profile-detect": "^1.1.0",
|
|
69
|
-
"circle-ir": "^3.
|
|
69
|
+
"circle-ir": "^3.193.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/node": "^25.5.0",
|