cognium-dev 3.85.1 → 3.89.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 +507 -46
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -12112,6 +12112,19 @@ function argIsClassLiteral(call, position) {
|
|
|
12112
12112
|
return false;
|
|
12113
12113
|
return CLASS_LITERAL_RE.test(expr);
|
|
12114
12114
|
}
|
|
12115
|
+
var CWE_78_RECEIVER_ALLOWLIST = new Set([
|
|
12116
|
+
"Runtime",
|
|
12117
|
+
"ProcessBuilder",
|
|
12118
|
+
"Process",
|
|
12119
|
+
"CommandLine",
|
|
12120
|
+
"DefaultExecutor",
|
|
12121
|
+
"Executor",
|
|
12122
|
+
"Exec",
|
|
12123
|
+
"Launcher",
|
|
12124
|
+
"ProcStarter",
|
|
12125
|
+
"ProcessExecutor",
|
|
12126
|
+
"RuntimeUtil"
|
|
12127
|
+
]);
|
|
12115
12128
|
function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
12116
12129
|
const sinkMap = new Map;
|
|
12117
12130
|
for (const call of calls) {
|
|
@@ -12132,6 +12145,18 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12132
12145
|
if (pattern.safe_if_class_literal_at !== undefined && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
|
|
12133
12146
|
continue;
|
|
12134
12147
|
}
|
|
12148
|
+
if (pattern.type === "command_injection") {
|
|
12149
|
+
if (call.is_constructor) {
|
|
12150
|
+
if (!CWE_78_RECEIVER_ALLOWLIST.has(call.method_name)) {
|
|
12151
|
+
continue;
|
|
12152
|
+
}
|
|
12153
|
+
} else {
|
|
12154
|
+
const receiverClass = call.receiver_type;
|
|
12155
|
+
if (receiverClass && !CWE_78_RECEIVER_ALLOWLIST.has(receiverClass)) {
|
|
12156
|
+
continue;
|
|
12157
|
+
}
|
|
12158
|
+
}
|
|
12159
|
+
}
|
|
12135
12160
|
const location = formatCallLocation(call);
|
|
12136
12161
|
const key = `${location}:${call.location.line}:${pattern.cwe}`;
|
|
12137
12162
|
const confidence = calculateSinkConfidence(call, pattern);
|
|
@@ -18456,11 +18481,14 @@ var LOG_LEVELS = {
|
|
|
18456
18481
|
fatal: 5,
|
|
18457
18482
|
silent: 6
|
|
18458
18483
|
};
|
|
18459
|
-
var currentLevel = "
|
|
18484
|
+
var currentLevel = "silent";
|
|
18460
18485
|
var customLogger = null;
|
|
18461
18486
|
function shouldLog(level) {
|
|
18462
18487
|
return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel];
|
|
18463
18488
|
}
|
|
18489
|
+
function setLogLevel(level) {
|
|
18490
|
+
currentLevel = level;
|
|
18491
|
+
}
|
|
18464
18492
|
var logger = {
|
|
18465
18493
|
trace: (msg, obj) => {
|
|
18466
18494
|
if (customLogger) {
|
|
@@ -18484,7 +18512,7 @@ var logger = {
|
|
|
18484
18512
|
return;
|
|
18485
18513
|
}
|
|
18486
18514
|
if (shouldLog("info"))
|
|
18487
|
-
console.
|
|
18515
|
+
console.error(obj ? `[INFO] ${msg} ${JSON.stringify(obj)}` : `[INFO] ${msg}`);
|
|
18488
18516
|
},
|
|
18489
18517
|
warn: (msg, obj) => {
|
|
18490
18518
|
if (customLogger) {
|
|
@@ -19352,7 +19380,88 @@ class SymbolTable {
|
|
|
19352
19380
|
}
|
|
19353
19381
|
}
|
|
19354
19382
|
// ../circle-ir/dist/resolution/cross-file.js
|
|
19383
|
+
function buildFileIndex(ir) {
|
|
19384
|
+
const callsByLine = new Map;
|
|
19385
|
+
for (const c of ir.calls) {
|
|
19386
|
+
const ln = c.location.line;
|
|
19387
|
+
let arr = callsByLine.get(ln);
|
|
19388
|
+
if (!arr) {
|
|
19389
|
+
arr = [];
|
|
19390
|
+
callsByLine.set(ln, arr);
|
|
19391
|
+
}
|
|
19392
|
+
arr.push(c);
|
|
19393
|
+
}
|
|
19394
|
+
const defsByLine = new Map;
|
|
19395
|
+
for (const d of ir.dfg.defs) {
|
|
19396
|
+
let arr = defsByLine.get(d.line);
|
|
19397
|
+
if (!arr) {
|
|
19398
|
+
arr = [];
|
|
19399
|
+
defsByLine.set(d.line, arr);
|
|
19400
|
+
}
|
|
19401
|
+
arr.push(d);
|
|
19402
|
+
}
|
|
19403
|
+
const usesByLine = new Map;
|
|
19404
|
+
for (const u of ir.dfg.uses) {
|
|
19405
|
+
let arr = usesByLine.get(u.line);
|
|
19406
|
+
if (!arr) {
|
|
19407
|
+
arr = [];
|
|
19408
|
+
usesByLine.set(u.line, arr);
|
|
19409
|
+
}
|
|
19410
|
+
arr.push(u);
|
|
19411
|
+
}
|
|
19412
|
+
const callsSorted = [...ir.calls].sort((a, b) => a.location.line - b.location.line);
|
|
19413
|
+
const sinksSorted = [...ir.taint.sinks].sort((a, b) => a.line - b.line);
|
|
19414
|
+
const defsSorted = [...ir.dfg.defs].sort((a, b) => a.line - b.line);
|
|
19415
|
+
const callsByMethod = new Map;
|
|
19416
|
+
const sinksByMethod = new Map;
|
|
19417
|
+
const defsByMethod = new Map;
|
|
19418
|
+
for (const type of ir.types) {
|
|
19419
|
+
for (const method of type.methods) {
|
|
19420
|
+
const start2 = method.start_line;
|
|
19421
|
+
const end = method.end_line;
|
|
19422
|
+
const inCalls = [];
|
|
19423
|
+
for (const c of callsSorted) {
|
|
19424
|
+
const ln = c.location.line;
|
|
19425
|
+
if (ln < start2)
|
|
19426
|
+
continue;
|
|
19427
|
+
if (ln > end)
|
|
19428
|
+
break;
|
|
19429
|
+
inCalls.push(c);
|
|
19430
|
+
}
|
|
19431
|
+
callsByMethod.set(method, inCalls);
|
|
19432
|
+
const inSinks = [];
|
|
19433
|
+
for (const s of sinksSorted) {
|
|
19434
|
+
if (s.line < start2)
|
|
19435
|
+
continue;
|
|
19436
|
+
if (s.line > end)
|
|
19437
|
+
break;
|
|
19438
|
+
inSinks.push(s);
|
|
19439
|
+
}
|
|
19440
|
+
sinksByMethod.set(method, inSinks);
|
|
19441
|
+
const inDefs = [];
|
|
19442
|
+
for (const d of defsSorted) {
|
|
19443
|
+
if (d.line < start2)
|
|
19444
|
+
continue;
|
|
19445
|
+
if (d.line > end)
|
|
19446
|
+
break;
|
|
19447
|
+
inDefs.push(d);
|
|
19448
|
+
}
|
|
19449
|
+
defsByMethod.set(method, inDefs);
|
|
19450
|
+
}
|
|
19451
|
+
}
|
|
19452
|
+
return { callsByLine, defsByLine, usesByLine, callsByMethod, sinksByMethod, defsByMethod };
|
|
19453
|
+
}
|
|
19454
|
+
|
|
19355
19455
|
class CrossFileResolver {
|
|
19456
|
+
fileIndexes = new WeakMap;
|
|
19457
|
+
getFileIndex(ir) {
|
|
19458
|
+
let idx = this.fileIndexes.get(ir);
|
|
19459
|
+
if (idx)
|
|
19460
|
+
return idx;
|
|
19461
|
+
idx = buildFileIndex(ir);
|
|
19462
|
+
this.fileIndexes.set(ir, idx);
|
|
19463
|
+
return idx;
|
|
19464
|
+
}
|
|
19356
19465
|
symbolTable;
|
|
19357
19466
|
typeHierarchy;
|
|
19358
19467
|
fileIRs = new Map;
|
|
@@ -19807,7 +19916,8 @@ class CrossFileResolver {
|
|
|
19807
19916
|
}
|
|
19808
19917
|
if (!targetMethod)
|
|
19809
19918
|
continue;
|
|
19810
|
-
const
|
|
19919
|
+
const targetIdx = this.getFileIndex(targetIR);
|
|
19920
|
+
const sinksInMethod = targetIdx.sinksByMethod.get(targetMethod) ?? [];
|
|
19811
19921
|
if (sinksInMethod.length === 0)
|
|
19812
19922
|
continue;
|
|
19813
19923
|
for (const sink of sinksInMethod) {
|
|
@@ -19836,6 +19946,7 @@ class CrossFileResolver {
|
|
|
19836
19946
|
const seen = new Set;
|
|
19837
19947
|
const methodIndex = this.buildMethodIndex();
|
|
19838
19948
|
for (const [callerFile, callerIR] of this.fileIRs) {
|
|
19949
|
+
const callerIdx = this.getFileIndex(callerIR);
|
|
19839
19950
|
for (const type of callerIR.types) {
|
|
19840
19951
|
for (const method of type.methods) {
|
|
19841
19952
|
const tainted = new Map;
|
|
@@ -19853,7 +19964,7 @@ class CrossFileResolver {
|
|
|
19853
19964
|
hopChain: [{ file: callerFile, line: src.line, method: method.name, kind: "source" }]
|
|
19854
19965
|
});
|
|
19855
19966
|
}
|
|
19856
|
-
const callsInMethod =
|
|
19967
|
+
const callsInMethod = callerIdx.callsByMethod.get(method) ?? [];
|
|
19857
19968
|
for (const call of callsInMethod) {
|
|
19858
19969
|
const resolved = this.resolveCall(call, callerFile);
|
|
19859
19970
|
if (!resolved)
|
|
@@ -19867,7 +19978,7 @@ class CrossFileResolver {
|
|
|
19867
19978
|
const sourceLine = calleeSourceLine ?? call.location.line;
|
|
19868
19979
|
const sourceFile = callee.file;
|
|
19869
19980
|
const sourceType = callee.sourceType;
|
|
19870
|
-
const defsAtLine =
|
|
19981
|
+
const defsAtLine = (callerIdx.defsByLine.get(call.location.line) ?? []).filter((d) => d.kind === "local");
|
|
19871
19982
|
for (const def of defsAtLine) {
|
|
19872
19983
|
if (!def.variable)
|
|
19873
19984
|
continue;
|
|
@@ -19895,7 +20006,8 @@ class CrossFileResolver {
|
|
|
19895
20006
|
const calleeNode = methodIndex.get(resolved.targetMethod);
|
|
19896
20007
|
if (!calleeNode)
|
|
19897
20008
|
continue;
|
|
19898
|
-
const
|
|
20009
|
+
const calleeIdx = this.getFileIndex(calleeNode.ir);
|
|
20010
|
+
const sinksInCallee = calleeIdx.sinksByMethod.get(calleeNode.method) ?? [];
|
|
19899
20011
|
for (const sink of sinksInCallee) {
|
|
19900
20012
|
const key = `${matched.origin.file}:${matched.origin.line}→${callee.file}:${sink.line}`;
|
|
19901
20013
|
if (seen.has(key))
|
|
@@ -19926,9 +20038,9 @@ class CrossFileResolver {
|
|
|
19926
20038
|
}
|
|
19927
20039
|
}
|
|
19928
20040
|
if (tainted.size > 0) {
|
|
19929
|
-
const sinksInCaller =
|
|
20041
|
+
const sinksInCaller = callerIdx.sinksByMethod.get(method) ?? [];
|
|
19930
20042
|
for (const sink of sinksInCaller) {
|
|
19931
|
-
const callsAtSink =
|
|
20043
|
+
const callsAtSink = callerIdx.callsByLine.get(sink.line) ?? [];
|
|
19932
20044
|
for (const sinkCall of callsAtSink) {
|
|
19933
20045
|
for (const arg of sinkCall.arguments ?? []) {
|
|
19934
20046
|
const matched = this.matchTaintedArg(arg, tainted);
|
|
@@ -19975,6 +20087,7 @@ class CrossFileResolver {
|
|
|
19975
20087
|
const fieldExprRe = /^(\w+)\.(\w+)$/;
|
|
19976
20088
|
const methodIndex = this.buildMethodIndex();
|
|
19977
20089
|
for (const [callerFile, callerIR] of this.fileIRs) {
|
|
20090
|
+
const callerIdx = this.getFileIndex(callerIR);
|
|
19978
20091
|
for (const type of callerIR.types) {
|
|
19979
20092
|
const callerTypeFqn = callerIR.meta.package ? `${callerIR.meta.package}.${type.name}` : type.name;
|
|
19980
20093
|
for (const method of type.methods) {
|
|
@@ -19993,9 +20106,9 @@ class CrossFileResolver {
|
|
|
19993
20106
|
hopChain: [{ file: callerFile, line: src.line, method: method.name, kind: "source" }]
|
|
19994
20107
|
});
|
|
19995
20108
|
}
|
|
19996
|
-
const defsInMethod =
|
|
20109
|
+
const defsInMethod = (callerIdx.defsByMethod.get(method) ?? []).filter((d) => d.kind === "local" && !!d.variable);
|
|
19997
20110
|
for (const def of defsInMethod) {
|
|
19998
|
-
const usesAtLine =
|
|
20111
|
+
const usesAtLine = callerIdx.usesByLine.get(def.line) ?? [];
|
|
19999
20112
|
if (usesAtLine.length < 2)
|
|
20000
20113
|
continue;
|
|
20001
20114
|
let receiver = null;
|
|
@@ -20076,9 +20189,9 @@ class CrossFileResolver {
|
|
|
20076
20189
|
}
|
|
20077
20190
|
if (tainted.size === 0)
|
|
20078
20191
|
continue;
|
|
20079
|
-
const sinksInCaller =
|
|
20192
|
+
const sinksInCaller = callerIdx.sinksByMethod.get(method) ?? [];
|
|
20080
20193
|
for (const sink of sinksInCaller) {
|
|
20081
|
-
const callsAtSink =
|
|
20194
|
+
const callsAtSink = callerIdx.callsByLine.get(sink.line) ?? [];
|
|
20082
20195
|
for (const sinkCall of callsAtSink) {
|
|
20083
20196
|
for (const arg of sinkCall.arguments ?? []) {
|
|
20084
20197
|
const matched = this.matchTaintedArg(arg, tainted);
|
|
@@ -20111,7 +20224,7 @@ class CrossFileResolver {
|
|
|
20111
20224
|
}
|
|
20112
20225
|
}
|
|
20113
20226
|
}
|
|
20114
|
-
const callsInMethod =
|
|
20227
|
+
const callsInMethod = callerIdx.callsByMethod.get(method) ?? [];
|
|
20115
20228
|
for (const call of callsInMethod) {
|
|
20116
20229
|
const resolved = this.resolveCall(call, callerFile);
|
|
20117
20230
|
if (!resolved)
|
|
@@ -20128,7 +20241,8 @@ class CrossFileResolver {
|
|
|
20128
20241
|
const calleeNode = methodIndex.get(resolved.targetMethod);
|
|
20129
20242
|
if (!calleeNode)
|
|
20130
20243
|
continue;
|
|
20131
|
-
const
|
|
20244
|
+
const calleeIdx = this.getFileIndex(calleeNode.ir);
|
|
20245
|
+
const sinksInCallee = calleeIdx.sinksByMethod.get(calleeNode.method) ?? [];
|
|
20132
20246
|
for (const sink of sinksInCallee) {
|
|
20133
20247
|
const key = `fb:${matched.origin.file}:${matched.origin.line}→${callee.file}:${sink.line}`;
|
|
20134
20248
|
if (seen.has(key))
|
|
@@ -20370,9 +20484,20 @@ class AnalysisPipeline {
|
|
|
20370
20484
|
}
|
|
20371
20485
|
// ../circle-ir/dist/analysis/passes/cross-file-pass.js
|
|
20372
20486
|
class CrossFilePass {
|
|
20373
|
-
run(projectGraph, sourceLines) {
|
|
20487
|
+
run(projectGraph, sourceLines, options = {}) {
|
|
20374
20488
|
const resolver = projectGraph.resolver;
|
|
20489
|
+
const budgetMs = options.budgetMs ?? 0;
|
|
20490
|
+
const startMs = Date.now();
|
|
20491
|
+
const budgetExceeded = () => budgetMs > 0 && Date.now() - startMs > budgetMs;
|
|
20492
|
+
const fileCount = projectGraph.filePaths.length;
|
|
20493
|
+
logger.info("cross-file: starting", { files: fileCount, budgetMs });
|
|
20494
|
+
const phase1Start = Date.now();
|
|
20495
|
+
logger.debug("cross-file: phase 1/4 starting (findCrossFileTaintFlows)");
|
|
20375
20496
|
const flows = resolver.findCrossFileTaintFlows();
|
|
20497
|
+
logger.info("cross-file: phase 1/4 done", {
|
|
20498
|
+
flows: flows.length,
|
|
20499
|
+
elapsedMs: Date.now() - phase1Start
|
|
20500
|
+
});
|
|
20376
20501
|
const taintPaths = flows.flatMap((flow, idx) => {
|
|
20377
20502
|
const srcLines = sourceLines.get(flow.sourceFile) ?? [];
|
|
20378
20503
|
const tgtLines = sourceLines.get(flow.targetFile) ?? [];
|
|
@@ -20418,11 +20543,59 @@ class CrossFilePass {
|
|
|
20418
20543
|
confidence: 0.7
|
|
20419
20544
|
}];
|
|
20420
20545
|
});
|
|
20421
|
-
|
|
20422
|
-
|
|
20423
|
-
|
|
20424
|
-
|
|
20425
|
-
|
|
20546
|
+
let exceeded = false;
|
|
20547
|
+
const ipPaths = [];
|
|
20548
|
+
if (budgetExceeded()) {
|
|
20549
|
+
exceeded = true;
|
|
20550
|
+
logger.warn("cross-file: budget exceeded after phase 1/4, skipping phases 2-4", {
|
|
20551
|
+
budgetMs,
|
|
20552
|
+
elapsedMs: Date.now() - startMs,
|
|
20553
|
+
partialPaths: taintPaths.length
|
|
20554
|
+
});
|
|
20555
|
+
} else {
|
|
20556
|
+
const phase2Start = Date.now();
|
|
20557
|
+
logger.debug("cross-file: phase 2/4 starting (findInterproceduralTaintPaths)");
|
|
20558
|
+
const phase2 = resolver.findInterproceduralTaintPaths();
|
|
20559
|
+
ipPaths.push(...phase2);
|
|
20560
|
+
logger.info("cross-file: phase 2/4 done", {
|
|
20561
|
+
paths: phase2.length,
|
|
20562
|
+
elapsedMs: Date.now() - phase2Start
|
|
20563
|
+
});
|
|
20564
|
+
}
|
|
20565
|
+
if (!exceeded && budgetExceeded()) {
|
|
20566
|
+
exceeded = true;
|
|
20567
|
+
logger.warn("cross-file: budget exceeded after phase 2/4, skipping phases 3-4", {
|
|
20568
|
+
budgetMs,
|
|
20569
|
+
elapsedMs: Date.now() - startMs,
|
|
20570
|
+
partialPaths: taintPaths.length + ipPaths.length
|
|
20571
|
+
});
|
|
20572
|
+
} else if (!exceeded) {
|
|
20573
|
+
const phase3Start = Date.now();
|
|
20574
|
+
logger.debug("cross-file: phase 3/4 starting (findFieldBindingTaintPaths)");
|
|
20575
|
+
const phase3 = resolver.findFieldBindingTaintPaths();
|
|
20576
|
+
ipPaths.push(...phase3);
|
|
20577
|
+
logger.info("cross-file: phase 3/4 done", {
|
|
20578
|
+
paths: phase3.length,
|
|
20579
|
+
elapsedMs: Date.now() - phase3Start
|
|
20580
|
+
});
|
|
20581
|
+
}
|
|
20582
|
+
if (!exceeded && budgetExceeded()) {
|
|
20583
|
+
exceeded = true;
|
|
20584
|
+
logger.warn("cross-file: budget exceeded after phase 3/4, skipping phase 4", {
|
|
20585
|
+
budgetMs,
|
|
20586
|
+
elapsedMs: Date.now() - startMs,
|
|
20587
|
+
partialPaths: taintPaths.length + ipPaths.length
|
|
20588
|
+
});
|
|
20589
|
+
} else if (!exceeded) {
|
|
20590
|
+
const phase4Start = Date.now();
|
|
20591
|
+
logger.debug("cross-file: phase 4/4 starting (findCrossInstanceAliasingPaths)");
|
|
20592
|
+
const phase4 = findCrossInstanceAliasingPaths(projectGraph, sourceLines);
|
|
20593
|
+
ipPaths.push(...phase4);
|
|
20594
|
+
logger.info("cross-file: phase 4/4 done", {
|
|
20595
|
+
paths: phase4.length,
|
|
20596
|
+
elapsedMs: Date.now() - phase4Start
|
|
20597
|
+
});
|
|
20598
|
+
}
|
|
20426
20599
|
for (let i2 = 0;i2 < ipPaths.length; i2++) {
|
|
20427
20600
|
const p = ipPaths[i2];
|
|
20428
20601
|
const sinkIR = projectGraph.getIR(p.sink.file);
|
|
@@ -20434,7 +20607,7 @@ class CrossFilePass {
|
|
|
20434
20607
|
const srcLines = sourceLines.get(p.source.file) ?? [];
|
|
20435
20608
|
const tgtLines = sourceLines.get(p.sink.file) ?? [];
|
|
20436
20609
|
const dupId = `${p.source.file}:${p.source.line}→${p.sink.file}:${p.sink.line}`;
|
|
20437
|
-
if (taintPaths.some((tp) => tp.source.file === p.source.file && tp.source.line === p.source.line && tp.sink.file === p.sink.file && tp.sink.line === p.sink.line)) {
|
|
20610
|
+
if (taintPaths.some((tp) => tp.source.file === p.source.file && tp.source.line === p.source.line && tp.sink.file === p.sink.file && tp.sink.line === p.sink.line && tp.sink.type === matchedSink.type)) {
|
|
20438
20611
|
continue;
|
|
20439
20612
|
}
|
|
20440
20613
|
taintPaths.push({
|
|
@@ -20494,7 +20667,16 @@ class CrossFilePass {
|
|
|
20494
20667
|
}
|
|
20495
20668
|
}
|
|
20496
20669
|
const typeHierarchy = projectGraph.typeHierarchy.toTypeHierarchyData();
|
|
20497
|
-
|
|
20670
|
+
logger.info("cross-file: complete", {
|
|
20671
|
+
totalMs: Date.now() - startMs,
|
|
20672
|
+
paths: taintPaths.length,
|
|
20673
|
+
crossFileCalls: crossFileCalls.length,
|
|
20674
|
+
budgetExceeded: exceeded
|
|
20675
|
+
});
|
|
20676
|
+
const result = { crossFileCalls, taintPaths, typeHierarchy };
|
|
20677
|
+
if (exceeded)
|
|
20678
|
+
result.budgetExceeded = true;
|
|
20679
|
+
return result;
|
|
20498
20680
|
}
|
|
20499
20681
|
}
|
|
20500
20682
|
function findCrossInstanceAliasingPaths(projectGraph, _sourceLines) {
|
|
@@ -24680,6 +24862,194 @@ function findTaintBridges2(result) {
|
|
|
24680
24862
|
return bridges;
|
|
24681
24863
|
}
|
|
24682
24864
|
|
|
24865
|
+
// ../circle-ir/dist/analysis/entry-point-detection.js
|
|
24866
|
+
var TIER_1_METHOD_ANNOTATIONS = new Set([
|
|
24867
|
+
"RequestMapping",
|
|
24868
|
+
"GetMapping",
|
|
24869
|
+
"PostMapping",
|
|
24870
|
+
"PutMapping",
|
|
24871
|
+
"DeleteMapping",
|
|
24872
|
+
"PatchMapping",
|
|
24873
|
+
"MessageMapping",
|
|
24874
|
+
"SubscribeMapping",
|
|
24875
|
+
"KafkaListener",
|
|
24876
|
+
"KafkaHandler",
|
|
24877
|
+
"RabbitListener",
|
|
24878
|
+
"RabbitHandler",
|
|
24879
|
+
"JmsListener",
|
|
24880
|
+
"StreamListener",
|
|
24881
|
+
"SqsListener",
|
|
24882
|
+
"SqsHandler",
|
|
24883
|
+
"EventListener",
|
|
24884
|
+
"Scheduled",
|
|
24885
|
+
"Path",
|
|
24886
|
+
"GET",
|
|
24887
|
+
"POST",
|
|
24888
|
+
"PUT",
|
|
24889
|
+
"DELETE",
|
|
24890
|
+
"PATCH",
|
|
24891
|
+
"HEAD",
|
|
24892
|
+
"OPTIONS"
|
|
24893
|
+
]);
|
|
24894
|
+
var TIER_1_CLASS_ANNOTATIONS = new Set([
|
|
24895
|
+
"RestController",
|
|
24896
|
+
"Controller",
|
|
24897
|
+
"Path",
|
|
24898
|
+
"WebServlet",
|
|
24899
|
+
"ServerEndpoint",
|
|
24900
|
+
"FeignClient"
|
|
24901
|
+
]);
|
|
24902
|
+
var TIER_1_BY_SUPERTYPE = new Map([
|
|
24903
|
+
["HttpServlet", new Set(["doGet", "doPost", "doPut", "doDelete", "doHead", "doOptions", "doTrace", "service"])],
|
|
24904
|
+
["GenericServlet", new Set(["service"])],
|
|
24905
|
+
["Filter", new Set(["doFilter"])],
|
|
24906
|
+
["HandlerInterceptor", new Set(["preHandle", "postHandle", "afterCompletion"])],
|
|
24907
|
+
["AsyncHandlerInterceptor", new Set(["preHandle", "postHandle", "afterCompletion", "afterConcurrentHandlingStarted"])],
|
|
24908
|
+
["CommandLineRunner", new Set(["run"])],
|
|
24909
|
+
["ApplicationRunner", new Set(["run"])]
|
|
24910
|
+
]);
|
|
24911
|
+
var TIER_3_CLASS_SUFFIXES = [
|
|
24912
|
+
"Util",
|
|
24913
|
+
"Utils",
|
|
24914
|
+
"Helper",
|
|
24915
|
+
"Helpers"
|
|
24916
|
+
];
|
|
24917
|
+
var TIER_3_PACKAGE_FRAGMENTS = [
|
|
24918
|
+
".template.",
|
|
24919
|
+
".templates.",
|
|
24920
|
+
".engine.",
|
|
24921
|
+
".engines."
|
|
24922
|
+
];
|
|
24923
|
+
var TIER_3_JDK_FACADE_INTERFACES = new Set([
|
|
24924
|
+
"Collection",
|
|
24925
|
+
"List",
|
|
24926
|
+
"Set",
|
|
24927
|
+
"Map",
|
|
24928
|
+
"Queue",
|
|
24929
|
+
"Deque",
|
|
24930
|
+
"SortedSet",
|
|
24931
|
+
"SortedMap",
|
|
24932
|
+
"NavigableSet",
|
|
24933
|
+
"NavigableMap",
|
|
24934
|
+
"Iterator",
|
|
24935
|
+
"Iterable",
|
|
24936
|
+
"ListIterator",
|
|
24937
|
+
"Comparator",
|
|
24938
|
+
"Comparable",
|
|
24939
|
+
"Serializable",
|
|
24940
|
+
"Externalizable",
|
|
24941
|
+
"Cloneable"
|
|
24942
|
+
]);
|
|
24943
|
+
function classNameLooksLikeUtility(name2) {
|
|
24944
|
+
if (!name2)
|
|
24945
|
+
return false;
|
|
24946
|
+
for (const suffix of TIER_3_CLASS_SUFFIXES) {
|
|
24947
|
+
if (name2.length > suffix.length && name2.endsWith(suffix))
|
|
24948
|
+
return true;
|
|
24949
|
+
}
|
|
24950
|
+
return false;
|
|
24951
|
+
}
|
|
24952
|
+
function packageLooksLikeTemplateOrEngine(pkg) {
|
|
24953
|
+
if (!pkg)
|
|
24954
|
+
return false;
|
|
24955
|
+
const padded = `.${pkg}.`;
|
|
24956
|
+
for (const frag of TIER_3_PACKAGE_FRAGMENTS) {
|
|
24957
|
+
if (padded.includes(frag))
|
|
24958
|
+
return true;
|
|
24959
|
+
}
|
|
24960
|
+
return false;
|
|
24961
|
+
}
|
|
24962
|
+
function implementsJdkFacade(t) {
|
|
24963
|
+
if (!t)
|
|
24964
|
+
return false;
|
|
24965
|
+
for (const impl of t.implements ?? []) {
|
|
24966
|
+
if (TIER_3_JDK_FACADE_INTERFACES.has(simpleTypeName(impl)))
|
|
24967
|
+
return true;
|
|
24968
|
+
}
|
|
24969
|
+
return false;
|
|
24970
|
+
}
|
|
24971
|
+
function classShapeIsLibraryFacade(enclosingType) {
|
|
24972
|
+
if (!enclosingType)
|
|
24973
|
+
return false;
|
|
24974
|
+
if (classNameLooksLikeUtility(enclosingType.name))
|
|
24975
|
+
return true;
|
|
24976
|
+
if (packageLooksLikeTemplateOrEngine(enclosingType.package))
|
|
24977
|
+
return true;
|
|
24978
|
+
if (implementsJdkFacade(enclosingType))
|
|
24979
|
+
return true;
|
|
24980
|
+
return false;
|
|
24981
|
+
}
|
|
24982
|
+
function annotationsInclude(annotations, targets) {
|
|
24983
|
+
if (!annotations || annotations.length === 0)
|
|
24984
|
+
return false;
|
|
24985
|
+
for (const raw of annotations) {
|
|
24986
|
+
const simple = raw.replace(/^@/, "").replace(/[<(].*$/, "").trim();
|
|
24987
|
+
if (targets.has(simple))
|
|
24988
|
+
return true;
|
|
24989
|
+
}
|
|
24990
|
+
return false;
|
|
24991
|
+
}
|
|
24992
|
+
function simpleTypeName(ref) {
|
|
24993
|
+
return ref.replace(/<.*$/, "").trim();
|
|
24994
|
+
}
|
|
24995
|
+
function looksLikeMainMethod(method) {
|
|
24996
|
+
if (method.name !== "main")
|
|
24997
|
+
return false;
|
|
24998
|
+
const params = method.parameters ?? [];
|
|
24999
|
+
if (params.length !== 1)
|
|
25000
|
+
return false;
|
|
25001
|
+
const t = (params[0].type ?? "").replace(/\s+/g, "");
|
|
25002
|
+
return t === "String[]" || t === "String..." || t === "java.lang.String[]";
|
|
25003
|
+
}
|
|
25004
|
+
function methodIsSupertypeLifecycleEntryPoint(method, enclosingType) {
|
|
25005
|
+
if (!method.name)
|
|
25006
|
+
return false;
|
|
25007
|
+
if (!enclosingType)
|
|
25008
|
+
return false;
|
|
25009
|
+
const candidates = [];
|
|
25010
|
+
if (enclosingType.extends)
|
|
25011
|
+
candidates.push(simpleTypeName(enclosingType.extends));
|
|
25012
|
+
for (const i2 of enclosingType.implements ?? [])
|
|
25013
|
+
candidates.push(simpleTypeName(i2));
|
|
25014
|
+
for (const supertype of candidates) {
|
|
25015
|
+
const lifecycleMethods = TIER_1_BY_SUPERTYPE.get(supertype);
|
|
25016
|
+
if (lifecycleMethods?.has(method.name))
|
|
25017
|
+
return true;
|
|
25018
|
+
}
|
|
25019
|
+
return false;
|
|
25020
|
+
}
|
|
25021
|
+
function classifyEntryPointTier(method, enclosingType, ctx) {
|
|
25022
|
+
const language = (ctx.language ?? "").toLowerCase();
|
|
25023
|
+
if (language !== "java")
|
|
25024
|
+
return "TIER_UNKNOWN";
|
|
25025
|
+
if (!method)
|
|
25026
|
+
return "TIER_UNKNOWN";
|
|
25027
|
+
if (classShapeIsLibraryFacade(enclosingType)) {
|
|
25028
|
+
return "TIER_3_LIBRARY_API";
|
|
25029
|
+
}
|
|
25030
|
+
if (annotationsInclude(method.annotations, TIER_1_METHOD_ANNOTATIONS)) {
|
|
25031
|
+
return "TIER_1_ENTRY_POINT";
|
|
25032
|
+
}
|
|
25033
|
+
if (enclosingType && annotationsInclude(enclosingType.annotations, TIER_1_CLASS_ANNOTATIONS)) {
|
|
25034
|
+
return "TIER_1_ENTRY_POINT";
|
|
25035
|
+
}
|
|
25036
|
+
if (methodIsSupertypeLifecycleEntryPoint(method, enclosingType)) {
|
|
25037
|
+
return "TIER_1_ENTRY_POINT";
|
|
25038
|
+
}
|
|
25039
|
+
if (looksLikeMainMethod(method)) {
|
|
25040
|
+
return "TIER_1_ENTRY_POINT";
|
|
25041
|
+
}
|
|
25042
|
+
return "TIER_3_LIBRARY_API";
|
|
25043
|
+
}
|
|
25044
|
+
function shouldGateInterproceduralParam(sourceType, enclosingMethod, enclosingType, ctx) {
|
|
25045
|
+
if (sourceType !== "interprocedural_param")
|
|
25046
|
+
return false;
|
|
25047
|
+
if (!enclosingMethod)
|
|
25048
|
+
return false;
|
|
25049
|
+
const tier = classifyEntryPointTier(enclosingMethod, enclosingType, ctx);
|
|
25050
|
+
return tier === "TIER_3_LIBRARY_API";
|
|
25051
|
+
}
|
|
25052
|
+
|
|
24683
25053
|
// ../circle-ir/dist/analysis/passes/interprocedural-pass.js
|
|
24684
25054
|
class InterproceduralPass {
|
|
24685
25055
|
name = "interprocedural";
|
|
@@ -24696,6 +25066,15 @@ class InterproceduralPass {
|
|
|
24696
25066
|
const additionalSinks = [];
|
|
24697
25067
|
const additionalFlows = [...taintProp.flows];
|
|
24698
25068
|
let interprocedural;
|
|
25069
|
+
const methodNameIndex = new Map;
|
|
25070
|
+
for (const type of graph.ir.types ?? []) {
|
|
25071
|
+
for (const method of type.methods ?? []) {
|
|
25072
|
+
if (method.name && !methodNameIndex.has(method.name)) {
|
|
25073
|
+
methodNameIndex.set(method.name, { method, type });
|
|
25074
|
+
}
|
|
25075
|
+
}
|
|
25076
|
+
}
|
|
25077
|
+
const language = graph.ir.meta.language;
|
|
24699
25078
|
if (sinks.length > 0) {
|
|
24700
25079
|
const interProc = analyzeInterprocedural2(graph, sources, sinks, sanitizers, {
|
|
24701
25080
|
taintedVariables: constProp.tainted
|
|
@@ -24733,6 +25112,12 @@ class InterproceduralPass {
|
|
|
24733
25112
|
continue;
|
|
24734
25113
|
if (source.type === "interprocedural_param" && source.confidence < 0.6)
|
|
24735
25114
|
continue;
|
|
25115
|
+
if (source.type === "interprocedural_param" && source.in_method) {
|
|
25116
|
+
const enclosing = methodNameIndex.get(source.in_method);
|
|
25117
|
+
if (shouldGateInterproceduralParam(source.type, enclosing?.method, enclosing?.type, { language, types: graph.ir.types })) {
|
|
25118
|
+
continue;
|
|
25119
|
+
}
|
|
25120
|
+
}
|
|
24736
25121
|
if (additionalFlows.some((f) => f.source_line === source.line && f.sink_line === sink.line))
|
|
24737
25122
|
continue;
|
|
24738
25123
|
additionalFlows.push({
|
|
@@ -29073,6 +29458,20 @@ var CRED_KEYWORD_RE = /\b([A-Za-z_$][\w$]*?(?:password|passwd|secret|api[_-]?key
|
|
|
29073
29458
|
var CRED_DYNAMIC_VALUE_RE = /\$\{|process\.env|os\.environ|os\.Getenv|System\.getenv/;
|
|
29074
29459
|
var CRED_FUNCTION_DECL_RE = /\b(?:function|func|def|fn)\s+\w+\s*\(/;
|
|
29075
29460
|
var CRED_COMPARISON_RE = /(?:===?|!==?|>=|<=|<>)\s*["'`]/;
|
|
29461
|
+
var PROPERTY_KEY_RE = /^[a-z][a-zA-Z0-9_-]*\.[a-zA-Z][a-zA-Z0-9_.-]*$/;
|
|
29462
|
+
var PLAIN_IDENTIFIER_RE = /^[a-z][a-zA-Z_]*$/;
|
|
29463
|
+
function charClassDiversity(s) {
|
|
29464
|
+
let n = 0;
|
|
29465
|
+
if (/[a-z]/.test(s))
|
|
29466
|
+
n++;
|
|
29467
|
+
if (/[A-Z]/.test(s))
|
|
29468
|
+
n++;
|
|
29469
|
+
if (/[0-9]/.test(s))
|
|
29470
|
+
n++;
|
|
29471
|
+
if (/[^a-zA-Z0-9]/.test(s))
|
|
29472
|
+
n++;
|
|
29473
|
+
return n;
|
|
29474
|
+
}
|
|
29076
29475
|
function isLikelyCredentialAssignment(line) {
|
|
29077
29476
|
if (CRED_FUNCTION_DECL_RE.test(line))
|
|
29078
29477
|
return null;
|
|
@@ -29091,6 +29490,18 @@ function isLikelyCredentialAssignment(line) {
|
|
|
29091
29490
|
return null;
|
|
29092
29491
|
if (isAllSameChar(value))
|
|
29093
29492
|
return null;
|
|
29493
|
+
if (value.length < 12)
|
|
29494
|
+
return null;
|
|
29495
|
+
if (shannonEntropy(value) < 3.5)
|
|
29496
|
+
return null;
|
|
29497
|
+
if (charClassDiversity(value) < 2)
|
|
29498
|
+
return null;
|
|
29499
|
+
if (PROPERTY_KEY_RE.test(value))
|
|
29500
|
+
return null;
|
|
29501
|
+
if (PLAIN_IDENTIFIER_RE.test(value))
|
|
29502
|
+
return null;
|
|
29503
|
+
if (/^[0-9]+$/.test(value) && value.length < 16)
|
|
29504
|
+
return null;
|
|
29094
29505
|
return { name: name2, value };
|
|
29095
29506
|
}
|
|
29096
29507
|
var STRING_LITERAL_RE = /(["'`])((?:\\.|(?!\1).){8,200})\1/g;
|
|
@@ -33916,7 +34327,8 @@ async function analyzeProject(files, options = {}) {
|
|
|
33916
34327
|
sourceLinesByFile.set(filePath, code.split(`
|
|
33917
34328
|
`));
|
|
33918
34329
|
}
|
|
33919
|
-
const
|
|
34330
|
+
const crossFileBudgetMs = options.crossFileBudgetMs ?? 300000;
|
|
34331
|
+
const crossFileResult = new CrossFilePass().run(projectGraph, sourceLinesByFile, { budgetMs: crossFileBudgetMs });
|
|
33920
34332
|
const disabledPasses = options.disabledPasses ?? [];
|
|
33921
34333
|
if (!disabledPasses.includes("security-headers")) {
|
|
33922
34334
|
const inheritedFindings = checkInheritedCorsHeaders(fileAnalyses, projectGraph.typeHierarchy, sourceLinesByFile);
|
|
@@ -33946,7 +34358,7 @@ async function analyzeProject(files, options = {}) {
|
|
|
33946
34358
|
total_loc: totalLoc,
|
|
33947
34359
|
analyzed_at: new Date().toISOString()
|
|
33948
34360
|
};
|
|
33949
|
-
|
|
34361
|
+
const projectAnalysis = {
|
|
33950
34362
|
meta,
|
|
33951
34363
|
files: fileAnalyses,
|
|
33952
34364
|
type_hierarchy: crossFileResult.typeHierarchy,
|
|
@@ -33954,6 +34366,10 @@ async function analyzeProject(files, options = {}) {
|
|
|
33954
34366
|
taint_paths: crossFileResult.taintPaths,
|
|
33955
34367
|
findings: []
|
|
33956
34368
|
};
|
|
34369
|
+
if (crossFileResult.budgetExceeded) {
|
|
34370
|
+
projectAnalysis.cross_file_budget_exceeded = true;
|
|
34371
|
+
}
|
|
34372
|
+
return projectAnalysis;
|
|
33957
34373
|
}
|
|
33958
34374
|
function deriveProjectName(paths) {
|
|
33959
34375
|
if (paths.length === 0)
|
|
@@ -33993,7 +34409,7 @@ var colors = {
|
|
|
33993
34409
|
};
|
|
33994
34410
|
|
|
33995
34411
|
// src/version.ts
|
|
33996
|
-
var version = "3.
|
|
34412
|
+
var version = "3.89.2";
|
|
33997
34413
|
|
|
33998
34414
|
// src/formatters.ts
|
|
33999
34415
|
var SINK_SEVERITY = {
|
|
@@ -34354,6 +34770,11 @@ function formatResults(results, verbose, crossFileData) {
|
|
|
34354
34770
|
lines.push("");
|
|
34355
34771
|
lines.push(formatCrossFilePaths(crossFileData.taintPaths));
|
|
34356
34772
|
}
|
|
34773
|
+
if (crossFileData?.budgetExceeded) {
|
|
34774
|
+
lines.push("");
|
|
34775
|
+
lines.push(colors.yellow("⚠ Cross-file budget exceeded — some cross-file taint paths may be missing."));
|
|
34776
|
+
lines.push(colors.yellow(" Raise the limit via the `crossFileBudgetMs` analyzer option (default 300000ms)."));
|
|
34777
|
+
}
|
|
34357
34778
|
return lines.join(`
|
|
34358
34779
|
`);
|
|
34359
34780
|
}
|
|
@@ -34368,11 +34789,13 @@ function formatJSON(results, crossFileData) {
|
|
|
34368
34789
|
})),
|
|
34369
34790
|
cross_file_taint_paths: crossFileData?.taintPaths ?? [],
|
|
34370
34791
|
cross_file_calls: crossFileData?.crossFileCalls ?? [],
|
|
34792
|
+
cross_file_budget_exceeded: crossFileData?.budgetExceeded ?? false,
|
|
34371
34793
|
summary: {
|
|
34372
34794
|
filesScanned: results.length,
|
|
34373
34795
|
filesWithVulnerabilities: results.filter((r) => r.vulnerabilities.length > 0).length,
|
|
34374
34796
|
totalVulnerabilities: results.reduce((sum, r) => sum + r.vulnerabilities.length, 0),
|
|
34375
34797
|
crossFileTaintPaths: crossFileData?.taintPaths.length ?? 0,
|
|
34798
|
+
crossFileBudgetExceeded: crossFileData?.budgetExceeded ?? false,
|
|
34376
34799
|
errors: results.filter((r) => r.error).length
|
|
34377
34800
|
}
|
|
34378
34801
|
};
|
|
@@ -34568,6 +34991,13 @@ SCAN OPTIONS:
|
|
|
34568
34991
|
-o, --output <file> Write results to file
|
|
34569
34992
|
-q, --quiet Suppress progress output
|
|
34570
34993
|
-v, --verbose Show detailed output
|
|
34994
|
+
--log-level <level> circle-ir logger level (silent|trace|debug|info|warn|error|fatal)
|
|
34995
|
+
[default: silent — also settable via COGNIUM_LOG_LEVEL env var]
|
|
34996
|
+
--cross-file-budget-ms <n> Wall-time budget (ms) for the cross-file phase
|
|
34997
|
+
[default: 300000 (5 min) — 0 = unlimited]
|
|
34998
|
+
On exceed: partial taint paths kept, remaining
|
|
34999
|
+
cross-file phases skipped, cross_file_budget_exceeded
|
|
35000
|
+
surfaced in output (text warning / JSON / SARIF field).
|
|
34571
35001
|
|
|
34572
35002
|
METRICS OPTIONS:
|
|
34573
35003
|
-l, --language <lang> Analyze only files for language (bash|go|html|java|javascript|typescript|python|rust)
|
|
@@ -34590,6 +35020,10 @@ EXAMPLES:
|
|
|
34590
35020
|
cognium-dev scan . --exclude-cwe CWE-330,CWE-327
|
|
34591
35021
|
cognium-dev scan . --disable-pass naming-convention,todo-in-prod
|
|
34592
35022
|
cognium-dev scan . --profile custom-config.json
|
|
35023
|
+
cognium-dev scan . --log-level info # phase markers to stderr
|
|
35024
|
+
COGNIUM_LOG_LEVEL=debug cognium-dev scan . # verbose via env var
|
|
35025
|
+
cognium-dev scan . --cross-file-budget-ms 60000 # 60s cross-file cap
|
|
35026
|
+
cognium-dev scan . --cross-file-budget-ms 0 # unlimited (pre-3.89.0 behaviour)
|
|
34593
35027
|
cognium-dev metrics src/
|
|
34594
35028
|
cognium-dev metrics src/ --category complexity
|
|
34595
35029
|
cognium-dev metrics src/ --format json --profile custom-config.json
|
|
@@ -34619,10 +35053,10 @@ class Spinner {
|
|
|
34619
35053
|
enabled;
|
|
34620
35054
|
constructor(text) {
|
|
34621
35055
|
this._text = text;
|
|
34622
|
-
this.enabled = Boolean(process.
|
|
35056
|
+
this.enabled = Boolean(process.stderr.isTTY);
|
|
34623
35057
|
}
|
|
34624
35058
|
render(frame) {
|
|
34625
|
-
process.
|
|
35059
|
+
process.stderr.write(`\r\x1B[K${frame} ${this._text}`);
|
|
34626
35060
|
}
|
|
34627
35061
|
start() {
|
|
34628
35062
|
if (this.isSpinning)
|
|
@@ -34631,7 +35065,7 @@ class Spinner {
|
|
|
34631
35065
|
return this;
|
|
34632
35066
|
this.isSpinning = true;
|
|
34633
35067
|
this.frameIndex = 0;
|
|
34634
|
-
process.
|
|
35068
|
+
process.stderr.write("\x1B[?25l");
|
|
34635
35069
|
this.render(SPINNER_FRAMES[this.frameIndex]);
|
|
34636
35070
|
this.intervalId = setInterval(() => {
|
|
34637
35071
|
this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
|
|
@@ -34648,26 +35082,26 @@ class Spinner {
|
|
|
34648
35082
|
this.intervalId = undefined;
|
|
34649
35083
|
}
|
|
34650
35084
|
this.isSpinning = false;
|
|
34651
|
-
process.
|
|
34652
|
-
process.
|
|
35085
|
+
process.stderr.write("\r\x1B[K");
|
|
35086
|
+
process.stderr.write("\x1B[?25h");
|
|
34653
35087
|
return this;
|
|
34654
35088
|
}
|
|
34655
35089
|
succeed(text) {
|
|
34656
35090
|
this.stop();
|
|
34657
35091
|
const message = text || this._text;
|
|
34658
|
-
console.
|
|
35092
|
+
console.error(`\x1B[32m${CHECKMARK}\x1B[0m ${message}`);
|
|
34659
35093
|
return this;
|
|
34660
35094
|
}
|
|
34661
35095
|
fail(text) {
|
|
34662
35096
|
this.stop();
|
|
34663
35097
|
const message = text || this._text;
|
|
34664
|
-
console.
|
|
35098
|
+
console.error(`\x1B[31m${CROSS}\x1B[0m ${message}`);
|
|
34665
35099
|
return this;
|
|
34666
35100
|
}
|
|
34667
35101
|
warn(text) {
|
|
34668
35102
|
this.stop();
|
|
34669
35103
|
const message = text || this._text;
|
|
34670
|
-
console.
|
|
35104
|
+
console.error(`\x1B[33m${WARNING}\x1B[0m ${message}`);
|
|
34671
35105
|
return this;
|
|
34672
35106
|
}
|
|
34673
35107
|
set text(value) {
|
|
@@ -34897,7 +35331,7 @@ async function scanFile(filePath, language, analyzeOpts) {
|
|
|
34897
35331
|
};
|
|
34898
35332
|
}
|
|
34899
35333
|
}
|
|
34900
|
-
async function scanProject(files, language, analyzeOpts) {
|
|
35334
|
+
async function scanProject(files, language, analyzeOpts, crossFileBudgetMs) {
|
|
34901
35335
|
const filesWithCode = files.map((f) => ({
|
|
34902
35336
|
code: readFileSync(f, "utf-8"),
|
|
34903
35337
|
filePath: f,
|
|
@@ -34905,7 +35339,8 @@ async function scanProject(files, language, analyzeOpts) {
|
|
|
34905
35339
|
}));
|
|
34906
35340
|
const projectResult = await analyzeProject(filesWithCode, {
|
|
34907
35341
|
passOptions: analyzeOpts?.passOptions,
|
|
34908
|
-
disabledPasses: analyzeOpts?.disabledPasses
|
|
35342
|
+
disabledPasses: analyzeOpts?.disabledPasses,
|
|
35343
|
+
...crossFileBudgetMs !== undefined ? { crossFileBudgetMs } : {}
|
|
34909
35344
|
});
|
|
34910
35345
|
const results = projectResult.files.map(({ file, analysis }) => {
|
|
34911
35346
|
const vulnerabilities = (analysis.taint.flows || []).map((flow) => ({
|
|
@@ -34933,7 +35368,8 @@ async function scanProject(files, language, analyzeOpts) {
|
|
|
34933
35368
|
results,
|
|
34934
35369
|
crossFileData: {
|
|
34935
35370
|
taintPaths: projectResult.taint_paths,
|
|
34936
|
-
crossFileCalls: projectResult.cross_file_calls
|
|
35371
|
+
crossFileCalls: projectResult.cross_file_calls,
|
|
35372
|
+
budgetExceeded: projectResult.cross_file_budget_exceeded === true
|
|
34937
35373
|
}
|
|
34938
35374
|
};
|
|
34939
35375
|
}
|
|
@@ -35028,7 +35464,7 @@ async function runScan(targetPath, options) {
|
|
|
35028
35464
|
disabledPasses = converted.disabledPasses;
|
|
35029
35465
|
suppressions = config.suppressions ?? [];
|
|
35030
35466
|
if (!options.quiet) {
|
|
35031
|
-
console.
|
|
35467
|
+
console.error(colors.dim(`Loaded config: ${options.profile || "cognium.config.json"}`));
|
|
35032
35468
|
}
|
|
35033
35469
|
}
|
|
35034
35470
|
if (options.disablePass) {
|
|
@@ -35073,7 +35509,7 @@ async function runScan(targetPath, options) {
|
|
|
35073
35509
|
if ((await stat(absPath)).isDirectory()) {
|
|
35074
35510
|
if (spin)
|
|
35075
35511
|
spin.text = `Running project analysis on ${files.length} file(s)...`;
|
|
35076
|
-
const projectScan = await scanProject(files, options.language, analyzeOpts);
|
|
35512
|
+
const projectScan = await scanProject(files, options.language, analyzeOpts, options.crossFileBudgetMs);
|
|
35077
35513
|
results = projectScan.results;
|
|
35078
35514
|
crossFileData = projectScan.crossFileData;
|
|
35079
35515
|
} else {
|
|
@@ -35112,7 +35548,7 @@ async function runScan(targetPath, options) {
|
|
|
35112
35548
|
results = applySuppressionsToResults(results, suppressions, process.cwd());
|
|
35113
35549
|
const afterCount = results.reduce((sum, r) => sum + r.vulnerabilities.length, 0);
|
|
35114
35550
|
if (!options.quiet && beforeCount !== afterCount) {
|
|
35115
|
-
console.
|
|
35551
|
+
console.error(colors.dim(`Suppressed ${beforeCount - afterCount} finding(s) via config`));
|
|
35116
35552
|
}
|
|
35117
35553
|
}
|
|
35118
35554
|
const severityOrder = ["low", "medium", "high", "critical"];
|
|
@@ -35195,7 +35631,7 @@ async function runScan(targetPath, options) {
|
|
|
35195
35631
|
if (options.output) {
|
|
35196
35632
|
const { writeFileSync } = await import("fs");
|
|
35197
35633
|
writeFileSync(options.output, output);
|
|
35198
|
-
console.
|
|
35634
|
+
console.error(colors.green(`Results written to ${options.output}`));
|
|
35199
35635
|
} else if (output.trim()) {
|
|
35200
35636
|
console.log(output);
|
|
35201
35637
|
}
|
|
@@ -35241,7 +35677,7 @@ async function runMetrics(targetPath, options) {
|
|
|
35241
35677
|
passOptions = converted.passOptions;
|
|
35242
35678
|
disabledPasses = converted.disabledPasses;
|
|
35243
35679
|
if (!options.quiet) {
|
|
35244
|
-
console.
|
|
35680
|
+
console.error(colors.dim(`Loaded config: ${options.profile || "cognium.config.json"}`));
|
|
35245
35681
|
}
|
|
35246
35682
|
}
|
|
35247
35683
|
try {
|
|
@@ -35349,7 +35785,7 @@ async function runMetrics(targetPath, options) {
|
|
|
35349
35785
|
if (options.output) {
|
|
35350
35786
|
const { writeFileSync } = await import("fs");
|
|
35351
35787
|
writeFileSync(options.output, output);
|
|
35352
|
-
console.
|
|
35788
|
+
console.error(colors.green(`Results written to ${options.output}`));
|
|
35353
35789
|
} else {
|
|
35354
35790
|
console.log(output);
|
|
35355
35791
|
}
|
|
@@ -35460,8 +35896,32 @@ async function handleInit() {
|
|
|
35460
35896
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
35461
35897
|
console.log(colors.green(`Created ${configPath}`));
|
|
35462
35898
|
}
|
|
35899
|
+
function applyLogLevel(cliValue) {
|
|
35900
|
+
const raw = typeof cliValue === "string" && cliValue.length > 0 ? cliValue : process.env.COGNIUM_LOG_LEVEL;
|
|
35901
|
+
if (!raw)
|
|
35902
|
+
return;
|
|
35903
|
+
const valid = ["trace", "debug", "info", "warn", "error", "fatal", "silent"];
|
|
35904
|
+
const normalized = raw.toLowerCase();
|
|
35905
|
+
if (!valid.includes(normalized)) {
|
|
35906
|
+
console.error(colors.yellow(`Warning: invalid log level "${raw}" (expected one of: ${valid.join(", ")}); ignoring`));
|
|
35907
|
+
return;
|
|
35908
|
+
}
|
|
35909
|
+
setLogLevel(normalized);
|
|
35910
|
+
}
|
|
35911
|
+
function parseCrossFileBudgetMs(raw) {
|
|
35912
|
+
if (raw === undefined || raw === true || raw === "")
|
|
35913
|
+
return;
|
|
35914
|
+
const s = String(raw);
|
|
35915
|
+
const n = Number.parseInt(s, 10);
|
|
35916
|
+
if (!Number.isFinite(n) || n < 0 || String(n) !== s) {
|
|
35917
|
+
console.error(colors.yellow(`Warning: invalid --cross-file-budget-ms "${s}" (expected non-negative integer in ms, 0 = unlimited); ignoring`));
|
|
35918
|
+
return;
|
|
35919
|
+
}
|
|
35920
|
+
return n;
|
|
35921
|
+
}
|
|
35463
35922
|
async function main() {
|
|
35464
35923
|
const { command, args: args2, options } = parseArgs(process.argv.slice(2));
|
|
35924
|
+
applyLogLevel(options["log-level"]);
|
|
35465
35925
|
if (options.help || options.h) {
|
|
35466
35926
|
showHelp();
|
|
35467
35927
|
return;
|
|
@@ -35481,7 +35941,7 @@ async function main() {
|
|
|
35481
35941
|
if (command === "metrics") {
|
|
35482
35942
|
if (args2.length === 0) {
|
|
35483
35943
|
console.error(colors.red("Error: metrics command requires a path argument"));
|
|
35484
|
-
console.
|
|
35944
|
+
console.error(`
|
|
35485
35945
|
Usage: cognium-dev metrics <path> [options]`);
|
|
35486
35946
|
process.exit(1);
|
|
35487
35947
|
}
|
|
@@ -35501,7 +35961,7 @@ Usage: cognium-dev metrics <path> [options]`);
|
|
|
35501
35961
|
if (command === "scan") {
|
|
35502
35962
|
if (args2.length === 0) {
|
|
35503
35963
|
console.error(colors.red("Error: scan command requires a path argument"));
|
|
35504
|
-
console.
|
|
35964
|
+
console.error(`
|
|
35505
35965
|
Usage: cognium-dev scan <path> [options]`);
|
|
35506
35966
|
process.exit(1);
|
|
35507
35967
|
}
|
|
@@ -35518,7 +35978,8 @@ Usage: cognium-dev scan <path> [options]`);
|
|
|
35518
35978
|
excludeTests: options["exclude-tests"] === true,
|
|
35519
35979
|
excludeCwe: options["exclude-cwe"],
|
|
35520
35980
|
profile: options.profile || options.p,
|
|
35521
|
-
disablePass: options["disable-pass"]
|
|
35981
|
+
disablePass: options["disable-pass"],
|
|
35982
|
+
crossFileBudgetMs: parseCrossFileBudgetMs(options["cross-file-budget-ms"])
|
|
35522
35983
|
};
|
|
35523
35984
|
await runScan(targetPath, scanOptions);
|
|
35524
35985
|
return;
|
|
@@ -35527,7 +35988,7 @@ Usage: cognium-dev scan <path> [options]`);
|
|
|
35527
35988
|
showHelp();
|
|
35528
35989
|
} else {
|
|
35529
35990
|
console.error(colors.red(`Error: Unknown command '${command}'`));
|
|
35530
|
-
console.
|
|
35991
|
+
console.error(`
|
|
35531
35992
|
Run 'cognium-dev --help' for usage information`);
|
|
35532
35993
|
process.exit(1);
|
|
35533
35994
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.89.2",
|
|
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.89.1"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/node": "^25.5.0",
|