cognium-dev 3.86.0 → 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 +456 -46
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -18481,11 +18481,14 @@ var LOG_LEVELS = {
|
|
|
18481
18481
|
fatal: 5,
|
|
18482
18482
|
silent: 6
|
|
18483
18483
|
};
|
|
18484
|
-
var currentLevel = "
|
|
18484
|
+
var currentLevel = "silent";
|
|
18485
18485
|
var customLogger = null;
|
|
18486
18486
|
function shouldLog(level) {
|
|
18487
18487
|
return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel];
|
|
18488
18488
|
}
|
|
18489
|
+
function setLogLevel(level) {
|
|
18490
|
+
currentLevel = level;
|
|
18491
|
+
}
|
|
18489
18492
|
var logger = {
|
|
18490
18493
|
trace: (msg, obj) => {
|
|
18491
18494
|
if (customLogger) {
|
|
@@ -18509,7 +18512,7 @@ var logger = {
|
|
|
18509
18512
|
return;
|
|
18510
18513
|
}
|
|
18511
18514
|
if (shouldLog("info"))
|
|
18512
|
-
console.
|
|
18515
|
+
console.error(obj ? `[INFO] ${msg} ${JSON.stringify(obj)}` : `[INFO] ${msg}`);
|
|
18513
18516
|
},
|
|
18514
18517
|
warn: (msg, obj) => {
|
|
18515
18518
|
if (customLogger) {
|
|
@@ -19377,7 +19380,88 @@ class SymbolTable {
|
|
|
19377
19380
|
}
|
|
19378
19381
|
}
|
|
19379
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
|
+
|
|
19380
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
|
+
}
|
|
19381
19465
|
symbolTable;
|
|
19382
19466
|
typeHierarchy;
|
|
19383
19467
|
fileIRs = new Map;
|
|
@@ -19832,7 +19916,8 @@ class CrossFileResolver {
|
|
|
19832
19916
|
}
|
|
19833
19917
|
if (!targetMethod)
|
|
19834
19918
|
continue;
|
|
19835
|
-
const
|
|
19919
|
+
const targetIdx = this.getFileIndex(targetIR);
|
|
19920
|
+
const sinksInMethod = targetIdx.sinksByMethod.get(targetMethod) ?? [];
|
|
19836
19921
|
if (sinksInMethod.length === 0)
|
|
19837
19922
|
continue;
|
|
19838
19923
|
for (const sink of sinksInMethod) {
|
|
@@ -19861,6 +19946,7 @@ class CrossFileResolver {
|
|
|
19861
19946
|
const seen = new Set;
|
|
19862
19947
|
const methodIndex = this.buildMethodIndex();
|
|
19863
19948
|
for (const [callerFile, callerIR] of this.fileIRs) {
|
|
19949
|
+
const callerIdx = this.getFileIndex(callerIR);
|
|
19864
19950
|
for (const type of callerIR.types) {
|
|
19865
19951
|
for (const method of type.methods) {
|
|
19866
19952
|
const tainted = new Map;
|
|
@@ -19878,7 +19964,7 @@ class CrossFileResolver {
|
|
|
19878
19964
|
hopChain: [{ file: callerFile, line: src.line, method: method.name, kind: "source" }]
|
|
19879
19965
|
});
|
|
19880
19966
|
}
|
|
19881
|
-
const callsInMethod =
|
|
19967
|
+
const callsInMethod = callerIdx.callsByMethod.get(method) ?? [];
|
|
19882
19968
|
for (const call of callsInMethod) {
|
|
19883
19969
|
const resolved = this.resolveCall(call, callerFile);
|
|
19884
19970
|
if (!resolved)
|
|
@@ -19892,7 +19978,7 @@ class CrossFileResolver {
|
|
|
19892
19978
|
const sourceLine = calleeSourceLine ?? call.location.line;
|
|
19893
19979
|
const sourceFile = callee.file;
|
|
19894
19980
|
const sourceType = callee.sourceType;
|
|
19895
|
-
const defsAtLine =
|
|
19981
|
+
const defsAtLine = (callerIdx.defsByLine.get(call.location.line) ?? []).filter((d) => d.kind === "local");
|
|
19896
19982
|
for (const def of defsAtLine) {
|
|
19897
19983
|
if (!def.variable)
|
|
19898
19984
|
continue;
|
|
@@ -19920,7 +20006,8 @@ class CrossFileResolver {
|
|
|
19920
20006
|
const calleeNode = methodIndex.get(resolved.targetMethod);
|
|
19921
20007
|
if (!calleeNode)
|
|
19922
20008
|
continue;
|
|
19923
|
-
const
|
|
20009
|
+
const calleeIdx = this.getFileIndex(calleeNode.ir);
|
|
20010
|
+
const sinksInCallee = calleeIdx.sinksByMethod.get(calleeNode.method) ?? [];
|
|
19924
20011
|
for (const sink of sinksInCallee) {
|
|
19925
20012
|
const key = `${matched.origin.file}:${matched.origin.line}→${callee.file}:${sink.line}`;
|
|
19926
20013
|
if (seen.has(key))
|
|
@@ -19951,9 +20038,9 @@ class CrossFileResolver {
|
|
|
19951
20038
|
}
|
|
19952
20039
|
}
|
|
19953
20040
|
if (tainted.size > 0) {
|
|
19954
|
-
const sinksInCaller =
|
|
20041
|
+
const sinksInCaller = callerIdx.sinksByMethod.get(method) ?? [];
|
|
19955
20042
|
for (const sink of sinksInCaller) {
|
|
19956
|
-
const callsAtSink =
|
|
20043
|
+
const callsAtSink = callerIdx.callsByLine.get(sink.line) ?? [];
|
|
19957
20044
|
for (const sinkCall of callsAtSink) {
|
|
19958
20045
|
for (const arg of sinkCall.arguments ?? []) {
|
|
19959
20046
|
const matched = this.matchTaintedArg(arg, tainted);
|
|
@@ -20000,6 +20087,7 @@ class CrossFileResolver {
|
|
|
20000
20087
|
const fieldExprRe = /^(\w+)\.(\w+)$/;
|
|
20001
20088
|
const methodIndex = this.buildMethodIndex();
|
|
20002
20089
|
for (const [callerFile, callerIR] of this.fileIRs) {
|
|
20090
|
+
const callerIdx = this.getFileIndex(callerIR);
|
|
20003
20091
|
for (const type of callerIR.types) {
|
|
20004
20092
|
const callerTypeFqn = callerIR.meta.package ? `${callerIR.meta.package}.${type.name}` : type.name;
|
|
20005
20093
|
for (const method of type.methods) {
|
|
@@ -20018,9 +20106,9 @@ class CrossFileResolver {
|
|
|
20018
20106
|
hopChain: [{ file: callerFile, line: src.line, method: method.name, kind: "source" }]
|
|
20019
20107
|
});
|
|
20020
20108
|
}
|
|
20021
|
-
const defsInMethod =
|
|
20109
|
+
const defsInMethod = (callerIdx.defsByMethod.get(method) ?? []).filter((d) => d.kind === "local" && !!d.variable);
|
|
20022
20110
|
for (const def of defsInMethod) {
|
|
20023
|
-
const usesAtLine =
|
|
20111
|
+
const usesAtLine = callerIdx.usesByLine.get(def.line) ?? [];
|
|
20024
20112
|
if (usesAtLine.length < 2)
|
|
20025
20113
|
continue;
|
|
20026
20114
|
let receiver = null;
|
|
@@ -20101,9 +20189,9 @@ class CrossFileResolver {
|
|
|
20101
20189
|
}
|
|
20102
20190
|
if (tainted.size === 0)
|
|
20103
20191
|
continue;
|
|
20104
|
-
const sinksInCaller =
|
|
20192
|
+
const sinksInCaller = callerIdx.sinksByMethod.get(method) ?? [];
|
|
20105
20193
|
for (const sink of sinksInCaller) {
|
|
20106
|
-
const callsAtSink =
|
|
20194
|
+
const callsAtSink = callerIdx.callsByLine.get(sink.line) ?? [];
|
|
20107
20195
|
for (const sinkCall of callsAtSink) {
|
|
20108
20196
|
for (const arg of sinkCall.arguments ?? []) {
|
|
20109
20197
|
const matched = this.matchTaintedArg(arg, tainted);
|
|
@@ -20136,7 +20224,7 @@ class CrossFileResolver {
|
|
|
20136
20224
|
}
|
|
20137
20225
|
}
|
|
20138
20226
|
}
|
|
20139
|
-
const callsInMethod =
|
|
20227
|
+
const callsInMethod = callerIdx.callsByMethod.get(method) ?? [];
|
|
20140
20228
|
for (const call of callsInMethod) {
|
|
20141
20229
|
const resolved = this.resolveCall(call, callerFile);
|
|
20142
20230
|
if (!resolved)
|
|
@@ -20153,7 +20241,8 @@ class CrossFileResolver {
|
|
|
20153
20241
|
const calleeNode = methodIndex.get(resolved.targetMethod);
|
|
20154
20242
|
if (!calleeNode)
|
|
20155
20243
|
continue;
|
|
20156
|
-
const
|
|
20244
|
+
const calleeIdx = this.getFileIndex(calleeNode.ir);
|
|
20245
|
+
const sinksInCallee = calleeIdx.sinksByMethod.get(calleeNode.method) ?? [];
|
|
20157
20246
|
for (const sink of sinksInCallee) {
|
|
20158
20247
|
const key = `fb:${matched.origin.file}:${matched.origin.line}→${callee.file}:${sink.line}`;
|
|
20159
20248
|
if (seen.has(key))
|
|
@@ -20395,9 +20484,20 @@ class AnalysisPipeline {
|
|
|
20395
20484
|
}
|
|
20396
20485
|
// ../circle-ir/dist/analysis/passes/cross-file-pass.js
|
|
20397
20486
|
class CrossFilePass {
|
|
20398
|
-
run(projectGraph, sourceLines) {
|
|
20487
|
+
run(projectGraph, sourceLines, options = {}) {
|
|
20399
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)");
|
|
20400
20496
|
const flows = resolver.findCrossFileTaintFlows();
|
|
20497
|
+
logger.info("cross-file: phase 1/4 done", {
|
|
20498
|
+
flows: flows.length,
|
|
20499
|
+
elapsedMs: Date.now() - phase1Start
|
|
20500
|
+
});
|
|
20401
20501
|
const taintPaths = flows.flatMap((flow, idx) => {
|
|
20402
20502
|
const srcLines = sourceLines.get(flow.sourceFile) ?? [];
|
|
20403
20503
|
const tgtLines = sourceLines.get(flow.targetFile) ?? [];
|
|
@@ -20443,11 +20543,59 @@ class CrossFilePass {
|
|
|
20443
20543
|
confidence: 0.7
|
|
20444
20544
|
}];
|
|
20445
20545
|
});
|
|
20446
|
-
|
|
20447
|
-
|
|
20448
|
-
|
|
20449
|
-
|
|
20450
|
-
|
|
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
|
+
}
|
|
20451
20599
|
for (let i2 = 0;i2 < ipPaths.length; i2++) {
|
|
20452
20600
|
const p = ipPaths[i2];
|
|
20453
20601
|
const sinkIR = projectGraph.getIR(p.sink.file);
|
|
@@ -20459,7 +20607,7 @@ class CrossFilePass {
|
|
|
20459
20607
|
const srcLines = sourceLines.get(p.source.file) ?? [];
|
|
20460
20608
|
const tgtLines = sourceLines.get(p.sink.file) ?? [];
|
|
20461
20609
|
const dupId = `${p.source.file}:${p.source.line}→${p.sink.file}:${p.sink.line}`;
|
|
20462
|
-
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)) {
|
|
20463
20611
|
continue;
|
|
20464
20612
|
}
|
|
20465
20613
|
taintPaths.push({
|
|
@@ -20519,7 +20667,16 @@ class CrossFilePass {
|
|
|
20519
20667
|
}
|
|
20520
20668
|
}
|
|
20521
20669
|
const typeHierarchy = projectGraph.typeHierarchy.toTypeHierarchyData();
|
|
20522
|
-
|
|
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;
|
|
20523
20680
|
}
|
|
20524
20681
|
}
|
|
20525
20682
|
function findCrossInstanceAliasingPaths(projectGraph, _sourceLines) {
|
|
@@ -24705,6 +24862,194 @@ function findTaintBridges2(result) {
|
|
|
24705
24862
|
return bridges;
|
|
24706
24863
|
}
|
|
24707
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
|
+
|
|
24708
25053
|
// ../circle-ir/dist/analysis/passes/interprocedural-pass.js
|
|
24709
25054
|
class InterproceduralPass {
|
|
24710
25055
|
name = "interprocedural";
|
|
@@ -24721,6 +25066,15 @@ class InterproceduralPass {
|
|
|
24721
25066
|
const additionalSinks = [];
|
|
24722
25067
|
const additionalFlows = [...taintProp.flows];
|
|
24723
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;
|
|
24724
25078
|
if (sinks.length > 0) {
|
|
24725
25079
|
const interProc = analyzeInterprocedural2(graph, sources, sinks, sanitizers, {
|
|
24726
25080
|
taintedVariables: constProp.tainted
|
|
@@ -24758,6 +25112,12 @@ class InterproceduralPass {
|
|
|
24758
25112
|
continue;
|
|
24759
25113
|
if (source.type === "interprocedural_param" && source.confidence < 0.6)
|
|
24760
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
|
+
}
|
|
24761
25121
|
if (additionalFlows.some((f) => f.source_line === source.line && f.sink_line === sink.line))
|
|
24762
25122
|
continue;
|
|
24763
25123
|
additionalFlows.push({
|
|
@@ -33967,7 +34327,8 @@ async function analyzeProject(files, options = {}) {
|
|
|
33967
34327
|
sourceLinesByFile.set(filePath, code.split(`
|
|
33968
34328
|
`));
|
|
33969
34329
|
}
|
|
33970
|
-
const
|
|
34330
|
+
const crossFileBudgetMs = options.crossFileBudgetMs ?? 300000;
|
|
34331
|
+
const crossFileResult = new CrossFilePass().run(projectGraph, sourceLinesByFile, { budgetMs: crossFileBudgetMs });
|
|
33971
34332
|
const disabledPasses = options.disabledPasses ?? [];
|
|
33972
34333
|
if (!disabledPasses.includes("security-headers")) {
|
|
33973
34334
|
const inheritedFindings = checkInheritedCorsHeaders(fileAnalyses, projectGraph.typeHierarchy, sourceLinesByFile);
|
|
@@ -33997,7 +34358,7 @@ async function analyzeProject(files, options = {}) {
|
|
|
33997
34358
|
total_loc: totalLoc,
|
|
33998
34359
|
analyzed_at: new Date().toISOString()
|
|
33999
34360
|
};
|
|
34000
|
-
|
|
34361
|
+
const projectAnalysis = {
|
|
34001
34362
|
meta,
|
|
34002
34363
|
files: fileAnalyses,
|
|
34003
34364
|
type_hierarchy: crossFileResult.typeHierarchy,
|
|
@@ -34005,6 +34366,10 @@ async function analyzeProject(files, options = {}) {
|
|
|
34005
34366
|
taint_paths: crossFileResult.taintPaths,
|
|
34006
34367
|
findings: []
|
|
34007
34368
|
};
|
|
34369
|
+
if (crossFileResult.budgetExceeded) {
|
|
34370
|
+
projectAnalysis.cross_file_budget_exceeded = true;
|
|
34371
|
+
}
|
|
34372
|
+
return projectAnalysis;
|
|
34008
34373
|
}
|
|
34009
34374
|
function deriveProjectName(paths) {
|
|
34010
34375
|
if (paths.length === 0)
|
|
@@ -34044,7 +34409,7 @@ var colors = {
|
|
|
34044
34409
|
};
|
|
34045
34410
|
|
|
34046
34411
|
// src/version.ts
|
|
34047
|
-
var version = "3.
|
|
34412
|
+
var version = "3.89.2";
|
|
34048
34413
|
|
|
34049
34414
|
// src/formatters.ts
|
|
34050
34415
|
var SINK_SEVERITY = {
|
|
@@ -34405,6 +34770,11 @@ function formatResults(results, verbose, crossFileData) {
|
|
|
34405
34770
|
lines.push("");
|
|
34406
34771
|
lines.push(formatCrossFilePaths(crossFileData.taintPaths));
|
|
34407
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
|
+
}
|
|
34408
34778
|
return lines.join(`
|
|
34409
34779
|
`);
|
|
34410
34780
|
}
|
|
@@ -34419,11 +34789,13 @@ function formatJSON(results, crossFileData) {
|
|
|
34419
34789
|
})),
|
|
34420
34790
|
cross_file_taint_paths: crossFileData?.taintPaths ?? [],
|
|
34421
34791
|
cross_file_calls: crossFileData?.crossFileCalls ?? [],
|
|
34792
|
+
cross_file_budget_exceeded: crossFileData?.budgetExceeded ?? false,
|
|
34422
34793
|
summary: {
|
|
34423
34794
|
filesScanned: results.length,
|
|
34424
34795
|
filesWithVulnerabilities: results.filter((r) => r.vulnerabilities.length > 0).length,
|
|
34425
34796
|
totalVulnerabilities: results.reduce((sum, r) => sum + r.vulnerabilities.length, 0),
|
|
34426
34797
|
crossFileTaintPaths: crossFileData?.taintPaths.length ?? 0,
|
|
34798
|
+
crossFileBudgetExceeded: crossFileData?.budgetExceeded ?? false,
|
|
34427
34799
|
errors: results.filter((r) => r.error).length
|
|
34428
34800
|
}
|
|
34429
34801
|
};
|
|
@@ -34619,6 +34991,13 @@ SCAN OPTIONS:
|
|
|
34619
34991
|
-o, --output <file> Write results to file
|
|
34620
34992
|
-q, --quiet Suppress progress output
|
|
34621
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).
|
|
34622
35001
|
|
|
34623
35002
|
METRICS OPTIONS:
|
|
34624
35003
|
-l, --language <lang> Analyze only files for language (bash|go|html|java|javascript|typescript|python|rust)
|
|
@@ -34641,6 +35020,10 @@ EXAMPLES:
|
|
|
34641
35020
|
cognium-dev scan . --exclude-cwe CWE-330,CWE-327
|
|
34642
35021
|
cognium-dev scan . --disable-pass naming-convention,todo-in-prod
|
|
34643
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)
|
|
34644
35027
|
cognium-dev metrics src/
|
|
34645
35028
|
cognium-dev metrics src/ --category complexity
|
|
34646
35029
|
cognium-dev metrics src/ --format json --profile custom-config.json
|
|
@@ -34670,10 +35053,10 @@ class Spinner {
|
|
|
34670
35053
|
enabled;
|
|
34671
35054
|
constructor(text) {
|
|
34672
35055
|
this._text = text;
|
|
34673
|
-
this.enabled = Boolean(process.
|
|
35056
|
+
this.enabled = Boolean(process.stderr.isTTY);
|
|
34674
35057
|
}
|
|
34675
35058
|
render(frame) {
|
|
34676
|
-
process.
|
|
35059
|
+
process.stderr.write(`\r\x1B[K${frame} ${this._text}`);
|
|
34677
35060
|
}
|
|
34678
35061
|
start() {
|
|
34679
35062
|
if (this.isSpinning)
|
|
@@ -34682,7 +35065,7 @@ class Spinner {
|
|
|
34682
35065
|
return this;
|
|
34683
35066
|
this.isSpinning = true;
|
|
34684
35067
|
this.frameIndex = 0;
|
|
34685
|
-
process.
|
|
35068
|
+
process.stderr.write("\x1B[?25l");
|
|
34686
35069
|
this.render(SPINNER_FRAMES[this.frameIndex]);
|
|
34687
35070
|
this.intervalId = setInterval(() => {
|
|
34688
35071
|
this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
|
|
@@ -34699,26 +35082,26 @@ class Spinner {
|
|
|
34699
35082
|
this.intervalId = undefined;
|
|
34700
35083
|
}
|
|
34701
35084
|
this.isSpinning = false;
|
|
34702
|
-
process.
|
|
34703
|
-
process.
|
|
35085
|
+
process.stderr.write("\r\x1B[K");
|
|
35086
|
+
process.stderr.write("\x1B[?25h");
|
|
34704
35087
|
return this;
|
|
34705
35088
|
}
|
|
34706
35089
|
succeed(text) {
|
|
34707
35090
|
this.stop();
|
|
34708
35091
|
const message = text || this._text;
|
|
34709
|
-
console.
|
|
35092
|
+
console.error(`\x1B[32m${CHECKMARK}\x1B[0m ${message}`);
|
|
34710
35093
|
return this;
|
|
34711
35094
|
}
|
|
34712
35095
|
fail(text) {
|
|
34713
35096
|
this.stop();
|
|
34714
35097
|
const message = text || this._text;
|
|
34715
|
-
console.
|
|
35098
|
+
console.error(`\x1B[31m${CROSS}\x1B[0m ${message}`);
|
|
34716
35099
|
return this;
|
|
34717
35100
|
}
|
|
34718
35101
|
warn(text) {
|
|
34719
35102
|
this.stop();
|
|
34720
35103
|
const message = text || this._text;
|
|
34721
|
-
console.
|
|
35104
|
+
console.error(`\x1B[33m${WARNING}\x1B[0m ${message}`);
|
|
34722
35105
|
return this;
|
|
34723
35106
|
}
|
|
34724
35107
|
set text(value) {
|
|
@@ -34948,7 +35331,7 @@ async function scanFile(filePath, language, analyzeOpts) {
|
|
|
34948
35331
|
};
|
|
34949
35332
|
}
|
|
34950
35333
|
}
|
|
34951
|
-
async function scanProject(files, language, analyzeOpts) {
|
|
35334
|
+
async function scanProject(files, language, analyzeOpts, crossFileBudgetMs) {
|
|
34952
35335
|
const filesWithCode = files.map((f) => ({
|
|
34953
35336
|
code: readFileSync(f, "utf-8"),
|
|
34954
35337
|
filePath: f,
|
|
@@ -34956,7 +35339,8 @@ async function scanProject(files, language, analyzeOpts) {
|
|
|
34956
35339
|
}));
|
|
34957
35340
|
const projectResult = await analyzeProject(filesWithCode, {
|
|
34958
35341
|
passOptions: analyzeOpts?.passOptions,
|
|
34959
|
-
disabledPasses: analyzeOpts?.disabledPasses
|
|
35342
|
+
disabledPasses: analyzeOpts?.disabledPasses,
|
|
35343
|
+
...crossFileBudgetMs !== undefined ? { crossFileBudgetMs } : {}
|
|
34960
35344
|
});
|
|
34961
35345
|
const results = projectResult.files.map(({ file, analysis }) => {
|
|
34962
35346
|
const vulnerabilities = (analysis.taint.flows || []).map((flow) => ({
|
|
@@ -34984,7 +35368,8 @@ async function scanProject(files, language, analyzeOpts) {
|
|
|
34984
35368
|
results,
|
|
34985
35369
|
crossFileData: {
|
|
34986
35370
|
taintPaths: projectResult.taint_paths,
|
|
34987
|
-
crossFileCalls: projectResult.cross_file_calls
|
|
35371
|
+
crossFileCalls: projectResult.cross_file_calls,
|
|
35372
|
+
budgetExceeded: projectResult.cross_file_budget_exceeded === true
|
|
34988
35373
|
}
|
|
34989
35374
|
};
|
|
34990
35375
|
}
|
|
@@ -35079,7 +35464,7 @@ async function runScan(targetPath, options) {
|
|
|
35079
35464
|
disabledPasses = converted.disabledPasses;
|
|
35080
35465
|
suppressions = config.suppressions ?? [];
|
|
35081
35466
|
if (!options.quiet) {
|
|
35082
|
-
console.
|
|
35467
|
+
console.error(colors.dim(`Loaded config: ${options.profile || "cognium.config.json"}`));
|
|
35083
35468
|
}
|
|
35084
35469
|
}
|
|
35085
35470
|
if (options.disablePass) {
|
|
@@ -35124,7 +35509,7 @@ async function runScan(targetPath, options) {
|
|
|
35124
35509
|
if ((await stat(absPath)).isDirectory()) {
|
|
35125
35510
|
if (spin)
|
|
35126
35511
|
spin.text = `Running project analysis on ${files.length} file(s)...`;
|
|
35127
|
-
const projectScan = await scanProject(files, options.language, analyzeOpts);
|
|
35512
|
+
const projectScan = await scanProject(files, options.language, analyzeOpts, options.crossFileBudgetMs);
|
|
35128
35513
|
results = projectScan.results;
|
|
35129
35514
|
crossFileData = projectScan.crossFileData;
|
|
35130
35515
|
} else {
|
|
@@ -35163,7 +35548,7 @@ async function runScan(targetPath, options) {
|
|
|
35163
35548
|
results = applySuppressionsToResults(results, suppressions, process.cwd());
|
|
35164
35549
|
const afterCount = results.reduce((sum, r) => sum + r.vulnerabilities.length, 0);
|
|
35165
35550
|
if (!options.quiet && beforeCount !== afterCount) {
|
|
35166
|
-
console.
|
|
35551
|
+
console.error(colors.dim(`Suppressed ${beforeCount - afterCount} finding(s) via config`));
|
|
35167
35552
|
}
|
|
35168
35553
|
}
|
|
35169
35554
|
const severityOrder = ["low", "medium", "high", "critical"];
|
|
@@ -35246,7 +35631,7 @@ async function runScan(targetPath, options) {
|
|
|
35246
35631
|
if (options.output) {
|
|
35247
35632
|
const { writeFileSync } = await import("fs");
|
|
35248
35633
|
writeFileSync(options.output, output);
|
|
35249
|
-
console.
|
|
35634
|
+
console.error(colors.green(`Results written to ${options.output}`));
|
|
35250
35635
|
} else if (output.trim()) {
|
|
35251
35636
|
console.log(output);
|
|
35252
35637
|
}
|
|
@@ -35292,7 +35677,7 @@ async function runMetrics(targetPath, options) {
|
|
|
35292
35677
|
passOptions = converted.passOptions;
|
|
35293
35678
|
disabledPasses = converted.disabledPasses;
|
|
35294
35679
|
if (!options.quiet) {
|
|
35295
|
-
console.
|
|
35680
|
+
console.error(colors.dim(`Loaded config: ${options.profile || "cognium.config.json"}`));
|
|
35296
35681
|
}
|
|
35297
35682
|
}
|
|
35298
35683
|
try {
|
|
@@ -35400,7 +35785,7 @@ async function runMetrics(targetPath, options) {
|
|
|
35400
35785
|
if (options.output) {
|
|
35401
35786
|
const { writeFileSync } = await import("fs");
|
|
35402
35787
|
writeFileSync(options.output, output);
|
|
35403
|
-
console.
|
|
35788
|
+
console.error(colors.green(`Results written to ${options.output}`));
|
|
35404
35789
|
} else {
|
|
35405
35790
|
console.log(output);
|
|
35406
35791
|
}
|
|
@@ -35511,8 +35896,32 @@ async function handleInit() {
|
|
|
35511
35896
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
35512
35897
|
console.log(colors.green(`Created ${configPath}`));
|
|
35513
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
|
+
}
|
|
35514
35922
|
async function main() {
|
|
35515
35923
|
const { command, args: args2, options } = parseArgs(process.argv.slice(2));
|
|
35924
|
+
applyLogLevel(options["log-level"]);
|
|
35516
35925
|
if (options.help || options.h) {
|
|
35517
35926
|
showHelp();
|
|
35518
35927
|
return;
|
|
@@ -35532,7 +35941,7 @@ async function main() {
|
|
|
35532
35941
|
if (command === "metrics") {
|
|
35533
35942
|
if (args2.length === 0) {
|
|
35534
35943
|
console.error(colors.red("Error: metrics command requires a path argument"));
|
|
35535
|
-
console.
|
|
35944
|
+
console.error(`
|
|
35536
35945
|
Usage: cognium-dev metrics <path> [options]`);
|
|
35537
35946
|
process.exit(1);
|
|
35538
35947
|
}
|
|
@@ -35552,7 +35961,7 @@ Usage: cognium-dev metrics <path> [options]`);
|
|
|
35552
35961
|
if (command === "scan") {
|
|
35553
35962
|
if (args2.length === 0) {
|
|
35554
35963
|
console.error(colors.red("Error: scan command requires a path argument"));
|
|
35555
|
-
console.
|
|
35964
|
+
console.error(`
|
|
35556
35965
|
Usage: cognium-dev scan <path> [options]`);
|
|
35557
35966
|
process.exit(1);
|
|
35558
35967
|
}
|
|
@@ -35569,7 +35978,8 @@ Usage: cognium-dev scan <path> [options]`);
|
|
|
35569
35978
|
excludeTests: options["exclude-tests"] === true,
|
|
35570
35979
|
excludeCwe: options["exclude-cwe"],
|
|
35571
35980
|
profile: options.profile || options.p,
|
|
35572
|
-
disablePass: options["disable-pass"]
|
|
35981
|
+
disablePass: options["disable-pass"],
|
|
35982
|
+
crossFileBudgetMs: parseCrossFileBudgetMs(options["cross-file-budget-ms"])
|
|
35573
35983
|
};
|
|
35574
35984
|
await runScan(targetPath, scanOptions);
|
|
35575
35985
|
return;
|
|
@@ -35578,7 +35988,7 @@ Usage: cognium-dev scan <path> [options]`);
|
|
|
35578
35988
|
showHelp();
|
|
35579
35989
|
} else {
|
|
35580
35990
|
console.error(colors.red(`Error: Unknown command '${command}'`));
|
|
35581
|
-
console.
|
|
35991
|
+
console.error(`
|
|
35582
35992
|
Run 'cognium-dev --help' for usage information`);
|
|
35583
35993
|
process.exit(1);
|
|
35584
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",
|