cognium-dev 3.38.0 → 3.40.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 +413 -11
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -11092,13 +11092,29 @@ var PYTHON_TAINTED_PATTERNS = [
|
|
|
11092
11092
|
{ pattern: /\brequest\.query_params\b/, sourceType: "http_param" },
|
|
11093
11093
|
{ pattern: /\brequest\.path_params\b/, sourceType: "http_param" }
|
|
11094
11094
|
];
|
|
11095
|
-
function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language) {
|
|
11096
|
-
const
|
|
11097
|
-
|
|
11095
|
+
function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code) {
|
|
11096
|
+
const sourceLines = code !== undefined ? code.split(`
|
|
11097
|
+
`) : undefined;
|
|
11098
|
+
const sources = findSources(calls, types, config.sources, sourceLines);
|
|
11099
|
+
const sinks = findSinks(calls, config.sinks, typeHierarchy, language, sourceLines);
|
|
11098
11100
|
const sanitizers = findSanitizers(calls, types, config.sanitizers);
|
|
11099
11101
|
return { sources, sinks, sanitizers };
|
|
11100
11102
|
}
|
|
11101
|
-
function
|
|
11103
|
+
function attachSourceLineCode(sources, sinks, code) {
|
|
11104
|
+
const lines = code.split(`
|
|
11105
|
+
`);
|
|
11106
|
+
for (const s of sources) {
|
|
11107
|
+
if (s.code === undefined) {
|
|
11108
|
+
s.code = lines[s.line - 1]?.trim();
|
|
11109
|
+
}
|
|
11110
|
+
}
|
|
11111
|
+
for (const s of sinks) {
|
|
11112
|
+
if (s.code === undefined) {
|
|
11113
|
+
s.code = lines[s.line - 1]?.trim();
|
|
11114
|
+
}
|
|
11115
|
+
}
|
|
11116
|
+
}
|
|
11117
|
+
function findSources(calls, types, patterns, sourceLines) {
|
|
11102
11118
|
const sources = [];
|
|
11103
11119
|
for (const call of calls) {
|
|
11104
11120
|
for (const pattern of patterns) {
|
|
@@ -11245,7 +11261,13 @@ function findSources(calls, types, patterns) {
|
|
|
11245
11261
|
sourceMap.set(key, source);
|
|
11246
11262
|
}
|
|
11247
11263
|
}
|
|
11248
|
-
|
|
11264
|
+
const result = Array.from(sourceMap.values());
|
|
11265
|
+
if (sourceLines) {
|
|
11266
|
+
for (const s of result) {
|
|
11267
|
+
s.code = sourceLines[s.line - 1]?.trim();
|
|
11268
|
+
}
|
|
11269
|
+
}
|
|
11270
|
+
return result;
|
|
11249
11271
|
}
|
|
11250
11272
|
function isInterproceduralTaintableType(typeName) {
|
|
11251
11273
|
const baseType = typeName.split("<")[0].trim();
|
|
@@ -11341,7 +11363,7 @@ function isParameterizedQueryCall(call, pattern) {
|
|
|
11341
11363
|
}
|
|
11342
11364
|
return false;
|
|
11343
11365
|
}
|
|
11344
|
-
function findSinks(calls, patterns, typeHierarchy, language) {
|
|
11366
|
+
function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
11345
11367
|
const sinkMap = new Map;
|
|
11346
11368
|
for (const call of calls) {
|
|
11347
11369
|
for (const pattern of patterns) {
|
|
@@ -11367,7 +11389,13 @@ function findSinks(calls, patterns, typeHierarchy, language) {
|
|
|
11367
11389
|
}
|
|
11368
11390
|
}
|
|
11369
11391
|
}
|
|
11370
|
-
|
|
11392
|
+
const result = Array.from(sinkMap.values());
|
|
11393
|
+
if (sourceLines) {
|
|
11394
|
+
for (const s of result) {
|
|
11395
|
+
s.code = sourceLines[s.line - 1]?.trim();
|
|
11396
|
+
}
|
|
11397
|
+
}
|
|
11398
|
+
return result;
|
|
11371
11399
|
}
|
|
11372
11400
|
function matchesSourcePattern(call, pattern) {
|
|
11373
11401
|
if (pattern.method) {
|
|
@@ -18050,6 +18078,7 @@ class CrossFileResolver {
|
|
|
18050
18078
|
typeHierarchy;
|
|
18051
18079
|
fileIRs = new Map;
|
|
18052
18080
|
methodTaintInfo = new Map;
|
|
18081
|
+
fieldTaintInfo = new Map;
|
|
18053
18082
|
resolvedCalls = new Map;
|
|
18054
18083
|
constructor(symbolTable, typeHierarchy) {
|
|
18055
18084
|
this.symbolTable = symbolTable;
|
|
@@ -18060,6 +18089,7 @@ class CrossFileResolver {
|
|
|
18060
18089
|
this.symbolTable.addFromIR(ir, filePath);
|
|
18061
18090
|
this.typeHierarchy.addFromIR(ir, filePath);
|
|
18062
18091
|
this.analyzeMethodTaint(ir, filePath);
|
|
18092
|
+
this.analyzeFieldTaint(ir, filePath);
|
|
18063
18093
|
}
|
|
18064
18094
|
resolveCall(call, fromFile) {
|
|
18065
18095
|
const cacheKey = `${fromFile}:${call.location.line}:${call.method_name}`;
|
|
@@ -18226,6 +18256,113 @@ class CrossFileResolver {
|
|
|
18226
18256
|
}
|
|
18227
18257
|
}
|
|
18228
18258
|
}
|
|
18259
|
+
analyzeFieldTaint(ir, filePath) {
|
|
18260
|
+
const pkg = ir.meta.package || "";
|
|
18261
|
+
const ctorFieldRe = /^(\w+)\.(\w+)\(\) returns tainted field '([^']+)' \(from constructor param '([^']+)'\)/;
|
|
18262
|
+
for (const src of ir.taint.sources) {
|
|
18263
|
+
if (src.type !== "constructor_field")
|
|
18264
|
+
continue;
|
|
18265
|
+
const m = ctorFieldRe.exec(src.location);
|
|
18266
|
+
if (!m)
|
|
18267
|
+
continue;
|
|
18268
|
+
const [, className, , fieldName, sourceParam] = m;
|
|
18269
|
+
const typeFqn = pkg ? `${pkg}.${className}` : className;
|
|
18270
|
+
const type = ir.types.find((t) => t.name === className);
|
|
18271
|
+
if (!type)
|
|
18272
|
+
continue;
|
|
18273
|
+
const writerMethod = type.methods.find((mth) => mth.name === className && mth.parameters.some((p) => p.name === sourceParam)) ?? type.methods.find((mth) => mth.parameters.some((p) => p.name === sourceParam));
|
|
18274
|
+
if (!writerMethod)
|
|
18275
|
+
continue;
|
|
18276
|
+
const field = type.fields?.find((f) => f.name === fieldName);
|
|
18277
|
+
const key = `${typeFqn}.${fieldName}`;
|
|
18278
|
+
const existing = this.fieldTaintInfo.get(key);
|
|
18279
|
+
const writer = {
|
|
18280
|
+
methodFqn: `${typeFqn}.${writerMethod.name}`,
|
|
18281
|
+
methodName: writerMethod.name,
|
|
18282
|
+
writeLine: writerMethod.start_line,
|
|
18283
|
+
sourceType: "constructor_field",
|
|
18284
|
+
sourceLine: src.line
|
|
18285
|
+
};
|
|
18286
|
+
if (existing) {
|
|
18287
|
+
if (!existing.writers.some((w) => w.methodFqn === writer.methodFqn)) {
|
|
18288
|
+
existing.writers.push(writer);
|
|
18289
|
+
}
|
|
18290
|
+
} else {
|
|
18291
|
+
this.fieldTaintInfo.set(key, {
|
|
18292
|
+
typeFqn,
|
|
18293
|
+
fieldName,
|
|
18294
|
+
fieldType: field?.type ?? null,
|
|
18295
|
+
file: filePath,
|
|
18296
|
+
writers: [writer]
|
|
18297
|
+
});
|
|
18298
|
+
}
|
|
18299
|
+
}
|
|
18300
|
+
for (const type of ir.types) {
|
|
18301
|
+
const typeFqn = pkg ? `${pkg}.${type.name}` : type.name;
|
|
18302
|
+
for (const method of type.methods) {
|
|
18303
|
+
if (!method.name.startsWith("set") || method.name.length <= 3)
|
|
18304
|
+
continue;
|
|
18305
|
+
if (method.parameters.length !== 1)
|
|
18306
|
+
continue;
|
|
18307
|
+
const fieldName = method.name.charAt(3).toLowerCase() + method.name.substring(4);
|
|
18308
|
+
const field = type.fields?.find((f) => f.name === fieldName);
|
|
18309
|
+
if (!field)
|
|
18310
|
+
continue;
|
|
18311
|
+
const key = `${typeFqn}.${fieldName}`;
|
|
18312
|
+
const writer = {
|
|
18313
|
+
methodFqn: `${typeFqn}.${method.name}`,
|
|
18314
|
+
methodName: method.name,
|
|
18315
|
+
writeLine: method.start_line,
|
|
18316
|
+
sourceType: "setter_param",
|
|
18317
|
+
sourceLine: method.start_line
|
|
18318
|
+
};
|
|
18319
|
+
const existing = this.fieldTaintInfo.get(key);
|
|
18320
|
+
if (existing) {
|
|
18321
|
+
if (!existing.writers.some((w) => w.methodFqn === writer.methodFqn)) {
|
|
18322
|
+
existing.writers.push(writer);
|
|
18323
|
+
}
|
|
18324
|
+
} else {
|
|
18325
|
+
this.fieldTaintInfo.set(key, {
|
|
18326
|
+
typeFqn,
|
|
18327
|
+
fieldName,
|
|
18328
|
+
fieldType: field.type ?? null,
|
|
18329
|
+
file: filePath,
|
|
18330
|
+
writers: [writer]
|
|
18331
|
+
});
|
|
18332
|
+
}
|
|
18333
|
+
}
|
|
18334
|
+
}
|
|
18335
|
+
const injectAnnotations = new Set(["Autowired", "Inject", "Resource"]);
|
|
18336
|
+
for (const type of ir.types) {
|
|
18337
|
+
const typeFqn = pkg ? `${pkg}.${type.name}` : type.name;
|
|
18338
|
+
for (const field of type.fields ?? []) {
|
|
18339
|
+
if (!field.annotations?.some((a) => injectAnnotations.has(a)))
|
|
18340
|
+
continue;
|
|
18341
|
+
const key = `${typeFqn}.${field.name}`;
|
|
18342
|
+
const writer = {
|
|
18343
|
+
methodFqn: `${typeFqn}.<injected>`,
|
|
18344
|
+
methodName: "<injected>",
|
|
18345
|
+
writeLine: type.start_line,
|
|
18346
|
+
sourceType: "autowired_field",
|
|
18347
|
+
sourceLine: type.start_line
|
|
18348
|
+
};
|
|
18349
|
+
const existing = this.fieldTaintInfo.get(key);
|
|
18350
|
+
if (existing) {
|
|
18351
|
+
if (!existing.writers.some((w) => w.methodFqn === writer.methodFqn)) {
|
|
18352
|
+
existing.writers.push(writer);
|
|
18353
|
+
}
|
|
18354
|
+
} else {
|
|
18355
|
+
this.fieldTaintInfo.set(key, {
|
|
18356
|
+
typeFqn,
|
|
18357
|
+
fieldName: field.name,
|
|
18358
|
+
fieldType: field.type ?? null,
|
|
18359
|
+
file: filePath,
|
|
18360
|
+
writers: [writer]
|
|
18361
|
+
});
|
|
18362
|
+
}
|
|
18363
|
+
}
|
|
18364
|
+
}
|
|
18365
|
+
}
|
|
18229
18366
|
isMethodTaintSource(method, sources) {
|
|
18230
18367
|
const sourceAnnotations = ["RequestParam", "RequestBody", "PathVariable", "QueryParam"];
|
|
18231
18368
|
for (const param of method.parameters) {
|
|
@@ -18493,11 +18630,268 @@ class CrossFileResolver {
|
|
|
18493
18630
|
}
|
|
18494
18631
|
}
|
|
18495
18632
|
}
|
|
18633
|
+
if (tainted.size > 0) {
|
|
18634
|
+
const sinksInCaller = callerIR.taint.sinks.filter((s) => s.line >= method.start_line && s.line <= method.end_line);
|
|
18635
|
+
for (const sink of sinksInCaller) {
|
|
18636
|
+
const callsAtSink = callerIR.calls.filter((c) => c.location.line === sink.line);
|
|
18637
|
+
for (const sinkCall of callsAtSink) {
|
|
18638
|
+
for (const arg of sinkCall.arguments ?? []) {
|
|
18639
|
+
const matched = this.matchTaintedArg(arg, tainted);
|
|
18640
|
+
if (!matched)
|
|
18641
|
+
continue;
|
|
18642
|
+
const key = `${matched.origin.file}:${matched.origin.line}→${callerFile}:${sink.line}`;
|
|
18643
|
+
if (seen.has(key))
|
|
18644
|
+
continue;
|
|
18645
|
+
seen.add(key);
|
|
18646
|
+
const hops = [
|
|
18647
|
+
...matched.origin.hopChain,
|
|
18648
|
+
{ file: callerFile, line: sink.line, method: method.name, kind: "sink" }
|
|
18649
|
+
];
|
|
18650
|
+
const decay = Math.max(0.3, Math.pow(0.85, Math.max(hops.length - 1, 0)));
|
|
18651
|
+
paths.push({
|
|
18652
|
+
source: {
|
|
18653
|
+
file: matched.origin.file,
|
|
18654
|
+
line: matched.origin.line,
|
|
18655
|
+
type: matched.origin.type
|
|
18656
|
+
},
|
|
18657
|
+
sink: {
|
|
18658
|
+
file: callerFile,
|
|
18659
|
+
line: sink.line,
|
|
18660
|
+
type: sink.type,
|
|
18661
|
+
cwe: sink.cwe
|
|
18662
|
+
},
|
|
18663
|
+
hops,
|
|
18664
|
+
confidence: decay
|
|
18665
|
+
});
|
|
18666
|
+
}
|
|
18667
|
+
}
|
|
18668
|
+
}
|
|
18669
|
+
}
|
|
18670
|
+
}
|
|
18671
|
+
}
|
|
18672
|
+
}
|
|
18673
|
+
return paths;
|
|
18674
|
+
}
|
|
18675
|
+
findFieldBindingTaintPaths() {
|
|
18676
|
+
const paths = [];
|
|
18677
|
+
const seen = new Set;
|
|
18678
|
+
if (this.fieldTaintInfo.size === 0)
|
|
18679
|
+
return paths;
|
|
18680
|
+
const fieldExprRe = /^(\w+)\.(\w+)$/;
|
|
18681
|
+
const methodIndex = this.buildMethodIndex();
|
|
18682
|
+
for (const [callerFile, callerIR] of this.fileIRs) {
|
|
18683
|
+
for (const type of callerIR.types) {
|
|
18684
|
+
const callerTypeFqn = callerIR.meta.package ? `${callerIR.meta.package}.${type.name}` : type.name;
|
|
18685
|
+
for (const method of type.methods) {
|
|
18686
|
+
const tainted = new Map;
|
|
18687
|
+
for (const src of callerIR.taint.sources) {
|
|
18688
|
+
if (src.type === "interprocedural_param")
|
|
18689
|
+
continue;
|
|
18690
|
+
if (src.line < method.start_line || src.line > method.end_line)
|
|
18691
|
+
continue;
|
|
18692
|
+
if (!src.variable)
|
|
18693
|
+
continue;
|
|
18694
|
+
tainted.set(src.variable, {
|
|
18695
|
+
file: callerFile,
|
|
18696
|
+
line: src.line,
|
|
18697
|
+
type: src.type,
|
|
18698
|
+
hopChain: [{ file: callerFile, line: src.line, method: method.name, kind: "source" }]
|
|
18699
|
+
});
|
|
18700
|
+
}
|
|
18701
|
+
const defsInMethod = callerIR.dfg.defs.filter((d) => d.kind === "local" && d.line >= method.start_line && d.line <= method.end_line && !!d.variable);
|
|
18702
|
+
for (const def of defsInMethod) {
|
|
18703
|
+
const usesAtLine = callerIR.dfg.uses.filter((u) => u.line === def.line);
|
|
18704
|
+
if (usesAtLine.length < 2)
|
|
18705
|
+
continue;
|
|
18706
|
+
let receiver = null;
|
|
18707
|
+
let fieldName = null;
|
|
18708
|
+
if (def.expression) {
|
|
18709
|
+
const exprMatch = fieldExprRe.exec(def.expression.trim());
|
|
18710
|
+
if (exprMatch) {
|
|
18711
|
+
receiver = exprMatch[1];
|
|
18712
|
+
fieldName = exprMatch[2];
|
|
18713
|
+
}
|
|
18714
|
+
}
|
|
18715
|
+
const resolveReceiverType = (rcv) => {
|
|
18716
|
+
const param = method.parameters.find((p) => p.name === rcv);
|
|
18717
|
+
if (param?.type)
|
|
18718
|
+
return param.type;
|
|
18719
|
+
const fieldOnSelf = type.fields?.find((f) => f.name === rcv);
|
|
18720
|
+
if (fieldOnSelf?.type)
|
|
18721
|
+
return fieldOnSelf.type;
|
|
18722
|
+
return null;
|
|
18723
|
+
};
|
|
18724
|
+
let receiverType = null;
|
|
18725
|
+
if (receiver && fieldName) {
|
|
18726
|
+
receiverType = resolveReceiverType(receiver);
|
|
18727
|
+
}
|
|
18728
|
+
if (!receiverType) {
|
|
18729
|
+
for (const rcvUse of usesAtLine) {
|
|
18730
|
+
if (!rcvUse.variable || rcvUse.variable === def.variable)
|
|
18731
|
+
continue;
|
|
18732
|
+
const rt = resolveReceiverType(rcvUse.variable);
|
|
18733
|
+
if (!rt)
|
|
18734
|
+
continue;
|
|
18735
|
+
const fieldUse = usesAtLine.find((u) => u !== rcvUse && !!u.variable && u.variable !== def.variable && u.variable !== rcvUse.variable && this.typeHasField(rt, u.variable));
|
|
18736
|
+
if (fieldUse) {
|
|
18737
|
+
receiver = rcvUse.variable;
|
|
18738
|
+
fieldName = fieldUse.variable;
|
|
18739
|
+
receiverType = rt;
|
|
18740
|
+
break;
|
|
18741
|
+
}
|
|
18742
|
+
}
|
|
18743
|
+
}
|
|
18744
|
+
if (!receiver || !fieldName || !receiverType)
|
|
18745
|
+
continue;
|
|
18746
|
+
const fieldKey = this.resolveFieldTaintKey(receiverType, fieldName, callerIR);
|
|
18747
|
+
if (!fieldKey)
|
|
18748
|
+
continue;
|
|
18749
|
+
const fieldInfo = this.fieldTaintInfo.get(fieldKey);
|
|
18750
|
+
if (!fieldInfo || fieldInfo.writers.length === 0)
|
|
18751
|
+
continue;
|
|
18752
|
+
const writer = fieldInfo.writers.find((w) => w.sourceType === "constructor_field" || w.sourceType === "autowired_field") ?? null;
|
|
18753
|
+
if (!writer)
|
|
18754
|
+
continue;
|
|
18755
|
+
const hopChain = [
|
|
18756
|
+
{
|
|
18757
|
+
file: fieldInfo.file,
|
|
18758
|
+
line: writer.sourceLine,
|
|
18759
|
+
method: writer.methodName,
|
|
18760
|
+
kind: "source"
|
|
18761
|
+
},
|
|
18762
|
+
{
|
|
18763
|
+
file: fieldInfo.file,
|
|
18764
|
+
line: writer.writeLine,
|
|
18765
|
+
method: writer.methodName,
|
|
18766
|
+
kind: "field_write"
|
|
18767
|
+
},
|
|
18768
|
+
{
|
|
18769
|
+
file: callerFile,
|
|
18770
|
+
line: def.line,
|
|
18771
|
+
method: method.name,
|
|
18772
|
+
kind: "field_read"
|
|
18773
|
+
}
|
|
18774
|
+
];
|
|
18775
|
+
tainted.set(def.variable, {
|
|
18776
|
+
file: fieldInfo.file,
|
|
18777
|
+
line: writer.sourceLine,
|
|
18778
|
+
type: writer.sourceType,
|
|
18779
|
+
hopChain
|
|
18780
|
+
});
|
|
18781
|
+
}
|
|
18782
|
+
if (tainted.size === 0)
|
|
18783
|
+
continue;
|
|
18784
|
+
const sinksInCaller = callerIR.taint.sinks.filter((s) => s.line >= method.start_line && s.line <= method.end_line);
|
|
18785
|
+
for (const sink of sinksInCaller) {
|
|
18786
|
+
const callsAtSink = callerIR.calls.filter((c) => c.location.line === sink.line);
|
|
18787
|
+
for (const sinkCall of callsAtSink) {
|
|
18788
|
+
for (const arg of sinkCall.arguments ?? []) {
|
|
18789
|
+
const matched = this.matchTaintedArg(arg, tainted);
|
|
18790
|
+
if (!matched)
|
|
18791
|
+
continue;
|
|
18792
|
+
const key = `fb:${matched.origin.file}:${matched.origin.line}→${callerFile}:${sink.line}`;
|
|
18793
|
+
if (seen.has(key))
|
|
18794
|
+
continue;
|
|
18795
|
+
seen.add(key);
|
|
18796
|
+
const hops = [
|
|
18797
|
+
...matched.origin.hopChain,
|
|
18798
|
+
{ file: callerFile, line: sink.line, method: method.name, kind: "sink" }
|
|
18799
|
+
];
|
|
18800
|
+
const decay = Math.max(0.3, Math.pow(0.85, Math.max(hops.length - 1, 0)));
|
|
18801
|
+
paths.push({
|
|
18802
|
+
source: {
|
|
18803
|
+
file: matched.origin.file,
|
|
18804
|
+
line: matched.origin.line,
|
|
18805
|
+
type: matched.origin.type
|
|
18806
|
+
},
|
|
18807
|
+
sink: {
|
|
18808
|
+
file: callerFile,
|
|
18809
|
+
line: sink.line,
|
|
18810
|
+
type: sink.type,
|
|
18811
|
+
cwe: sink.cwe
|
|
18812
|
+
},
|
|
18813
|
+
hops,
|
|
18814
|
+
confidence: decay
|
|
18815
|
+
});
|
|
18816
|
+
}
|
|
18817
|
+
}
|
|
18818
|
+
}
|
|
18819
|
+
const callsInMethod = callerIR.calls.filter((c) => c.location.line >= method.start_line && c.location.line <= method.end_line).sort((a, b) => a.location.line - b.location.line);
|
|
18820
|
+
for (const call of callsInMethod) {
|
|
18821
|
+
const resolved = this.resolveCall(call, callerFile);
|
|
18822
|
+
if (!resolved)
|
|
18823
|
+
continue;
|
|
18824
|
+
const callee = this.methodTaintInfo.get(resolved.targetMethod);
|
|
18825
|
+
if (!callee || callee.sanitizes || callee.taintedParams.length === 0)
|
|
18826
|
+
continue;
|
|
18827
|
+
for (let argIdx = 0;argIdx < call.arguments.length; argIdx++) {
|
|
18828
|
+
if (!callee.taintedParams.includes(argIdx))
|
|
18829
|
+
continue;
|
|
18830
|
+
const matched = this.matchTaintedArg(call.arguments[argIdx], tainted);
|
|
18831
|
+
if (!matched)
|
|
18832
|
+
continue;
|
|
18833
|
+
const calleeNode = methodIndex.get(resolved.targetMethod);
|
|
18834
|
+
if (!calleeNode)
|
|
18835
|
+
continue;
|
|
18836
|
+
const sinksInCallee = calleeNode.ir.taint.sinks.filter((s) => s.line >= calleeNode.method.start_line && s.line <= calleeNode.method.end_line);
|
|
18837
|
+
for (const sink of sinksInCallee) {
|
|
18838
|
+
const key = `fb:${matched.origin.file}:${matched.origin.line}→${callee.file}:${sink.line}`;
|
|
18839
|
+
if (seen.has(key))
|
|
18840
|
+
continue;
|
|
18841
|
+
seen.add(key);
|
|
18842
|
+
const hops = [
|
|
18843
|
+
...matched.origin.hopChain,
|
|
18844
|
+
{ file: callerFile, line: call.location.line, method: method.name, kind: "sink_call" },
|
|
18845
|
+
{ file: callee.file, line: sink.line, method: resolved.targetMethod, kind: "sink" }
|
|
18846
|
+
];
|
|
18847
|
+
const decay = Math.max(0.3, Math.pow(0.85, Math.max(hops.length - 1, 0)));
|
|
18848
|
+
paths.push({
|
|
18849
|
+
source: {
|
|
18850
|
+
file: matched.origin.file,
|
|
18851
|
+
line: matched.origin.line,
|
|
18852
|
+
type: matched.origin.type
|
|
18853
|
+
},
|
|
18854
|
+
sink: {
|
|
18855
|
+
file: callee.file,
|
|
18856
|
+
line: sink.line,
|
|
18857
|
+
type: sink.type,
|
|
18858
|
+
cwe: sink.cwe
|
|
18859
|
+
},
|
|
18860
|
+
hops,
|
|
18861
|
+
confidence: decay
|
|
18862
|
+
});
|
|
18863
|
+
}
|
|
18864
|
+
}
|
|
18865
|
+
}
|
|
18496
18866
|
}
|
|
18497
18867
|
}
|
|
18498
18868
|
}
|
|
18499
18869
|
return paths;
|
|
18500
18870
|
}
|
|
18871
|
+
typeHasField(typeName, fieldName) {
|
|
18872
|
+
for (const [, ir] of this.fileIRs) {
|
|
18873
|
+
for (const t of ir.types) {
|
|
18874
|
+
if (t.name !== typeName)
|
|
18875
|
+
continue;
|
|
18876
|
+
if ((t.fields ?? []).some((f) => f.name === fieldName))
|
|
18877
|
+
return true;
|
|
18878
|
+
}
|
|
18879
|
+
}
|
|
18880
|
+
return false;
|
|
18881
|
+
}
|
|
18882
|
+
resolveFieldTaintKey(receiverType, fieldName, _callerIR) {
|
|
18883
|
+
const direct = `${receiverType}.${fieldName}`;
|
|
18884
|
+
if (this.fieldTaintInfo.has(direct))
|
|
18885
|
+
return direct;
|
|
18886
|
+
const suffix = `.${receiverType}.${fieldName}`;
|
|
18887
|
+
for (const key of this.fieldTaintInfo.keys()) {
|
|
18888
|
+
if (key === direct)
|
|
18889
|
+
return key;
|
|
18890
|
+
if (key.endsWith(suffix))
|
|
18891
|
+
return key;
|
|
18892
|
+
}
|
|
18893
|
+
return;
|
|
18894
|
+
}
|
|
18501
18895
|
matchTaintedArg(arg, tainted) {
|
|
18502
18896
|
if (tainted.size === 0)
|
|
18503
18897
|
return null;
|
|
@@ -18582,8 +18976,12 @@ class CrossFileResolver {
|
|
|
18582
18976
|
clear() {
|
|
18583
18977
|
this.fileIRs.clear();
|
|
18584
18978
|
this.methodTaintInfo.clear();
|
|
18979
|
+
this.fieldTaintInfo.clear();
|
|
18585
18980
|
this.resolvedCalls.clear();
|
|
18586
18981
|
}
|
|
18982
|
+
getFieldTaintInfo(typeFqn, fieldName) {
|
|
18983
|
+
return this.fieldTaintInfo.get(`${typeFqn}.${fieldName}`);
|
|
18984
|
+
}
|
|
18587
18985
|
}
|
|
18588
18986
|
// ../circle-ir/dist/graph/project-graph.js
|
|
18589
18987
|
class ProjectGraph {
|
|
@@ -18725,7 +19123,10 @@ class CrossFilePass {
|
|
|
18725
19123
|
confidence: 0.7
|
|
18726
19124
|
}];
|
|
18727
19125
|
});
|
|
18728
|
-
const ipPaths =
|
|
19126
|
+
const ipPaths = [
|
|
19127
|
+
...resolver.findInterproceduralTaintPaths(),
|
|
19128
|
+
...resolver.findFieldBindingTaintPaths()
|
|
19129
|
+
];
|
|
18729
19130
|
for (let i2 = 0;i2 < ipPaths.length; i2++) {
|
|
18730
19131
|
const p = ipPaths[i2];
|
|
18731
19132
|
const sinkIR = projectGraph.getIR(p.sink.file);
|
|
@@ -19321,7 +19722,7 @@ class TaintMatcherPass {
|
|
|
19321
19722
|
name = "taint-matcher";
|
|
19322
19723
|
category = "security";
|
|
19323
19724
|
run(ctx) {
|
|
19324
|
-
const { graph, language, config } = ctx;
|
|
19725
|
+
const { graph, language, config, code } = ctx;
|
|
19325
19726
|
const { calls, types } = graph.ir;
|
|
19326
19727
|
let mergedConfig = config;
|
|
19327
19728
|
const plugin = getLanguagePlugin(language);
|
|
@@ -19358,7 +19759,7 @@ class TaintMatcherPass {
|
|
|
19358
19759
|
}
|
|
19359
19760
|
const hierarchy = createWithJdkTypes();
|
|
19360
19761
|
hierarchy.addFromIR(graph.ir, graph.ir.meta.file);
|
|
19361
|
-
const taint = analyzeTaint(calls, types, mergedConfig, hierarchy, language);
|
|
19762
|
+
const taint = analyzeTaint(calls, types, mergedConfig, hierarchy, language, code);
|
|
19362
19763
|
const sanitizerMethods = [];
|
|
19363
19764
|
for (const type of types) {
|
|
19364
19765
|
for (const method of type.methods) {
|
|
@@ -19540,6 +19941,7 @@ class LanguageSourcesPass {
|
|
|
19540
19941
|
ctx.addFinding(finding);
|
|
19541
19942
|
}
|
|
19542
19943
|
}
|
|
19944
|
+
attachSourceLineCode(additionalSources, additionalSinks, code);
|
|
19543
19945
|
return { additionalSources, additionalSinks, pyTaintedVars, pySanitizedVars, jsTaintedVars };
|
|
19544
19946
|
}
|
|
19545
19947
|
}
|
|
@@ -27217,7 +27619,7 @@ var colors = {
|
|
|
27217
27619
|
};
|
|
27218
27620
|
|
|
27219
27621
|
// src/version.ts
|
|
27220
|
-
var version = "3.
|
|
27622
|
+
var version = "3.40.0";
|
|
27221
27623
|
|
|
27222
27624
|
// src/formatters.ts
|
|
27223
27625
|
var SINK_SEVERITY = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.40.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.
|
|
68
|
+
"circle-ir": "^3.40.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/node": "^25.5.0",
|