cognium-dev 4.9.25 → 4.9.27
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 +273 -34
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import { readFileSync, existsSync, writeFileSync } from "fs";
|
|
|
7
7
|
import { stat as stat2, readdir as readdir2 } from "fs/promises";
|
|
8
8
|
import { join as join2, dirname as dirname3, extname, resolve as resolve2, relative as relative4, basename } from "path";
|
|
9
9
|
import { createRequire as createRequire2 } from "module";
|
|
10
|
+
import { getHeapStatistics } from "v8";
|
|
10
11
|
|
|
11
12
|
// ../../node_modules/web-tree-sitter/web-tree-sitter.js
|
|
12
13
|
var __defProp = Object.defineProperty;
|
|
@@ -3349,8 +3350,9 @@ async function initParser(options = {}) {
|
|
|
3349
3350
|
});
|
|
3350
3351
|
}
|
|
3351
3352
|
parserInitialized = true;
|
|
3353
|
+
})().finally(() => {
|
|
3352
3354
|
parserInitializing = null;
|
|
3353
|
-
})
|
|
3355
|
+
});
|
|
3354
3356
|
return parserInitializing;
|
|
3355
3357
|
}
|
|
3356
3358
|
async function loadLanguage(language, wasmPath) {
|
|
@@ -12702,6 +12704,7 @@ var DEFAULT_SINKS = [
|
|
|
12702
12704
|
{ method: "Deserialize", class: "ObjectStateFormatter", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
12703
12705
|
{ method: "Raw", class: "Html", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
12704
12706
|
{ method: "Write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
12707
|
+
{ method: "WriteAsync", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
12705
12708
|
{ method: "HtmlString", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
12706
12709
|
{ method: "MarkupString", class: "constructor", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
12707
12710
|
{ method: "Content", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -26727,6 +26730,52 @@ function findGoRequestBodySources(sourceCode, language) {
|
|
|
26727
26730
|
continue;
|
|
26728
26731
|
}
|
|
26729
26732
|
}
|
|
26733
|
+
const wrapRe = new RegExp(`^\\s*(?:var\\s+)?([A-Za-z_]\\w*)\\s*:?=\\s*bufio\\s*\\.\\s*New(?:Scanner|Reader|ReaderSize)\\s*\\(.*${bodyRef}`);
|
|
26734
|
+
const braceDelta = (s) => {
|
|
26735
|
+
const code = s.replace(/"(?:[^"\\]|\\.)*"|`[^`]*`|'(?:[^'\\]|\\.)*'/g, '""').replace(/\/\/.*$/, "");
|
|
26736
|
+
let d = 0;
|
|
26737
|
+
for (const ch of code) {
|
|
26738
|
+
if (ch === "{")
|
|
26739
|
+
d++;
|
|
26740
|
+
else if (ch === "}")
|
|
26741
|
+
d--;
|
|
26742
|
+
}
|
|
26743
|
+
return d;
|
|
26744
|
+
};
|
|
26745
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
26746
|
+
const w = wrapRe.exec(lines[i2]);
|
|
26747
|
+
if (!w)
|
|
26748
|
+
continue;
|
|
26749
|
+
const wrapper = w[1];
|
|
26750
|
+
if (wrapper === "_")
|
|
26751
|
+
continue;
|
|
26752
|
+
const readRe = new RegExp(`(?<![\\w.])${wrapper}\\s*\\.\\s*(Text|Bytes|ReadString|ReadBytes|ReadLine|ReadSlice)\\s*\\(`);
|
|
26753
|
+
const assignRe = new RegExp(`^\\s*(?:var\\s+)?([A-Za-z_][\\w\\s,]*?)\\s*:?=(?!=)\\s*(?:string\\s*\\(\\s*)?${wrapper}\\s*\\.\\s*(?:Text|Bytes|ReadString|ReadBytes|ReadLine|ReadSlice)\\s*\\(`);
|
|
26754
|
+
const rebindRe = new RegExp(`(?<![\\w.])${wrapper}\\s*:?=(?!=)`);
|
|
26755
|
+
let depth = 0;
|
|
26756
|
+
for (let j = i2 + 1;j < lines.length; j++) {
|
|
26757
|
+
depth += braceDelta(lines[j - 1]);
|
|
26758
|
+
if (depth < 0)
|
|
26759
|
+
break;
|
|
26760
|
+
const line = lines[j];
|
|
26761
|
+
if (/^\s*\/\//.test(line))
|
|
26762
|
+
continue;
|
|
26763
|
+
if (rebindRe.test(line))
|
|
26764
|
+
break;
|
|
26765
|
+
const rd = readRe.exec(line);
|
|
26766
|
+
if (!rd)
|
|
26767
|
+
continue;
|
|
26768
|
+
const as = assignRe.exec(line);
|
|
26769
|
+
let variable = "<inline>";
|
|
26770
|
+
if (as) {
|
|
26771
|
+
const bound = as[1].split(",").map((v) => v.trim()).find((v) => v !== "_" && v !== "err" && /^[A-Za-z_]\w*$/.test(v));
|
|
26772
|
+
if (!bound)
|
|
26773
|
+
continue;
|
|
26774
|
+
variable = bound;
|
|
26775
|
+
}
|
|
26776
|
+
push(variable, j + 1, `bufio ${wrapper}.${rd[1]}() over req.Body`);
|
|
26777
|
+
}
|
|
26778
|
+
}
|
|
26730
26779
|
return sources;
|
|
26731
26780
|
}
|
|
26732
26781
|
function findCSharpRequestSources(sourceCode, language) {
|
|
@@ -26736,24 +26785,101 @@ function findCSharpRequestSources(sourceCode, language) {
|
|
|
26736
26785
|
const lines = sourceCode.split(`
|
|
26737
26786
|
`);
|
|
26738
26787
|
const assignRe = /^\s*(?:await\s+)?(?:using\s*\(?\s*)?(?:var\s+|[A-Za-z_][\w.<>\[\]]*\s+)?([A-Za-z_]\w*)\s*=\s*(.+?);?\s*$/;
|
|
26739
|
-
const requestReadRe = /\bRequest\s*\.\s*(?:Query|Form|Headers|Cookies|QueryString|RouteValues|Body|Files|Params)\b/;
|
|
26740
26788
|
const consoleReadRe = /\bConsole\s*\.\s*ReadLine\s*\(/;
|
|
26741
26789
|
const envReadRe = /\bEnvironment\s*\.\s*GetEnvironmentVariables?\s*(?:\(|\[)/;
|
|
26742
26790
|
const formFileParams = new Set;
|
|
26743
26791
|
const formFileDeclRe = /\bIFormFile\s+([A-Za-z_]\w*)/g;
|
|
26792
|
+
const httpRequestReceivers = new Set(["Request"]);
|
|
26793
|
+
const httpRequestDeclRe = /\b(?:HttpRequest|HttpRequestBase)\??\s+([A-Za-z_]\w*)/g;
|
|
26744
26794
|
for (const line of lines) {
|
|
26745
|
-
const
|
|
26795
|
+
const formRe = new RegExp(formFileDeclRe.source, "g");
|
|
26746
26796
|
let d;
|
|
26747
|
-
while ((d =
|
|
26797
|
+
while ((d = formRe.exec(line)) !== null)
|
|
26748
26798
|
formFileParams.add(d[1]);
|
|
26799
|
+
const reqRe = new RegExp(httpRequestDeclRe.source, "g");
|
|
26800
|
+
while ((d = reqRe.exec(line)) !== null)
|
|
26801
|
+
httpRequestReceivers.add(d[1]);
|
|
26749
26802
|
}
|
|
26750
26803
|
const formFileReadRe = formFileParams.size > 0 ? new RegExp(`\\b(?:${[...formFileParams].join("|")})\\s*\\.\\s*(?:FileName|ContentType)\\b`) : null;
|
|
26804
|
+
const requestReadRe = new RegExp(`\\b(?:${[...httpRequestReceivers].join("|")})\\s*\\.\\s*(?:Query|Form|Headers|Cookies|QueryString|RouteValues|Body|Files|Params)\\b`);
|
|
26805
|
+
const classify = (text) => requestReadRe.test(text) ? "http_param" : formFileReadRe?.test(text) ? "http_param" : consoleReadRe.test(text) ? "io_input" : envReadRe.test(text) ? "env_input" : null;
|
|
26806
|
+
const inlineReads = [
|
|
26807
|
+
[requestReadRe, "http_param"],
|
|
26808
|
+
[formFileReadRe, "http_param"],
|
|
26809
|
+
[consoleReadRe, "io_input"],
|
|
26810
|
+
[envReadRe, "env_input"]
|
|
26811
|
+
];
|
|
26812
|
+
const inlineRead = (text) => {
|
|
26813
|
+
const argsStart = text.indexOf("(");
|
|
26814
|
+
for (const [re, type] of inlineReads) {
|
|
26815
|
+
if (!re)
|
|
26816
|
+
continue;
|
|
26817
|
+
const g = new RegExp(re.source, "g");
|
|
26818
|
+
let hit;
|
|
26819
|
+
while ((hit = g.exec(text)) !== null) {
|
|
26820
|
+
if (hit.index > argsStart) {
|
|
26821
|
+
return { type, read: hit[0].replace(/\s+/g, "").replace(/[([]$/, "") };
|
|
26822
|
+
}
|
|
26823
|
+
}
|
|
26824
|
+
}
|
|
26825
|
+
return null;
|
|
26826
|
+
};
|
|
26827
|
+
const exprStmtCallRe = /^\s*(?:await\s+)?[A-Za-z_][\w.]*(?:<[^>]*>)?\s*\(.*\)\s*;\s*$/;
|
|
26828
|
+
const nonCallKeywordRe = /^\s*(?:await\s+)?(?:if|while|for|foreach|switch|using|lock|return|throw|catch|when|nameof|typeof|sizeof|default)\b/;
|
|
26829
|
+
const embeddedAssignRe = /(?:^|[{;])\s*(?:await\s+)?(?:using\s*\(?\s*)?(?:var\s+|[A-Za-z_][\w.<>\[\]]*\s+)?([A-Za-z_]\w*)\s*=(?!=)\s*([^;{}]+)/;
|
|
26751
26830
|
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
26752
26831
|
const m = assignRe.exec(lines[i2]);
|
|
26753
|
-
if (!m)
|
|
26832
|
+
if (!m) {
|
|
26833
|
+
const stmt = lines[i2];
|
|
26834
|
+
if (stmt.includes("{") && !/^\s*(?:\/\/|\/\*|\*)/.test(stmt)) {
|
|
26835
|
+
const emb = new RegExp(embeddedAssignRe.source, "g");
|
|
26836
|
+
let em;
|
|
26837
|
+
let seeded = false;
|
|
26838
|
+
while ((em = emb.exec(stmt)) !== null) {
|
|
26839
|
+
const varName2 = em[1];
|
|
26840
|
+
const rhs2 = em[2];
|
|
26841
|
+
const type3 = classify(rhs2);
|
|
26842
|
+
if (!type3)
|
|
26843
|
+
continue;
|
|
26844
|
+
const lineNumber3 = i2 + 1;
|
|
26845
|
+
if (sources.some((s) => s.line === lineNumber3 && s.variable === varName2))
|
|
26846
|
+
continue;
|
|
26847
|
+
sources.push({
|
|
26848
|
+
type: type3,
|
|
26849
|
+
location: `${varName2} = ${rhs2.trim().substring(0, 50)}${rhs2.length > 50 ? "..." : ""}`,
|
|
26850
|
+
severity: "high",
|
|
26851
|
+
line: lineNumber3,
|
|
26852
|
+
confidence: 1,
|
|
26853
|
+
variable: varName2
|
|
26854
|
+
});
|
|
26855
|
+
seeded = true;
|
|
26856
|
+
}
|
|
26857
|
+
if (seeded)
|
|
26858
|
+
continue;
|
|
26859
|
+
}
|
|
26860
|
+
if (!exprStmtCallRe.test(stmt) || nonCallKeywordRe.test(stmt))
|
|
26861
|
+
continue;
|
|
26862
|
+
const hit = inlineRead(stmt);
|
|
26863
|
+
if (!hit)
|
|
26864
|
+
continue;
|
|
26865
|
+
const { type: type2, read } = hit;
|
|
26866
|
+
const lineNumber2 = i2 + 1;
|
|
26867
|
+
if (sources.some((s) => s.line === lineNumber2))
|
|
26868
|
+
continue;
|
|
26869
|
+
const code = stmt.trim();
|
|
26870
|
+
sources.push({
|
|
26871
|
+
type: type2,
|
|
26872
|
+
location: `${read} in ${code.substring(0, 50)}${code.length > 50 ? "..." : ""}`,
|
|
26873
|
+
severity: "high",
|
|
26874
|
+
line: lineNumber2,
|
|
26875
|
+
confidence: 1,
|
|
26876
|
+
variable: "<inline>",
|
|
26877
|
+
code
|
|
26878
|
+
});
|
|
26754
26879
|
continue;
|
|
26880
|
+
}
|
|
26755
26881
|
const [, varName, rhs] = m;
|
|
26756
|
-
const type =
|
|
26882
|
+
const type = classify(rhs);
|
|
26757
26883
|
if (!type)
|
|
26758
26884
|
continue;
|
|
26759
26885
|
const lineNumber = i2 + 1;
|
|
@@ -37425,6 +37551,18 @@ function maskStringLiteralsForTokenScan(expr) {
|
|
|
37425
37551
|
}
|
|
37426
37552
|
function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachableLines, tainted, code, language, types) {
|
|
37427
37553
|
const flows = [];
|
|
37554
|
+
const goFnRanges = language === "go" && types ? types.flatMap((t) => t.methods.map((m) => [m.start_line, m.end_line])) : [];
|
|
37555
|
+
const goFnStart = (line) => {
|
|
37556
|
+
let best = -1;
|
|
37557
|
+
let bestSpan = -1;
|
|
37558
|
+
for (const [start2, end] of goFnRanges) {
|
|
37559
|
+
if (line >= start2 && line <= end && end - start2 > bestSpan) {
|
|
37560
|
+
best = start2;
|
|
37561
|
+
bestSpan = end - start2;
|
|
37562
|
+
}
|
|
37563
|
+
}
|
|
37564
|
+
return best;
|
|
37565
|
+
};
|
|
37428
37566
|
const sourcesWithVar = sources.filter((s) => typeof s.variable === "string" && s.variable.length > 0);
|
|
37429
37567
|
const aliasSanitizedFor = new Map;
|
|
37430
37568
|
if (language === "python" && typeof code === "string") {
|
|
@@ -37833,6 +37971,12 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
37833
37971
|
if (source.in_method && call.in_method && source.in_method !== call.in_method) {
|
|
37834
37972
|
continue;
|
|
37835
37973
|
}
|
|
37974
|
+
if (goFnRanges.length > 0) {
|
|
37975
|
+
const srcFn = goFnStart(source.line);
|
|
37976
|
+
const sinkFn = goFnStart(sink.line);
|
|
37977
|
+
if (srcFn !== -1 && sinkFn !== -1 && srcFn !== sinkFn)
|
|
37978
|
+
continue;
|
|
37979
|
+
}
|
|
37836
37980
|
if (complex) {
|
|
37837
37981
|
const re = reCache.get(source.variable);
|
|
37838
37982
|
if (!re || !re.test(expr))
|
|
@@ -49025,7 +49169,30 @@ async function analyzeProject(files, options = {}) {
|
|
|
49025
49169
|
const perFileBudgetMs = options.perFileBudgetMs ?? 0;
|
|
49026
49170
|
const perFileStart = Date.now();
|
|
49027
49171
|
let perFileBudgetExceeded = false;
|
|
49172
|
+
const maxProjectSourceChars = options.maxProjectSourceChars ?? 0;
|
|
49173
|
+
const maxProjectFiles = options.maxProjectFiles ?? 0;
|
|
49174
|
+
let projectSourceChars = 0;
|
|
49175
|
+
let projectSizeBudgetExceeded = false;
|
|
49028
49176
|
for (const { code, filePath, language } of files) {
|
|
49177
|
+
if (maxProjectFiles > 0 && fileAnalyses.length >= maxProjectFiles) {
|
|
49178
|
+
projectSizeBudgetExceeded = true;
|
|
49179
|
+
logger.warn("analyzeProject: file-count ceiling reached, skipping remaining files", {
|
|
49180
|
+
analysed: fileAnalyses.length,
|
|
49181
|
+
total: files.length,
|
|
49182
|
+
maxProjectFiles
|
|
49183
|
+
});
|
|
49184
|
+
break;
|
|
49185
|
+
}
|
|
49186
|
+
if (maxProjectSourceChars > 0 && projectSourceChars + code.length > maxProjectSourceChars) {
|
|
49187
|
+
projectSizeBudgetExceeded = true;
|
|
49188
|
+
logger.warn("analyzeProject: source-size ceiling reached, skipping remaining files", {
|
|
49189
|
+
analysed: fileAnalyses.length,
|
|
49190
|
+
total: files.length,
|
|
49191
|
+
sourceChars: projectSourceChars,
|
|
49192
|
+
maxProjectSourceChars
|
|
49193
|
+
});
|
|
49194
|
+
break;
|
|
49195
|
+
}
|
|
49029
49196
|
if (perFileBudgetMs > 0 && Date.now() - perFileStart > perFileBudgetMs) {
|
|
49030
49197
|
perFileBudgetExceeded = true;
|
|
49031
49198
|
logger.warn("analyzeProject: per-file budget exceeded, skipping remaining files", {
|
|
@@ -49040,6 +49207,7 @@ async function analyzeProject(files, options = {}) {
|
|
|
49040
49207
|
projectGraph.addFile(filePath, new CodeGraph(ir));
|
|
49041
49208
|
sourceLinesByFile.set(filePath, code.split(`
|
|
49042
49209
|
`));
|
|
49210
|
+
projectSourceChars += code.length;
|
|
49043
49211
|
}
|
|
49044
49212
|
const crossFileBudgetMs = options.crossFileBudgetMs ?? 300000;
|
|
49045
49213
|
const crossFileResult = new CrossFilePass().run(projectGraph, sourceLinesByFile, { budgetMs: crossFileBudgetMs });
|
|
@@ -49093,6 +49261,9 @@ async function analyzeProject(files, options = {}) {
|
|
|
49093
49261
|
if (perFileBudgetExceeded) {
|
|
49094
49262
|
projectAnalysis.per_file_budget_exceeded = true;
|
|
49095
49263
|
}
|
|
49264
|
+
if (projectSizeBudgetExceeded) {
|
|
49265
|
+
projectAnalysis.project_size_budget_exceeded = true;
|
|
49266
|
+
}
|
|
49096
49267
|
return projectAnalysis;
|
|
49097
49268
|
}
|
|
49098
49269
|
function deriveProjectName(paths) {
|
|
@@ -49945,18 +50116,24 @@ async function enumerate(dir, out2) {
|
|
|
49945
50116
|
}
|
|
49946
50117
|
}
|
|
49947
50118
|
}
|
|
49948
|
-
function
|
|
49949
|
-
|
|
49950
|
-
let bestLen = -1;
|
|
50119
|
+
function createOwnerIndex(modules) {
|
|
50120
|
+
const byRoot = new Map;
|
|
49951
50121
|
for (const m of modules) {
|
|
49952
|
-
if (
|
|
49953
|
-
|
|
49954
|
-
|
|
49955
|
-
|
|
49956
|
-
|
|
50122
|
+
if (!byRoot.has(m.root))
|
|
50123
|
+
byRoot.set(m.root, m);
|
|
50124
|
+
}
|
|
50125
|
+
return (file) => {
|
|
50126
|
+
let p = file;
|
|
50127
|
+
for (;; ) {
|
|
50128
|
+
const hit = byRoot.get(p);
|
|
50129
|
+
if (hit)
|
|
50130
|
+
return hit;
|
|
50131
|
+
const slash = p.lastIndexOf("/");
|
|
50132
|
+
if (slash <= 0)
|
|
50133
|
+
return;
|
|
50134
|
+
p = p.slice(0, slash);
|
|
49957
50135
|
}
|
|
49958
|
-
}
|
|
49959
|
-
return best;
|
|
50136
|
+
};
|
|
49960
50137
|
}
|
|
49961
50138
|
|
|
49962
50139
|
// ../project-profile-detect/dist/publication-detect.js
|
|
@@ -50115,6 +50292,7 @@ async function detectProjectProfiles(scanRoot, options = {}) {
|
|
|
50115
50292
|
}
|
|
50116
50293
|
const profileByFile = new Map;
|
|
50117
50294
|
const unknownFiles = [];
|
|
50295
|
+
const ownerIndex = createOwnerIndex(modules);
|
|
50118
50296
|
for (const file of files) {
|
|
50119
50297
|
const rel = relative3(scanRoot, file);
|
|
50120
50298
|
const ov = applyOverrides(rel, compiledOverrides);
|
|
@@ -50128,7 +50306,7 @@ async function detectProjectProfiles(scanRoot, options = {}) {
|
|
|
50128
50306
|
profileByFile.set(file, `${shape2}/${env2}`);
|
|
50129
50307
|
continue;
|
|
50130
50308
|
}
|
|
50131
|
-
const owner =
|
|
50309
|
+
const owner = ownerIndex(file);
|
|
50132
50310
|
if (!owner) {
|
|
50133
50311
|
profileByFile.set(file, "unknown");
|
|
50134
50312
|
unknownFiles.push(file);
|
|
@@ -50179,7 +50357,7 @@ var colors = {
|
|
|
50179
50357
|
};
|
|
50180
50358
|
|
|
50181
50359
|
// src/version.ts
|
|
50182
|
-
var version = "4.9.
|
|
50360
|
+
var version = "4.9.27";
|
|
50183
50361
|
|
|
50184
50362
|
// src/formatters.ts
|
|
50185
50363
|
var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
|
|
@@ -50577,6 +50755,12 @@ function formatResults(results, verbose, crossFileData, profileSummary) {
|
|
|
50577
50755
|
lines.push("");
|
|
50578
50756
|
lines.push(formatCrossFilePaths(crossFileData.taintPaths));
|
|
50579
50757
|
}
|
|
50758
|
+
if (crossFileData?.projectSizeBudgetExceeded) {
|
|
50759
|
+
lines.push("");
|
|
50760
|
+
lines.push(colors.yellow(`⚠ Project size ceiling reached — analysed ${crossFileData.filesAnalysed ?? "?"} of ${crossFileData.filesTotal ?? "?"} files.`));
|
|
50761
|
+
lines.push(colors.yellow(" Results cover the analysed files only; this is NOT a clean scan of the project."));
|
|
50762
|
+
lines.push(colors.yellow(" Raise or disable the cap with --max-project-source-chars <n> (0 = unlimited)."));
|
|
50763
|
+
}
|
|
50580
50764
|
if (crossFileData?.budgetExceeded) {
|
|
50581
50765
|
lines.push("");
|
|
50582
50766
|
lines.push(colors.yellow("⚠ Cross-file budget exceeded — some cross-file taint paths may be missing."));
|
|
@@ -50597,6 +50781,7 @@ function formatJSON(results, crossFileData, profileSummary) {
|
|
|
50597
50781
|
cross_file_taint_paths: crossFileData?.taintPaths ?? [],
|
|
50598
50782
|
cross_file_calls: crossFileData?.crossFileCalls ?? [],
|
|
50599
50783
|
cross_file_budget_exceeded: crossFileData?.budgetExceeded ?? false,
|
|
50784
|
+
project_size_budget_exceeded: crossFileData?.projectSizeBudgetExceeded ?? false,
|
|
50600
50785
|
...profileSummary ? { project_profile: profileSummary } : {},
|
|
50601
50786
|
summary: {
|
|
50602
50787
|
filesScanned: results.length,
|
|
@@ -50724,8 +50909,8 @@ function generateSarifResults(results, crossFileData) {
|
|
|
50724
50909
|
|
|
50725
50910
|
// src/build-info.ts
|
|
50726
50911
|
var buildInfo = {
|
|
50727
|
-
gitSha: "
|
|
50728
|
-
builtAt: "2026-09-
|
|
50912
|
+
gitSha: "39d07d9",
|
|
50913
|
+
builtAt: "2026-09-29T23:22:05.194Z"
|
|
50729
50914
|
};
|
|
50730
50915
|
|
|
50731
50916
|
// src/utils/args.ts
|
|
@@ -50815,6 +51000,17 @@ SCAN OPTIONS:
|
|
|
50815
51000
|
On exceed: partial taint paths kept, remaining
|
|
50816
51001
|
cross-file phases skipped, cross_file_budget_exceeded
|
|
50817
51002
|
surfaced in output (text warning / JSON / SARIF field).
|
|
51003
|
+
--max-project-source-chars <n>
|
|
51004
|
+
Cap the per-file phase by total source characters
|
|
51005
|
+
[default: scaled to this process's V8 heap limit
|
|
51006
|
+
(~7000 chars per MB, so ~29M at Node's default
|
|
51007
|
+
4 GB heap) — 0 = unlimited. Raise it together
|
|
51008
|
+
with --max-old-space-size, not on its own.]
|
|
51009
|
+
A project large enough to exhaust the V8 heap would
|
|
51010
|
+
otherwise abort the process with no results at all.
|
|
51011
|
+
On exceed: remaining files skipped, results kept for
|
|
51012
|
+
the analysed subset, project_size_budget_exceeded
|
|
51013
|
+
surfaced in output (text warning / JSON field).
|
|
50818
51014
|
|
|
50819
51015
|
METRICS OPTIONS:
|
|
50820
51016
|
-l, --language <lang> Analyze only files for language (bash|csharp|go|html|java|javascript|typescript|python|rust)
|
|
@@ -51233,12 +51429,35 @@ async function scanFile(filePath, language, analyzeOpts) {
|
|
|
51233
51429
|
};
|
|
51234
51430
|
}
|
|
51235
51431
|
}
|
|
51236
|
-
|
|
51237
|
-
|
|
51238
|
-
|
|
51239
|
-
|
|
51240
|
-
|
|
51241
|
-
|
|
51432
|
+
var CHARS_PER_MB_OF_HEAP = 7000;
|
|
51433
|
+
function defaultMaxProjectSourceChars() {
|
|
51434
|
+
try {
|
|
51435
|
+
const heapMb = getHeapStatistics().heap_size_limit / (1024 * 1024);
|
|
51436
|
+
if (!Number.isFinite(heapMb) || heapMb <= 0)
|
|
51437
|
+
return 32000000;
|
|
51438
|
+
return Math.floor(heapMb * CHARS_PER_MB_OF_HEAP);
|
|
51439
|
+
} catch {
|
|
51440
|
+
return 32000000;
|
|
51441
|
+
}
|
|
51442
|
+
}
|
|
51443
|
+
async function scanProject(files, language, analyzeOpts, crossFileBudgetMs, maxProjectSourceChars) {
|
|
51444
|
+
const sourceCap = maxProjectSourceChars ?? defaultMaxProjectSourceChars();
|
|
51445
|
+
const filesWithCode = [];
|
|
51446
|
+
let sourceChars = 0;
|
|
51447
|
+
let readTruncated = false;
|
|
51448
|
+
for (const f of files) {
|
|
51449
|
+
const code = readFileSync(f, "utf-8");
|
|
51450
|
+
if (sourceCap > 0 && sourceChars + code.length > sourceCap && filesWithCode.length > 0) {
|
|
51451
|
+
readTruncated = true;
|
|
51452
|
+
break;
|
|
51453
|
+
}
|
|
51454
|
+
sourceChars += code.length;
|
|
51455
|
+
filesWithCode.push({
|
|
51456
|
+
code,
|
|
51457
|
+
filePath: f,
|
|
51458
|
+
language: language || detectLanguage7(f)
|
|
51459
|
+
});
|
|
51460
|
+
}
|
|
51242
51461
|
const projectResult = await analyzeProject(filesWithCode, {
|
|
51243
51462
|
passOptions: analyzeOpts?.passOptions,
|
|
51244
51463
|
disabledPasses: analyzeOpts?.disabledPasses,
|
|
@@ -51274,7 +51493,10 @@ async function scanProject(files, language, analyzeOpts, crossFileBudgetMs) {
|
|
|
51274
51493
|
crossFileData: {
|
|
51275
51494
|
taintPaths: projectResult.taint_paths,
|
|
51276
51495
|
crossFileCalls: projectResult.cross_file_calls,
|
|
51277
|
-
budgetExceeded: projectResult.cross_file_budget_exceeded === true
|
|
51496
|
+
budgetExceeded: projectResult.cross_file_budget_exceeded === true,
|
|
51497
|
+
projectSizeBudgetExceeded: readTruncated || projectResult.project_size_budget_exceeded === true,
|
|
51498
|
+
filesAnalysed: projectResult.files.length,
|
|
51499
|
+
filesTotal: files.length
|
|
51278
51500
|
}
|
|
51279
51501
|
};
|
|
51280
51502
|
}
|
|
@@ -51505,7 +51727,7 @@ async function runScan(targetPath, options) {
|
|
|
51505
51727
|
if ((await stat2(absPath)).isDirectory()) {
|
|
51506
51728
|
if (spin)
|
|
51507
51729
|
spin.text = `Running project analysis on ${files.length} file(s)...`;
|
|
51508
|
-
const projectScan = await scanProject(files, options.language, analyzeOpts, options.crossFileBudgetMs);
|
|
51730
|
+
const projectScan = await scanProject(files, options.language, analyzeOpts, options.crossFileBudgetMs, options.maxProjectSourceChars);
|
|
51509
51731
|
results = projectScan.results;
|
|
51510
51732
|
crossFileData = projectScan.crossFileData;
|
|
51511
51733
|
} else {
|
|
@@ -51537,8 +51759,10 @@ async function runScan(targetPath, options) {
|
|
|
51537
51759
|
spin.text = `Scanning... (${processed}/${files.length})`;
|
|
51538
51760
|
}
|
|
51539
51761
|
}
|
|
51540
|
-
if (spin)
|
|
51541
|
-
|
|
51762
|
+
if (spin) {
|
|
51763
|
+
const analysed = crossFileData?.projectSizeBudgetExceeded ? crossFileData.filesAnalysed ?? files.length : files.length;
|
|
51764
|
+
spin.succeed(analysed === files.length ? `Scanned ${files.length} file(s)` : `Scanned ${analysed} of ${files.length} file(s) — stopped at the project size ceiling`);
|
|
51765
|
+
}
|
|
51542
51766
|
if (suppressions.length > 0) {
|
|
51543
51767
|
const beforeCount = results.reduce((sum, r) => sum + r.vulnerabilities.length, 0) + (crossFileData?.taintPaths.length ?? 0);
|
|
51544
51768
|
results = applySuppressionsToResults(results, suppressions, process.cwd());
|
|
@@ -51614,7 +51838,8 @@ async function runScan(targetPath, options) {
|
|
|
51614
51838
|
const qualityCount = allVulns.filter((v) => v.category !== "security").length;
|
|
51615
51839
|
const errors = results.filter((r) => r.error).length;
|
|
51616
51840
|
const crossFilePaths = crossFileData?.taintPaths.length ?? 0;
|
|
51617
|
-
const
|
|
51841
|
+
const truncated = crossFileData?.projectSizeBudgetExceeded === true;
|
|
51842
|
+
const shouldOutput = totalVulns > 0 || crossFilePaths > 0 || errors > 0 || options.verbose || options.output || options.format !== "text" || truncated;
|
|
51618
51843
|
const profileSummary = buildProfileSummary(absPath, detectedModules, resolvedProfiles);
|
|
51619
51844
|
if (resolvedProfiles) {
|
|
51620
51845
|
for (const r of results) {
|
|
@@ -51646,15 +51871,17 @@ async function runScan(targetPath, options) {
|
|
|
51646
51871
|
}
|
|
51647
51872
|
if (!options.quiet && options.format === "text") {
|
|
51648
51873
|
console.log();
|
|
51874
|
+
const scanned = crossFileData?.projectSizeBudgetExceeded ? crossFileData.filesAnalysed ?? files.length : files.length;
|
|
51875
|
+
const scannedLabel = crossFileData?.projectSizeBudgetExceeded ? `${scanned} of ${crossFileData.filesTotal ?? files.length} file(s) analysed` : `${scanned} file(s)`;
|
|
51649
51876
|
if (securityCount > 0) {
|
|
51650
|
-
console.log(colors.red(`Found ${securityCount} security finding(s) in ${
|
|
51877
|
+
console.log(colors.red(`Found ${securityCount} security finding(s) in ${scannedLabel}`));
|
|
51651
51878
|
}
|
|
51652
51879
|
if (qualityCount > 0) {
|
|
51653
51880
|
const label = securityCount > 0 ? "Also found" : "Found";
|
|
51654
|
-
console.log(colors.yellow(`${label} ${qualityCount} code quality finding(s) in ${
|
|
51881
|
+
console.log(colors.yellow(`${label} ${qualityCount} code quality finding(s) in ${scannedLabel}`));
|
|
51655
51882
|
}
|
|
51656
51883
|
if (securityCount === 0 && qualityCount === 0 && options.verbose) {
|
|
51657
|
-
console.log(colors.green(`No findings in ${
|
|
51884
|
+
console.log(colors.green(`No findings in ${scannedLabel}`));
|
|
51658
51885
|
}
|
|
51659
51886
|
if (errors > 0) {
|
|
51660
51887
|
console.log(colors.yellow(`${errors} file(s) had errors during analysis`));
|
|
@@ -51948,6 +52175,17 @@ function parseCrossFileBudgetMs(raw) {
|
|
|
51948
52175
|
}
|
|
51949
52176
|
return n;
|
|
51950
52177
|
}
|
|
52178
|
+
function parseMaxProjectSourceChars(raw) {
|
|
52179
|
+
if (raw === undefined || raw === true || raw === "")
|
|
52180
|
+
return;
|
|
52181
|
+
const s = String(raw);
|
|
52182
|
+
const n = Number.parseInt(s, 10);
|
|
52183
|
+
if (!Number.isFinite(n) || n < 0 || String(n) !== s) {
|
|
52184
|
+
console.error(colors.yellow(`Warning: invalid --max-project-source-chars "${s}" (expected non-negative integer, 0 = unlimited); ignoring`));
|
|
52185
|
+
return;
|
|
52186
|
+
}
|
|
52187
|
+
return n;
|
|
52188
|
+
}
|
|
51951
52189
|
var SBOM_MANIFESTS = {
|
|
51952
52190
|
"package.json": parseNpmDependencies,
|
|
51953
52191
|
"package-lock.json": parseNpmLockDependencies,
|
|
@@ -52145,6 +52383,7 @@ Usage: cognium-dev scan <path> [options]`);
|
|
|
52145
52383
|
profile: options.profile || options.p,
|
|
52146
52384
|
disablePass: options["disable-pass"],
|
|
52147
52385
|
crossFileBudgetMs: parseCrossFileBudgetMs(options["cross-file-budget-ms"]),
|
|
52386
|
+
maxProjectSourceChars: parseMaxProjectSourceChars(options["max-project-source-chars"]),
|
|
52148
52387
|
projectProfile: parseProjectProfileArg(options["project-profile"]),
|
|
52149
52388
|
noProjectProfile: options["no-project-profile"] === true,
|
|
52150
52389
|
projectProfileExplain: options["project-profile-explain"] === true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "4.9.
|
|
3
|
+
"version": "4.9.27",
|
|
4
4
|
"description": "Static Application Security Testing CLI for detecting security vulnerabilities via taint tracking",
|
|
5
5
|
"bin": {
|
|
6
6
|
"cognium-dev": "dist/cli.js"
|
|
@@ -64,11 +64,11 @@
|
|
|
64
64
|
"registry": "https://registry.npmjs.org/"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@cognium/project-profile-detect": "1.1.
|
|
68
|
-
"circle-ir": "4.9.
|
|
67
|
+
"@cognium/project-profile-detect": "1.1.2",
|
|
68
|
+
"circle-ir": "4.9.27"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
|
-
"@types/node": "^26.6.
|
|
71
|
+
"@types/node": "^26.6.2",
|
|
72
72
|
"ajv": "^8.17.1",
|
|
73
73
|
"ajv-draft-04": "^1.0.0",
|
|
74
74
|
"bun-types": "^1.4.2",
|