cognium-dev 4.9.23 → 4.9.25
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 +128 -28
- package/package.json +3 -5
package/dist/cli.js
CHANGED
|
@@ -16846,6 +16846,9 @@ function buildFileIndex(ir) {
|
|
|
16846
16846
|
}
|
|
16847
16847
|
return { callsByLine, defsByLine, usesByLine, callsByMethod, sinksByMethod, defsByMethod };
|
|
16848
16848
|
}
|
|
16849
|
+
function escapeRegExp(s) {
|
|
16850
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16851
|
+
}
|
|
16849
16852
|
|
|
16850
16853
|
class CrossFileResolver {
|
|
16851
16854
|
fileIndexes = new WeakMap;
|
|
@@ -17214,7 +17217,7 @@ class CrossFileResolver {
|
|
|
17214
17217
|
candidates.push(arg.variable);
|
|
17215
17218
|
if (arg.expression) {
|
|
17216
17219
|
for (const [name2] of paramNameToIndex) {
|
|
17217
|
-
const re = new RegExp(`\\b${name2}\\b`);
|
|
17220
|
+
const re = new RegExp(`\\b${escapeRegExp(name2)}\\b`);
|
|
17218
17221
|
if (re.test(arg.expression))
|
|
17219
17222
|
candidates.push(name2);
|
|
17220
17223
|
}
|
|
@@ -17298,7 +17301,7 @@ class CrossFileResolver {
|
|
|
17298
17301
|
}
|
|
17299
17302
|
if (arg.expression) {
|
|
17300
17303
|
for (const v of reachable.names) {
|
|
17301
|
-
if (new RegExp(`\\b${v}\\b`).test(arg.expression)) {
|
|
17304
|
+
if (new RegExp(`\\b${escapeRegExp(v)}\\b`).test(arg.expression)) {
|
|
17302
17305
|
matchedName = v;
|
|
17303
17306
|
break;
|
|
17304
17307
|
}
|
|
@@ -17729,7 +17732,7 @@ class CrossFileResolver {
|
|
|
17729
17732
|
}
|
|
17730
17733
|
if (arg.expression) {
|
|
17731
17734
|
for (const [tv, origin] of tainted) {
|
|
17732
|
-
const re = new RegExp(`\\b${tv}\\b`);
|
|
17735
|
+
const re = new RegExp(`\\b${escapeRegExp(tv)}\\b`);
|
|
17733
17736
|
if (re.test(arg.expression))
|
|
17734
17737
|
return { var: tv, origin };
|
|
17735
17738
|
}
|
|
@@ -37658,7 +37661,7 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
37658
37661
|
for (const other of taintedVars) {
|
|
37659
37662
|
if (other === varName)
|
|
37660
37663
|
continue;
|
|
37661
|
-
const re = new RegExp(`\\b${other}\\b`);
|
|
37664
|
+
const re = new RegExp(`\\b${other.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
|
|
37662
37665
|
if (!re.test(rhs))
|
|
37663
37666
|
continue;
|
|
37664
37667
|
anyReferenced = true;
|
|
@@ -37889,7 +37892,7 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
37889
37892
|
}
|
|
37890
37893
|
const assignMatch = sinkCode.match(/^\s*(?:[A-Za-z_][\w.<>[\]\s,?]*\s+)?[A-Za-z_]\w*\s*=(?!=)\s*/);
|
|
37891
37894
|
const rhs = assignMatch ? sinkCode.slice(assignMatch[0].length) : sinkCode;
|
|
37892
|
-
if (new RegExp(`\\b${sourceVar}\\b`).test(rhs)) {
|
|
37895
|
+
if (new RegExp(`\\b${sourceVar.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(rhs)) {
|
|
37893
37896
|
continue;
|
|
37894
37897
|
}
|
|
37895
37898
|
}
|
|
@@ -48608,6 +48611,20 @@ function computeProjectProfileSummary(fileAnalyses) {
|
|
|
48608
48611
|
}
|
|
48609
48612
|
return { byShape, byEnv, totalFiles: fileAnalyses.length };
|
|
48610
48613
|
}
|
|
48614
|
+
function longestLineLength(code) {
|
|
48615
|
+
let max = 0, start2 = 0;
|
|
48616
|
+
for (let i2 = 0;i2 < code.length; i2++) {
|
|
48617
|
+
if (code.charCodeAt(i2) === 10) {
|
|
48618
|
+
if (i2 - start2 > max)
|
|
48619
|
+
max = i2 - start2;
|
|
48620
|
+
start2 = i2 + 1;
|
|
48621
|
+
}
|
|
48622
|
+
}
|
|
48623
|
+
if (code.length - start2 > max)
|
|
48624
|
+
max = code.length - start2;
|
|
48625
|
+
return max;
|
|
48626
|
+
}
|
|
48627
|
+
var MAX_ANALYZABLE_LINE_LENGTH = 1e4;
|
|
48611
48628
|
async function analyze(code, filePath, language, options = {}) {
|
|
48612
48629
|
if (!initialized) {
|
|
48613
48630
|
await initAnalyzer(options);
|
|
@@ -48659,6 +48676,27 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
48659
48676
|
meta.projectProfile = makeProfileResolver2(options.projectProfile)(filePath);
|
|
48660
48677
|
}
|
|
48661
48678
|
phaseEnd("extractMeta", tMeta);
|
|
48679
|
+
if (longestLineLength(code) > MAX_ANALYZABLE_LINE_LENGTH) {
|
|
48680
|
+
logger.warn("Skipping analysis of a minified / single-line file", {
|
|
48681
|
+
filePath,
|
|
48682
|
+
language,
|
|
48683
|
+
codeLength: code.length
|
|
48684
|
+
});
|
|
48685
|
+
return {
|
|
48686
|
+
meta,
|
|
48687
|
+
types: [],
|
|
48688
|
+
calls: [],
|
|
48689
|
+
cfg: { blocks: [], edges: [] },
|
|
48690
|
+
dfg: { defs: [], uses: [], chains: [] },
|
|
48691
|
+
taint: { sources: [], sinks: [], sanitizers: [] },
|
|
48692
|
+
imports: [],
|
|
48693
|
+
exports: [],
|
|
48694
|
+
unresolved: [],
|
|
48695
|
+
enriched: {},
|
|
48696
|
+
metrics: { file: filePath, metrics: [] },
|
|
48697
|
+
parse_status: parseStatus
|
|
48698
|
+
};
|
|
48699
|
+
}
|
|
48662
48700
|
const tTypes = phaseStart();
|
|
48663
48701
|
const types = extractTypes(tree, nodeCache, language);
|
|
48664
48702
|
phaseEnd("extractTypes", tTypes);
|
|
@@ -50118,19 +50156,30 @@ var YELLOW = "\x1B[93m";
|
|
|
50118
50156
|
var BLUE = "\x1B[94m";
|
|
50119
50157
|
var CYAN = "\x1B[96m";
|
|
50120
50158
|
var MAGENTA = "\x1B[95m";
|
|
50159
|
+
function colorEnabled() {
|
|
50160
|
+
const noColor = process.env.NO_COLOR;
|
|
50161
|
+
if (noColor !== undefined && noColor !== "")
|
|
50162
|
+
return false;
|
|
50163
|
+
if (process.env.TERM === "dumb")
|
|
50164
|
+
return false;
|
|
50165
|
+
return true;
|
|
50166
|
+
}
|
|
50167
|
+
function paint(code, text) {
|
|
50168
|
+
return colorEnabled() ? `${code}${text}${RESET}` : text;
|
|
50169
|
+
}
|
|
50121
50170
|
var colors = {
|
|
50122
|
-
red: (text) =>
|
|
50123
|
-
green: (text) =>
|
|
50124
|
-
yellow: (text) =>
|
|
50125
|
-
blue: (text) =>
|
|
50126
|
-
cyan: (text) =>
|
|
50127
|
-
magenta: (text) =>
|
|
50128
|
-
bold: (text) =>
|
|
50171
|
+
red: (text) => paint(RED, text),
|
|
50172
|
+
green: (text) => paint(GREEN, text),
|
|
50173
|
+
yellow: (text) => paint(YELLOW, text),
|
|
50174
|
+
blue: (text) => paint(BLUE, text),
|
|
50175
|
+
cyan: (text) => paint(CYAN, text),
|
|
50176
|
+
magenta: (text) => paint(MAGENTA, text),
|
|
50177
|
+
bold: (text) => paint(BOLD, text),
|
|
50129
50178
|
dim: (text) => text
|
|
50130
50179
|
};
|
|
50131
50180
|
|
|
50132
50181
|
// src/version.ts
|
|
50133
|
-
var version = "4.9.
|
|
50182
|
+
var version = "4.9.25";
|
|
50134
50183
|
|
|
50135
50184
|
// src/formatters.ts
|
|
50136
50185
|
var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
|
|
@@ -50675,8 +50724,8 @@ function generateSarifResults(results, crossFileData) {
|
|
|
50675
50724
|
|
|
50676
50725
|
// src/build-info.ts
|
|
50677
50726
|
var buildInfo = {
|
|
50678
|
-
gitSha: "
|
|
50679
|
-
builtAt: "2026-09-
|
|
50727
|
+
gitSha: "2467260",
|
|
50728
|
+
builtAt: "2026-09-24T06:46:18.058Z"
|
|
50680
50729
|
};
|
|
50681
50730
|
|
|
50682
50731
|
// src/utils/args.ts
|
|
@@ -50735,7 +50784,7 @@ COMMANDS:
|
|
|
50735
50784
|
version Display version information
|
|
50736
50785
|
|
|
50737
50786
|
SCAN OPTIONS:
|
|
50738
|
-
-l, --language <lang> Scan only files for language (bash|go|html|java|javascript|typescript|python|rust)
|
|
50787
|
+
-l, --language <lang> Scan only files for language (bash|csharp|go|html|java|javascript|typescript|python|rust)
|
|
50739
50788
|
-f, --format <format> Output format (text|json|sarif) [default: text]
|
|
50740
50789
|
--threads <n> Parallel analysis threads [default: 4]
|
|
50741
50790
|
--severity <level> Filter by severity:
|
|
@@ -50768,7 +50817,7 @@ SCAN OPTIONS:
|
|
|
50768
50817
|
surfaced in output (text warning / JSON / SARIF field).
|
|
50769
50818
|
|
|
50770
50819
|
METRICS OPTIONS:
|
|
50771
|
-
-l, --language <lang> Analyze only files for language (bash|go|html|java|javascript|typescript|python|rust)
|
|
50820
|
+
-l, --language <lang> Analyze only files for language (bash|csharp|go|html|java|javascript|typescript|python|rust)
|
|
50772
50821
|
-f, --format <format> Output format (text|json) [default: text]
|
|
50773
50822
|
--category <cats> Filter by metric category (comma-separated):
|
|
50774
50823
|
- Valid categories: complexity, size, coupling,
|
|
@@ -50877,19 +50926,19 @@ class Spinner {
|
|
|
50877
50926
|
succeed(text) {
|
|
50878
50927
|
this.stop();
|
|
50879
50928
|
const message = text || this._text;
|
|
50880
|
-
console.error(
|
|
50929
|
+
console.error(`${colors.green(CHECKMARK)} ${message}`);
|
|
50881
50930
|
return this;
|
|
50882
50931
|
}
|
|
50883
50932
|
fail(text) {
|
|
50884
50933
|
this.stop();
|
|
50885
50934
|
const message = text || this._text;
|
|
50886
|
-
console.error(
|
|
50935
|
+
console.error(`${colors.red(CROSS)} ${message}`);
|
|
50887
50936
|
return this;
|
|
50888
50937
|
}
|
|
50889
50938
|
warn(text) {
|
|
50890
50939
|
this.stop();
|
|
50891
50940
|
const message = text || this._text;
|
|
50892
|
-
console.error(
|
|
50941
|
+
console.error(`${colors.yellow(WARNING)} ${message}`);
|
|
50893
50942
|
return this;
|
|
50894
50943
|
}
|
|
50895
50944
|
set text(value) {
|
|
@@ -50916,8 +50965,7 @@ function loadConfig(profilePath) {
|
|
|
50916
50965
|
const content = readFileSync(configPath, "utf-8");
|
|
50917
50966
|
return JSON.parse(content);
|
|
50918
50967
|
} catch (err2) {
|
|
50919
|
-
|
|
50920
|
-
return null;
|
|
50968
|
+
throw new Error(`Failed to parse ${configPath}: ${err2 instanceof Error ? err2.message : String(err2)}. ` + "Fix the JSON or remove the file.");
|
|
50921
50969
|
}
|
|
50922
50970
|
}
|
|
50923
50971
|
function convertConfigToPassOptions(config) {
|
|
@@ -50983,6 +51031,27 @@ function applySuppressionsToResults(results, suppressions, basePath) {
|
|
|
50983
51031
|
return { ...result, vulnerabilities: filteredVulns };
|
|
50984
51032
|
});
|
|
50985
51033
|
}
|
|
51034
|
+
function applySuppressionsToTaintPaths(paths, suppressions, basePath) {
|
|
51035
|
+
if (suppressions.length === 0)
|
|
51036
|
+
return paths;
|
|
51037
|
+
return paths.filter((path) => {
|
|
51038
|
+
for (const supp of suppressions) {
|
|
51039
|
+
const sinkType = path.sink.type;
|
|
51040
|
+
if (supp.pass !== sinkType && supp.pass !== `cross-file-${sinkType}`)
|
|
51041
|
+
continue;
|
|
51042
|
+
if (supp.file) {
|
|
51043
|
+
const suppFile = supp.file.replace(/^\.\//, "");
|
|
51044
|
+
const relativeFile = relative4(basePath, path.sink.file) || path.sink.file;
|
|
51045
|
+
if (suppFile !== relativeFile && suppFile !== path.sink.file)
|
|
51046
|
+
continue;
|
|
51047
|
+
}
|
|
51048
|
+
if (supp.line !== undefined && supp.line !== path.sink.line)
|
|
51049
|
+
continue;
|
|
51050
|
+
return false;
|
|
51051
|
+
}
|
|
51052
|
+
return true;
|
|
51053
|
+
});
|
|
51054
|
+
}
|
|
50986
51055
|
var TEST_PATTERNS = [
|
|
50987
51056
|
/[\/\\]test[\/\\]/i,
|
|
50988
51057
|
/[\/\\]tests[\/\\]/i,
|
|
@@ -51040,9 +51109,30 @@ function detectLanguage7(filePath) {
|
|
|
51040
51109
|
const ext = extname(filePath).toLowerCase();
|
|
51041
51110
|
return LANG_MAP[ext] || null;
|
|
51042
51111
|
}
|
|
51112
|
+
var SUPPORTED_SCAN_LANGUAGES = [
|
|
51113
|
+
...new Set(Object.values(LANG_MAP))
|
|
51114
|
+
].sort();
|
|
51043
51115
|
function normalizeLanguage(language) {
|
|
51044
51116
|
return language.trim().toLowerCase();
|
|
51045
51117
|
}
|
|
51118
|
+
function resolveScanLanguage(raw) {
|
|
51119
|
+
if (raw === undefined || raw === "")
|
|
51120
|
+
return;
|
|
51121
|
+
const normalized = normalizeLanguage(String(raw));
|
|
51122
|
+
if (!SUPPORTED_SCAN_LANGUAGES.includes(normalized)) {
|
|
51123
|
+
throw new Error(`Invalid language: ${normalized}
|
|
51124
|
+
Valid options: ${SUPPORTED_SCAN_LANGUAGES.join(", ")}`);
|
|
51125
|
+
}
|
|
51126
|
+
return normalized;
|
|
51127
|
+
}
|
|
51128
|
+
function languageFromCliOptions(options) {
|
|
51129
|
+
try {
|
|
51130
|
+
return resolveScanLanguage(options.language || options.l);
|
|
51131
|
+
} catch (err2) {
|
|
51132
|
+
console.error(colors.red(`Error: ${err2.message}`));
|
|
51133
|
+
process.exit(1);
|
|
51134
|
+
}
|
|
51135
|
+
}
|
|
51046
51136
|
function fileMatchesLanguage(filePath, language) {
|
|
51047
51137
|
const detected = detectLanguage7(filePath);
|
|
51048
51138
|
if (!detected)
|
|
@@ -51053,9 +51143,13 @@ function fileMatchesLanguage(filePath, language) {
|
|
|
51053
51143
|
}
|
|
51054
51144
|
function matchesGlob(filePath, pattern) {
|
|
51055
51145
|
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
51056
|
-
|
|
51146
|
+
let normalizedPattern = pattern.replace(/\\/g, "/");
|
|
51147
|
+
normalizedPattern = normalizedPattern.replace(/\*{3,}/g, "**").replace(/(?:\*\*\/)+/g, "**/").replace(/(?:\/\*\*)+/g, "/**");
|
|
51148
|
+
const globstarCount = (normalizedPattern.match(/\*\*/g) ?? []).length;
|
|
51149
|
+
if (globstarCount > 4)
|
|
51150
|
+
return false;
|
|
51057
51151
|
let regex = normalizedPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, ".");
|
|
51058
|
-
regex = regex.replace(/\{\{GLOBSTAR\}\}\//g, "(
|
|
51152
|
+
regex = regex.replace(/\{\{GLOBSTAR\}\}\//g, "(?:[^/]+/)*").replace(/\/\{\{GLOBSTAR\}\}/g, "(?:/[^/]+)*").replace(/\{\{GLOBSTAR\}\}/g, "[^]*");
|
|
51059
51153
|
return new RegExp("^" + regex + "$").test(normalizedPath);
|
|
51060
51154
|
}
|
|
51061
51155
|
function matchesAnyPattern(filePath, patterns) {
|
|
@@ -51446,9 +51540,12 @@ async function runScan(targetPath, options) {
|
|
|
51446
51540
|
if (spin)
|
|
51447
51541
|
spin.succeed(`Scanned ${files.length} file(s)`);
|
|
51448
51542
|
if (suppressions.length > 0) {
|
|
51449
|
-
const beforeCount = results.reduce((sum, r) => sum + r.vulnerabilities.length, 0);
|
|
51543
|
+
const beforeCount = results.reduce((sum, r) => sum + r.vulnerabilities.length, 0) + (crossFileData?.taintPaths.length ?? 0);
|
|
51450
51544
|
results = applySuppressionsToResults(results, suppressions, process.cwd());
|
|
51451
|
-
|
|
51545
|
+
if (crossFileData) {
|
|
51546
|
+
crossFileData.taintPaths = applySuppressionsToTaintPaths(crossFileData.taintPaths, suppressions, process.cwd());
|
|
51547
|
+
}
|
|
51548
|
+
const afterCount = results.reduce((sum, r) => sum + r.vulnerabilities.length, 0) + (crossFileData?.taintPaths.length ?? 0);
|
|
51452
51549
|
if (!options.quiet && beforeCount !== afterCount) {
|
|
51453
51550
|
console.error(colors.dim(`Suppressed ${beforeCount - afterCount} finding(s) via config`));
|
|
51454
51551
|
}
|
|
@@ -51997,7 +52094,7 @@ Usage: cognium-dev metrics <path> [options]`);
|
|
|
51997
52094
|
category: options.category,
|
|
51998
52095
|
output: options.output || options.o,
|
|
51999
52096
|
quiet: options.quiet === true || options.q === true,
|
|
52000
|
-
language:
|
|
52097
|
+
language: languageFromCliOptions(options),
|
|
52001
52098
|
excludeTests: options["exclude-tests"] === true,
|
|
52002
52099
|
profile: options.profile || options.p
|
|
52003
52100
|
};
|
|
@@ -52035,7 +52132,7 @@ Usage: cognium-dev scan <path> [options]`);
|
|
|
52035
52132
|
}
|
|
52036
52133
|
const targetPath = args2[0];
|
|
52037
52134
|
const scanOptions = {
|
|
52038
|
-
language:
|
|
52135
|
+
language: languageFromCliOptions(options),
|
|
52039
52136
|
format: options.format || options.f || "text",
|
|
52040
52137
|
threads: parseInt(options.threads || "4", 10),
|
|
52041
52138
|
severity: options.severity,
|
|
@@ -52069,12 +52166,15 @@ main().catch((error) => {
|
|
|
52069
52166
|
process.exit(2);
|
|
52070
52167
|
});
|
|
52071
52168
|
export {
|
|
52169
|
+
resolveScanLanguage,
|
|
52072
52170
|
matchesGlob,
|
|
52073
52171
|
loadConfig,
|
|
52074
52172
|
isTestFile2 as isTestFile,
|
|
52075
52173
|
detectLanguage7 as detectLanguage,
|
|
52076
52174
|
dedupeVulnerabilities,
|
|
52077
52175
|
convertConfigToPassOptions,
|
|
52176
|
+
applySuppressionsToTaintPaths,
|
|
52078
52177
|
applySuppressionsToResults,
|
|
52178
|
+
SUPPORTED_SCAN_LANGUAGES,
|
|
52079
52179
|
PASS_REGISTRY
|
|
52080
52180
|
};
|
package/package.json
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "4.9.
|
|
3
|
+
"version": "4.9.25",
|
|
4
4
|
"description": "Static Application Security Testing CLI for detecting security vulnerabilities via taint tracking",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
5
|
"bin": {
|
|
8
6
|
"cognium-dev": "dist/cli.js"
|
|
9
7
|
},
|
|
@@ -14,7 +12,7 @@
|
|
|
14
12
|
"test": "bun test",
|
|
15
13
|
"clean": "rm -rf dist cognium-dev cognium-dev-* *.bun-build wasm",
|
|
16
14
|
"typecheck": "tsc --noEmit",
|
|
17
|
-
"version": "node -e \"const v = require('./package.json').version; require('fs').writeFileSync('src/version.ts', '/**\\n * Version information\\n *\\n * Kept in sync with package.json via the \\`version\\` npm lifecycle script.\\n * Do not edit manually
|
|
15
|
+
"version": "node -e \"const v = require('./package.json').version; require('fs').writeFileSync('src/version.ts', '/**\\n * Version information\\n *\\n * Kept in sync with package.json via the \\`version\\` npm lifecycle script.\\n * Do not edit manually \u2014 use \\`npm version patch|minor|major\\` instead.\\n */\\nexport const version = \\x27' + v + '\\x27;\\n')\" && git add src/version.ts",
|
|
18
16
|
"dogfood": "bun run src/cli.ts scan src/ -q",
|
|
19
17
|
"prepublishOnly": "bun run build",
|
|
20
18
|
"test:coverage": "bun test --coverage"
|
|
@@ -67,7 +65,7 @@
|
|
|
67
65
|
},
|
|
68
66
|
"dependencies": {
|
|
69
67
|
"@cognium/project-profile-detect": "1.1.1",
|
|
70
|
-
"circle-ir": "4.9.
|
|
68
|
+
"circle-ir": "4.9.25"
|
|
71
69
|
},
|
|
72
70
|
"devDependencies": {
|
|
73
71
|
"@types/node": "^26.6.1",
|