cognium-dev 3.158.0 → 3.160.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 +126 -8
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -12982,7 +12982,10 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
|
|
|
12982
12982
|
return true;
|
|
12983
12983
|
}
|
|
12984
12984
|
const subtypeArgArityOk = !pattern.arg_positions || pattern.arg_positions.length === 0 || pattern.arg_positions.every((pos) => pos < (call.arguments?.length ?? 0));
|
|
12985
|
-
if (call.receiver_type && call.receiver_type === pattern.class) {} else if (call.receiver_type && typeHierarchy && subtypeArgArityOk && typeHierarchy.isSubtypeOf(call.receiver_type, pattern.class)) {} else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {} else if (call.receiver_type_fqn && typeHierarchy && subtypeArgArityOk && typeHierarchy.isSubtypeOf(call.receiver_type_fqn, pattern.class)) {} else if (call.
|
|
12985
|
+
if (call.receiver_type && call.receiver_type === pattern.class) {} else if (call.receiver_type && typeHierarchy && subtypeArgArityOk && typeHierarchy.isSubtypeOf(call.receiver_type, pattern.class)) {} else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {} else if (call.receiver_type_fqn && typeHierarchy && subtypeArgArityOk && typeHierarchy.isSubtypeOf(call.receiver_type_fqn, pattern.class)) {} else if (!call.receiver_type && !call.receiver_type_fqn && call.receiver && typeHierarchy && subtypeArgArityOk && (() => {
|
|
12986
|
+
const factoryReturn = typeHierarchy.resolveFactoryReturnType(call.receiver);
|
|
12987
|
+
return factoryReturn !== null && typeHierarchy.isSubtypeOf(factoryReturn, pattern.class);
|
|
12988
|
+
})()) {} else if (call.receiver && !receiverMightBeClass(call.receiver, pattern.class)) {
|
|
12986
12989
|
if (typeHierarchy && typeHierarchy.couldBeType(call.receiver, pattern.class)) {
|
|
12987
12990
|
return true;
|
|
12988
12991
|
}
|
|
@@ -13915,6 +13918,7 @@ class TypeHierarchyResolver {
|
|
|
13915
13918
|
implementations = new Map;
|
|
13916
13919
|
_subtypeCache = new Map;
|
|
13917
13920
|
_implCache = new Map;
|
|
13921
|
+
factoryReturnTypes = new Map;
|
|
13918
13922
|
addFromIR(ir, filePath) {
|
|
13919
13923
|
for (const type of ir.types) {
|
|
13920
13924
|
this.addType(type, filePath, ir.meta.package || null);
|
|
@@ -14120,11 +14124,22 @@ class TypeHierarchyResolver {
|
|
|
14120
14124
|
getAllTypes() {
|
|
14121
14125
|
return Array.from(this.types.values());
|
|
14122
14126
|
}
|
|
14127
|
+
registerFactoryReturnType(factoryClass, method, returnFqn) {
|
|
14128
|
+
this.factoryReturnTypes.set(`${factoryClass}.${method}`, returnFqn);
|
|
14129
|
+
}
|
|
14130
|
+
resolveFactoryReturnType(receiver) {
|
|
14131
|
+
const m = receiver.match(/(?:^|\.)([A-Z]\w*)\.(\w+)\(\)$/);
|
|
14132
|
+
if (!m)
|
|
14133
|
+
return null;
|
|
14134
|
+
const key = `${m[1]}.${m[2]}`;
|
|
14135
|
+
return this.factoryReturnTypes.get(key) ?? null;
|
|
14136
|
+
}
|
|
14123
14137
|
clear() {
|
|
14124
14138
|
this.types.clear();
|
|
14125
14139
|
this.nameToFqn.clear();
|
|
14126
14140
|
this.subtypes.clear();
|
|
14127
14141
|
this.implementations.clear();
|
|
14142
|
+
this.factoryReturnTypes.clear();
|
|
14128
14143
|
}
|
|
14129
14144
|
resolveTypeName(name2, currentPackage) {
|
|
14130
14145
|
if (name2.includes("."))
|
|
@@ -14463,6 +14478,9 @@ function registerCommonLibraries(resolver) {
|
|
|
14463
14478
|
for (const type of [...apacheHttpClient4x, ...apacheHttpClient5x]) {
|
|
14464
14479
|
resolver.addType(type, "common-libraries", type.package);
|
|
14465
14480
|
}
|
|
14481
|
+
resolver.registerFactoryReturnType("HttpClients", "createDefault", "org.apache.http.impl.client.CloseableHttpClient");
|
|
14482
|
+
resolver.registerFactoryReturnType("HttpClients", "createSystem", "org.apache.http.impl.client.CloseableHttpClient");
|
|
14483
|
+
resolver.registerFactoryReturnType("HttpClients", "createMinimal", "org.apache.http.impl.client.MinimalHttpClient");
|
|
14466
14484
|
}
|
|
14467
14485
|
// ../circle-ir/dist/resolution/symbol-table.js
|
|
14468
14486
|
class SymbolTable {
|
|
@@ -22516,14 +22534,40 @@ class CrossFilePass {
|
|
|
22516
22534
|
});
|
|
22517
22535
|
}
|
|
22518
22536
|
}
|
|
22537
|
+
const filteredTaintPaths = taintPaths.filter((tp) => {
|
|
22538
|
+
const sinkIR = projectGraph.getIR(tp.sink.file);
|
|
22539
|
+
if (!sinkIR)
|
|
22540
|
+
return true;
|
|
22541
|
+
const sinkTypeStr = tp.sink.type;
|
|
22542
|
+
if (tp.source.file === tp.sink.file) {
|
|
22543
|
+
const lo = Math.min(tp.source.line, tp.sink.line);
|
|
22544
|
+
const hi = Math.max(tp.source.line, tp.sink.line);
|
|
22545
|
+
for (const san of sinkIR.taint.sanitizers ?? []) {
|
|
22546
|
+
if (san.line < lo || san.line > hi)
|
|
22547
|
+
continue;
|
|
22548
|
+
if (san.sanitizes.includes(sinkTypeStr)) {
|
|
22549
|
+
return false;
|
|
22550
|
+
}
|
|
22551
|
+
}
|
|
22552
|
+
return true;
|
|
22553
|
+
}
|
|
22554
|
+
for (const san of sinkIR.taint.sanitizers ?? []) {
|
|
22555
|
+
if (san.line !== tp.sink.line)
|
|
22556
|
+
continue;
|
|
22557
|
+
if (san.sanitizes.includes(sinkTypeStr)) {
|
|
22558
|
+
return false;
|
|
22559
|
+
}
|
|
22560
|
+
}
|
|
22561
|
+
return true;
|
|
22562
|
+
});
|
|
22519
22563
|
const typeHierarchy = projectGraph.typeHierarchy.toTypeHierarchyData();
|
|
22520
22564
|
logger.info("cross-file: complete", {
|
|
22521
22565
|
totalMs: Date.now() - startMs,
|
|
22522
|
-
paths:
|
|
22566
|
+
paths: filteredTaintPaths.length,
|
|
22523
22567
|
crossFileCalls: crossFileCalls.length,
|
|
22524
22568
|
budgetExceeded: exceeded
|
|
22525
22569
|
});
|
|
22526
|
-
const result = { crossFileCalls, taintPaths, typeHierarchy };
|
|
22570
|
+
const result = { crossFileCalls, taintPaths: filteredTaintPaths, typeHierarchy };
|
|
22527
22571
|
if (exceeded)
|
|
22528
22572
|
result.budgetExceeded = true;
|
|
22529
22573
|
return result;
|
|
@@ -23669,6 +23713,7 @@ class LanguageSourcesPass {
|
|
|
23669
23713
|
if (language === "java") {
|
|
23670
23714
|
additionalSanitizers.push(...findJavaSafeJsonParseSanitizers(code));
|
|
23671
23715
|
additionalSanitizers.push(...findJavaPathNormalizeStartsWithGuardSanitizers(code));
|
|
23716
|
+
additionalSanitizers.push(...findJavaPathGetFileNameSanitizers(code));
|
|
23672
23717
|
additionalSanitizers.push(...findJavaInlineCrlfStripLogSanitizers(code));
|
|
23673
23718
|
additionalSanitizers.push(...findJavaArgvFormExecSanitizers(code));
|
|
23674
23719
|
for (const finding of findJavaPatternFindings(code, graph.ir.meta.file)) {
|
|
@@ -25937,6 +25982,48 @@ function findJavaPathNormalizeStartsWithGuardSanitizers(code) {
|
|
|
25937
25982
|
}
|
|
25938
25983
|
return sanitizers;
|
|
25939
25984
|
}
|
|
25985
|
+
function findJavaPathGetFileNameSanitizers(code) {
|
|
25986
|
+
const sanitizers = [];
|
|
25987
|
+
const lines = code.split(`
|
|
25988
|
+
`);
|
|
25989
|
+
const assignmentRe = /^\s*(?:(?:final\s+)?[A-Za-z_][\w.<>?,\s\[\]]*?\s+)?([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
|
|
25990
|
+
const rhsHasGetFileNameRe = /\.\s*getFileName\s*\(\s*\)/;
|
|
25991
|
+
const rhsHasPathsChainRe = /\b(?:Paths\s*\.\s*get|Path\s*\.\s*of)\s*\(/;
|
|
25992
|
+
const candidates = [];
|
|
25993
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
25994
|
+
const m = assignmentRe.exec(lines[i2]);
|
|
25995
|
+
if (!m)
|
|
25996
|
+
continue;
|
|
25997
|
+
const rhs = m[2];
|
|
25998
|
+
if (!rhsHasGetFileNameRe.test(rhs))
|
|
25999
|
+
continue;
|
|
26000
|
+
if (!rhsHasPathsChainRe.test(rhs))
|
|
26001
|
+
continue;
|
|
26002
|
+
candidates.push({ line: i2 + 1, boundVar: m[1] });
|
|
26003
|
+
}
|
|
26004
|
+
if (candidates.length === 0)
|
|
26005
|
+
return sanitizers;
|
|
26006
|
+
for (const c of candidates) {
|
|
26007
|
+
sanitizers.push({
|
|
26008
|
+
type: "java_path_get_filename",
|
|
26009
|
+
method: "getFileName",
|
|
26010
|
+
line: c.line,
|
|
26011
|
+
sanitizes: ["path_traversal", "external_taint_escape"]
|
|
26012
|
+
});
|
|
26013
|
+
const varRefRe = new RegExp(`\\b${c.boundVar}\\b`);
|
|
26014
|
+
for (let l = c.line;l < lines.length; l++) {
|
|
26015
|
+
if (!varRefRe.test(lines[l]))
|
|
26016
|
+
continue;
|
|
26017
|
+
sanitizers.push({
|
|
26018
|
+
type: "java_path_get_filename",
|
|
26019
|
+
method: "getFileName",
|
|
26020
|
+
line: l + 1,
|
|
26021
|
+
sanitizes: ["path_traversal", "external_taint_escape"]
|
|
26022
|
+
});
|
|
26023
|
+
}
|
|
26024
|
+
}
|
|
26025
|
+
return sanitizers;
|
|
26026
|
+
}
|
|
25940
26027
|
function findJavaInlineCrlfStripLogSanitizers(code) {
|
|
25941
26028
|
const sanitizers = [];
|
|
25942
26029
|
const lines = code.split(`
|
|
@@ -29759,6 +29846,12 @@ function correlateDollarBraceToArgPositions(method, refs) {
|
|
|
29759
29846
|
}
|
|
29760
29847
|
}
|
|
29761
29848
|
});
|
|
29849
|
+
const declaredNameToIndex = new Map;
|
|
29850
|
+
method.parameters.forEach((param, idx) => {
|
|
29851
|
+
if (param.name && !declaredNameToIndex.has(param.name)) {
|
|
29852
|
+
declaredNameToIndex.set(param.name, idx);
|
|
29853
|
+
}
|
|
29854
|
+
});
|
|
29762
29855
|
const positions = new Set;
|
|
29763
29856
|
for (const ref of refs) {
|
|
29764
29857
|
const namedIdx = paramNameToIndex.get(ref);
|
|
@@ -29766,6 +29859,11 @@ function correlateDollarBraceToArgPositions(method, refs) {
|
|
|
29766
29859
|
positions.add(namedIdx);
|
|
29767
29860
|
continue;
|
|
29768
29861
|
}
|
|
29862
|
+
const declaredIdx = declaredNameToIndex.get(ref);
|
|
29863
|
+
if (declaredIdx !== undefined) {
|
|
29864
|
+
positions.add(declaredIdx);
|
|
29865
|
+
continue;
|
|
29866
|
+
}
|
|
29769
29867
|
const positionalIdx = parsePositionalRef(ref);
|
|
29770
29868
|
if (positionalIdx !== null && positionalIdx < method.parameters.length) {
|
|
29771
29869
|
positions.add(positionalIdx);
|
|
@@ -29796,11 +29894,11 @@ class MyBatisAnnotationSqlSinkPass {
|
|
|
29796
29894
|
category = "security";
|
|
29797
29895
|
run(ctx) {
|
|
29798
29896
|
if (ctx.language !== "java") {
|
|
29799
|
-
return { annotatedMethodCount: 0, addedSinkCount: 0 };
|
|
29897
|
+
return { annotatedMethodCount: 0, addedSinkCount: 0, declarationFindingCount: 0 };
|
|
29800
29898
|
}
|
|
29801
29899
|
const { types, imports, calls } = ctx.graph.ir;
|
|
29802
29900
|
if (!fileImportsMyBatis(imports)) {
|
|
29803
|
-
return { annotatedMethodCount: 0, addedSinkCount: 0 };
|
|
29901
|
+
return { annotatedMethodCount: 0, addedSinkCount: 0, declarationFindingCount: 0 };
|
|
29804
29902
|
}
|
|
29805
29903
|
const mapperMethods = [];
|
|
29806
29904
|
for (const type of types) {
|
|
@@ -29829,6 +29927,7 @@ class MyBatisAnnotationSqlSinkPass {
|
|
|
29829
29927
|
interfaceSimpleName: type.name,
|
|
29830
29928
|
interfaceFqn,
|
|
29831
29929
|
methodName: method.name,
|
|
29930
|
+
declarationLine: method.start_line,
|
|
29832
29931
|
taintedArgPositions: positions
|
|
29833
29932
|
});
|
|
29834
29933
|
}
|
|
@@ -29836,9 +29935,27 @@ class MyBatisAnnotationSqlSinkPass {
|
|
|
29836
29935
|
if (mapperMethods.length === 0) {
|
|
29837
29936
|
return {
|
|
29838
29937
|
annotatedMethodCount: 0,
|
|
29839
|
-
addedSinkCount: 0
|
|
29938
|
+
addedSinkCount: 0,
|
|
29939
|
+
declarationFindingCount: 0
|
|
29840
29940
|
};
|
|
29841
29941
|
}
|
|
29942
|
+
const file = ctx.graph.ir.meta.file;
|
|
29943
|
+
let declarationFindingCount = 0;
|
|
29944
|
+
for (const rec of mapperMethods) {
|
|
29945
|
+
ctx.addFinding({
|
|
29946
|
+
id: `${this.name}-${file}-${rec.declarationLine}-${rec.methodName}`,
|
|
29947
|
+
pass: this.name,
|
|
29948
|
+
category: this.category,
|
|
29949
|
+
rule_id: this.name,
|
|
29950
|
+
cwe: "CWE-89",
|
|
29951
|
+
severity: "critical",
|
|
29952
|
+
level: "error",
|
|
29953
|
+
message: `MyBatis Mapper method \`${rec.interfaceSimpleName}.${rec.methodName}\` uses raw ` + `\`\${}\` interpolation in its @Select/@Update/@Insert/@Delete annotation. ` + `Any caller passing dynamic input into arg position(s) ` + `${rec.taintedArgPositions.join(", ")} will execute unbound SQL (CWE-89). ` + `Replace \`\${x}\` with \`#{x}\` to enable JDBC parameter binding.`,
|
|
29954
|
+
file,
|
|
29955
|
+
line: rec.declarationLine
|
|
29956
|
+
});
|
|
29957
|
+
declarationFindingCount++;
|
|
29958
|
+
}
|
|
29842
29959
|
const sinks = ctx.hasResult("taint-matcher") ? ctx.getResult("taint-matcher").sinks : ctx.graph.ir.taint.sinks;
|
|
29843
29960
|
let addedSinkCount = 0;
|
|
29844
29961
|
for (const call of calls) {
|
|
@@ -29866,7 +29983,8 @@ class MyBatisAnnotationSqlSinkPass {
|
|
|
29866
29983
|
}
|
|
29867
29984
|
return {
|
|
29868
29985
|
annotatedMethodCount: mapperMethods.length,
|
|
29869
|
-
addedSinkCount
|
|
29986
|
+
addedSinkCount,
|
|
29987
|
+
declarationFindingCount
|
|
29870
29988
|
};
|
|
29871
29989
|
}
|
|
29872
29990
|
}
|
|
@@ -43682,7 +43800,7 @@ var colors = {
|
|
|
43682
43800
|
};
|
|
43683
43801
|
|
|
43684
43802
|
// src/version.ts
|
|
43685
|
-
var version = "3.
|
|
43803
|
+
var version = "3.160.0";
|
|
43686
43804
|
|
|
43687
43805
|
// src/formatters.ts
|
|
43688
43806
|
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.160.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.160.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/node": "^25.5.0",
|