cognium-dev 3.105.0 → 3.106.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +746 -45
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -4,8 +4,8 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
4
4
|
|
|
5
5
|
// src/cli.ts
|
|
6
6
|
import { readFileSync, existsSync } from "fs";
|
|
7
|
-
import { stat, readdir } from "fs/promises";
|
|
8
|
-
import { join, dirname as dirname2, extname, resolve, relative } from "path";
|
|
7
|
+
import { stat as stat2, readdir as readdir2 } from "fs/promises";
|
|
8
|
+
import { join as join2, dirname as dirname2, extname, resolve, relative as relative3 } from "path";
|
|
9
9
|
import { createRequire as createRequire2 } from "module";
|
|
10
10
|
|
|
11
11
|
// ../../node_modules/web-tree-sitter/web-tree-sitter.js
|
|
@@ -18068,7 +18068,74 @@ function applyLibraryApiSurfaceDowngrade(findings) {
|
|
|
18068
18068
|
return f;
|
|
18069
18069
|
if (f.severity === "medium" || f.severity === "low")
|
|
18070
18070
|
return f;
|
|
18071
|
-
return {
|
|
18071
|
+
return {
|
|
18072
|
+
...f,
|
|
18073
|
+
original_severity: f.severity,
|
|
18074
|
+
severity: "medium",
|
|
18075
|
+
level: "warning"
|
|
18076
|
+
};
|
|
18077
|
+
});
|
|
18078
|
+
}
|
|
18079
|
+
|
|
18080
|
+
// ../circle-ir/dist/analysis/project-profile-transform.js
|
|
18081
|
+
var DOWNGRADE_ELIGIBLE_RULE_IDS = new Set([
|
|
18082
|
+
"code_injection",
|
|
18083
|
+
"template_injection",
|
|
18084
|
+
"xpath_injection",
|
|
18085
|
+
"sql_injection"
|
|
18086
|
+
]);
|
|
18087
|
+
function libraryDowngrade(severity) {
|
|
18088
|
+
switch (severity) {
|
|
18089
|
+
case "critical":
|
|
18090
|
+
return { severity: "medium", level: "warning" };
|
|
18091
|
+
case "high":
|
|
18092
|
+
return { severity: "low", level: "note" };
|
|
18093
|
+
case "medium":
|
|
18094
|
+
return { severity: "low", level: "note" };
|
|
18095
|
+
case "low":
|
|
18096
|
+
return { severity: "low", level: "note" };
|
|
18097
|
+
}
|
|
18098
|
+
}
|
|
18099
|
+
function levelForSeverity(severity) {
|
|
18100
|
+
switch (severity) {
|
|
18101
|
+
case "critical":
|
|
18102
|
+
return "error";
|
|
18103
|
+
case "high":
|
|
18104
|
+
return "error";
|
|
18105
|
+
case "medium":
|
|
18106
|
+
return "warning";
|
|
18107
|
+
case "low":
|
|
18108
|
+
return "note";
|
|
18109
|
+
}
|
|
18110
|
+
}
|
|
18111
|
+
function applyProjectProfileTransform(findings, resolveProfile) {
|
|
18112
|
+
return findings.map((f) => {
|
|
18113
|
+
if (!f.tags?.includes(LIBRARY_API_SURFACE_TAG))
|
|
18114
|
+
return f;
|
|
18115
|
+
const profile = resolveProfile(f.file);
|
|
18116
|
+
if (profile === "unknown")
|
|
18117
|
+
return f;
|
|
18118
|
+
const shape = profile.split("/")[0];
|
|
18119
|
+
if (shape === "library") {
|
|
18120
|
+
if (!DOWNGRADE_ELIGIBLE_RULE_IDS.has(f.rule_id))
|
|
18121
|
+
return f;
|
|
18122
|
+
const { severity, level } = libraryDowngrade(f.severity);
|
|
18123
|
+
if (severity === f.severity && level === f.level)
|
|
18124
|
+
return f;
|
|
18125
|
+
return { ...f, severity, level };
|
|
18126
|
+
}
|
|
18127
|
+
if (shape === "application") {
|
|
18128
|
+
if (!f.original_severity)
|
|
18129
|
+
return f;
|
|
18130
|
+
if (f.original_severity === f.severity)
|
|
18131
|
+
return f;
|
|
18132
|
+
return {
|
|
18133
|
+
...f,
|
|
18134
|
+
severity: f.original_severity,
|
|
18135
|
+
level: levelForSeverity(f.original_severity)
|
|
18136
|
+
};
|
|
18137
|
+
}
|
|
18138
|
+
return f;
|
|
18072
18139
|
});
|
|
18073
18140
|
}
|
|
18074
18141
|
|
|
@@ -34933,6 +35000,13 @@ function getNodeTypesForLanguage(language) {
|
|
|
34933
35000
|
]);
|
|
34934
35001
|
}
|
|
34935
35002
|
}
|
|
35003
|
+
function makeProfileResolver(p) {
|
|
35004
|
+
if (p === undefined)
|
|
35005
|
+
return () => "unknown";
|
|
35006
|
+
if (typeof p === "string")
|
|
35007
|
+
return () => p;
|
|
35008
|
+
return (file) => p.get(file) ?? "unknown";
|
|
35009
|
+
}
|
|
34936
35010
|
async function analyze(code, filePath, language, options = {}) {
|
|
34937
35011
|
if (!initialized) {
|
|
34938
35012
|
await initAnalyzer(options);
|
|
@@ -35139,7 +35213,8 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
35139
35213
|
emitFindingsInstrumentation(filePath, findings, taint);
|
|
35140
35214
|
const verifiedFindings = applyConfidenceFilter(findings, options.includeSpeculative === true);
|
|
35141
35215
|
const downgradedFindings = applyLibraryApiSurfaceDowngrade(verifiedFindings);
|
|
35142
|
-
const
|
|
35216
|
+
const profiledFindings = applyProjectProfileTransform(downgradedFindings, makeProfileResolver(options.projectProfile));
|
|
35217
|
+
const cappedFindings = applyPerFileFindingCap(filePath, profiledFindings, options.perFileFindingCap ?? DEFAULT_PER_FILE_FINDING_CAP);
|
|
35143
35218
|
return {
|
|
35144
35219
|
meta,
|
|
35145
35220
|
types,
|
|
@@ -35297,6 +35372,491 @@ function deriveProjectRoot(paths) {
|
|
|
35297
35372
|
}
|
|
35298
35373
|
return common.join("/") || "/";
|
|
35299
35374
|
}
|
|
35375
|
+
// src/project-profile-detect/index.ts
|
|
35376
|
+
import { relative as relative2 } from "path";
|
|
35377
|
+
|
|
35378
|
+
// src/project-profile-detect/walk.ts
|
|
35379
|
+
import { readdir, readFile, stat } from "fs/promises";
|
|
35380
|
+
import { join, relative } from "path";
|
|
35381
|
+
|
|
35382
|
+
// src/project-profile-detect/maven-parse.ts
|
|
35383
|
+
var TAG = (name2) => new RegExp(`<${name2}\\b[^>]*>([\\s\\S]*?)<\\/${name2}>`, "i");
|
|
35384
|
+
var ALL_TAGS = (name2) => new RegExp(`<${name2}\\b[^>]*>([\\s\\S]*?)<\\/${name2}>`, "gi");
|
|
35385
|
+
function firstTag(xml, name2) {
|
|
35386
|
+
const m = TAG(name2).exec(xml);
|
|
35387
|
+
return m?.[1].trim();
|
|
35388
|
+
}
|
|
35389
|
+
function allTags(xml, name2) {
|
|
35390
|
+
const out2 = [];
|
|
35391
|
+
let m;
|
|
35392
|
+
const re = ALL_TAGS(name2);
|
|
35393
|
+
while ((m = re.exec(xml)) !== null)
|
|
35394
|
+
out2.push(m[1].trim());
|
|
35395
|
+
return out2;
|
|
35396
|
+
}
|
|
35397
|
+
var MAVEN_PLUGIN_MAP = {
|
|
35398
|
+
"spring-boot-maven-plugin": "spring-boot",
|
|
35399
|
+
"maven-plugin-plugin": "maven-plugin",
|
|
35400
|
+
"maven-war-plugin": "war",
|
|
35401
|
+
"maven-ear-plugin": "ear",
|
|
35402
|
+
"maven-shade-plugin": "application",
|
|
35403
|
+
"exec-maven-plugin": "application",
|
|
35404
|
+
"maven-assembly-plugin": "application"
|
|
35405
|
+
};
|
|
35406
|
+
function parseMavenPom(xml, moduleRoot, buildFile, directorySignals) {
|
|
35407
|
+
const stripped = xml.replace(/<parent\b[\s\S]*?<\/parent>/i, "");
|
|
35408
|
+
const groupId = firstTag(stripped, "groupId");
|
|
35409
|
+
const artifactId = firstTag(stripped, "artifactId");
|
|
35410
|
+
const version = firstTag(stripped, "version");
|
|
35411
|
+
const packaging = firstTag(stripped, "packaging");
|
|
35412
|
+
const buildBlock = firstTag(xml, "build") ?? "";
|
|
35413
|
+
const pluginBlocks = allTags(buildBlock, "plugin");
|
|
35414
|
+
const plugins = new Set;
|
|
35415
|
+
for (const p of pluginBlocks) {
|
|
35416
|
+
const aid = firstTag(p, "artifactId");
|
|
35417
|
+
if (aid && MAVEN_PLUGIN_MAP[aid])
|
|
35418
|
+
plugins.add(MAVEN_PLUGIN_MAP[aid]);
|
|
35419
|
+
}
|
|
35420
|
+
if (/<parent\b[\s\S]*?<artifactId>\s*spring-boot-starter-parent\s*<\/artifactId>/i.test(xml)) {
|
|
35421
|
+
plugins.add("spring-boot");
|
|
35422
|
+
}
|
|
35423
|
+
if (packaging === "war")
|
|
35424
|
+
plugins.add("war");
|
|
35425
|
+
if (packaging === "ear")
|
|
35426
|
+
plugins.add("ear");
|
|
35427
|
+
if (packaging === "maven-plugin")
|
|
35428
|
+
plugins.add("maven-plugin");
|
|
35429
|
+
const distBlock = firstTag(xml, "distributionManagement") ?? "";
|
|
35430
|
+
const urls = [
|
|
35431
|
+
...allTags(distBlock, "url")
|
|
35432
|
+
].map((u) => u.trim()).filter(Boolean);
|
|
35433
|
+
const signals = {
|
|
35434
|
+
...directorySignals,
|
|
35435
|
+
plugins: [...directorySignals.plugins, ...plugins],
|
|
35436
|
+
packaging,
|
|
35437
|
+
distributionUrls: [...directorySignals.distributionUrls, ...urls]
|
|
35438
|
+
};
|
|
35439
|
+
return {
|
|
35440
|
+
root: moduleRoot,
|
|
35441
|
+
buildSystem: "maven",
|
|
35442
|
+
buildFile,
|
|
35443
|
+
groupId,
|
|
35444
|
+
artifactId,
|
|
35445
|
+
version,
|
|
35446
|
+
signals
|
|
35447
|
+
};
|
|
35448
|
+
}
|
|
35449
|
+
|
|
35450
|
+
// src/project-profile-detect/gradle-parse.ts
|
|
35451
|
+
var GRADLE_PLUGIN_MAP = {
|
|
35452
|
+
"org.springframework.boot": "spring-boot",
|
|
35453
|
+
"io.spring.dependency-management": "spring-boot",
|
|
35454
|
+
"java-library": "java-library",
|
|
35455
|
+
application: "application",
|
|
35456
|
+
war: "war",
|
|
35457
|
+
ear: "ear",
|
|
35458
|
+
"maven-publish": "maven-publish",
|
|
35459
|
+
"java-gradle-plugin": "gradle-plugin",
|
|
35460
|
+
"com.gradle.plugin-publish": "gradle-plugin"
|
|
35461
|
+
};
|
|
35462
|
+
var PLUGIN_ID_RE = /\bid\s*[(\s]\s*['"]([^'"]+)['"]/g;
|
|
35463
|
+
var APPLY_PLUGIN_RE = /\bapply\s+plugin\s*:\s*['"]([^'"]+)['"]/g;
|
|
35464
|
+
var GROUP_RE = /\bgroup\s*[=(]\s*['"]([^'"]+)['"]/;
|
|
35465
|
+
var VERSION_RE = /\bversion\s*[=(]\s*['"]([^'"]+)['"]/;
|
|
35466
|
+
var URL_RE = /\burl\s*[=(]?\s*(?:uri\s*\(\s*)?['"]([^'"]+)['"]/g;
|
|
35467
|
+
function parsePlugins(text) {
|
|
35468
|
+
const ids = new Set;
|
|
35469
|
+
let m;
|
|
35470
|
+
PLUGIN_ID_RE.lastIndex = 0;
|
|
35471
|
+
while ((m = PLUGIN_ID_RE.exec(text)) !== null)
|
|
35472
|
+
ids.add(m[1]);
|
|
35473
|
+
APPLY_PLUGIN_RE.lastIndex = 0;
|
|
35474
|
+
while ((m = APPLY_PLUGIN_RE.exec(text)) !== null)
|
|
35475
|
+
ids.add(m[1]);
|
|
35476
|
+
const out2 = new Set;
|
|
35477
|
+
for (const id of ids) {
|
|
35478
|
+
const tag = GRADLE_PLUGIN_MAP[id];
|
|
35479
|
+
if (tag)
|
|
35480
|
+
out2.add(tag);
|
|
35481
|
+
}
|
|
35482
|
+
return out2;
|
|
35483
|
+
}
|
|
35484
|
+
function parseUrls(text) {
|
|
35485
|
+
const out2 = [];
|
|
35486
|
+
let m;
|
|
35487
|
+
URL_RE.lastIndex = 0;
|
|
35488
|
+
while ((m = URL_RE.exec(text)) !== null)
|
|
35489
|
+
out2.push(m[1]);
|
|
35490
|
+
return out2;
|
|
35491
|
+
}
|
|
35492
|
+
function parseGradleBuild(text, moduleRoot, buildFile, buildSystem, directorySignals) {
|
|
35493
|
+
const cleaned = text.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
35494
|
+
const pluginsSet = parsePlugins(cleaned);
|
|
35495
|
+
const urls = parseUrls(cleaned);
|
|
35496
|
+
const groupId = GROUP_RE.exec(cleaned)?.[1];
|
|
35497
|
+
const version = VERSION_RE.exec(cleaned)?.[1];
|
|
35498
|
+
const artifactId = moduleRoot.split(/[\\/]/).filter(Boolean).pop();
|
|
35499
|
+
const signals = {
|
|
35500
|
+
...directorySignals,
|
|
35501
|
+
plugins: [...directorySignals.plugins, ...pluginsSet],
|
|
35502
|
+
distributionUrls: [...directorySignals.distributionUrls, ...urls]
|
|
35503
|
+
};
|
|
35504
|
+
return {
|
|
35505
|
+
root: moduleRoot,
|
|
35506
|
+
buildSystem,
|
|
35507
|
+
buildFile,
|
|
35508
|
+
groupId,
|
|
35509
|
+
artifactId,
|
|
35510
|
+
version,
|
|
35511
|
+
signals
|
|
35512
|
+
};
|
|
35513
|
+
}
|
|
35514
|
+
|
|
35515
|
+
// src/project-profile-detect/walk.ts
|
|
35516
|
+
var BUILD_FILES = ["pom.xml", "build.gradle", "build.gradle.kts"];
|
|
35517
|
+
var SKIP_DIRS = new Set([
|
|
35518
|
+
"node_modules",
|
|
35519
|
+
".git",
|
|
35520
|
+
".svn",
|
|
35521
|
+
".hg",
|
|
35522
|
+
"target",
|
|
35523
|
+
"build",
|
|
35524
|
+
"out",
|
|
35525
|
+
"dist",
|
|
35526
|
+
".gradle",
|
|
35527
|
+
".idea",
|
|
35528
|
+
".vscode",
|
|
35529
|
+
"bin",
|
|
35530
|
+
"obj"
|
|
35531
|
+
]);
|
|
35532
|
+
async function discoverBuildModules(scanRoot) {
|
|
35533
|
+
const modules = [];
|
|
35534
|
+
await walk(scanRoot, modules);
|
|
35535
|
+
return modules;
|
|
35536
|
+
}
|
|
35537
|
+
async function walk(dir, out2) {
|
|
35538
|
+
let entries;
|
|
35539
|
+
try {
|
|
35540
|
+
entries = await readdir(dir);
|
|
35541
|
+
} catch {
|
|
35542
|
+
return;
|
|
35543
|
+
}
|
|
35544
|
+
const buildFile = entries.find((e) => BUILD_FILES.includes(e));
|
|
35545
|
+
if (buildFile) {
|
|
35546
|
+
const buildFilePath = join(dir, buildFile);
|
|
35547
|
+
const buildSystem = buildFile === "pom.xml" ? "maven" : buildFile === "build.gradle.kts" ? "gradle-kts" : "gradle";
|
|
35548
|
+
const signals = await collectDirectorySignals(dir);
|
|
35549
|
+
let mod;
|
|
35550
|
+
try {
|
|
35551
|
+
const raw = await readFile(buildFilePath, "utf-8");
|
|
35552
|
+
mod = buildSystem === "maven" ? parseMavenPom(raw, dir, buildFilePath, signals) : parseGradleBuild(raw, dir, buildFilePath, buildSystem, signals);
|
|
35553
|
+
} catch {
|
|
35554
|
+
mod = { root: dir, buildSystem, buildFile: buildFilePath, signals };
|
|
35555
|
+
}
|
|
35556
|
+
out2.push(mod);
|
|
35557
|
+
}
|
|
35558
|
+
for (const entry of entries) {
|
|
35559
|
+
if (SKIP_DIRS.has(entry) || entry.startsWith("."))
|
|
35560
|
+
continue;
|
|
35561
|
+
const full = join(dir, entry);
|
|
35562
|
+
let s;
|
|
35563
|
+
try {
|
|
35564
|
+
s = await stat(full);
|
|
35565
|
+
} catch {
|
|
35566
|
+
continue;
|
|
35567
|
+
}
|
|
35568
|
+
if (s.isDirectory()) {
|
|
35569
|
+
await walk(full, out2);
|
|
35570
|
+
}
|
|
35571
|
+
}
|
|
35572
|
+
}
|
|
35573
|
+
async function collectDirectorySignals(moduleRoot) {
|
|
35574
|
+
const signals = {
|
|
35575
|
+
plugins: [],
|
|
35576
|
+
distributionUrls: [],
|
|
35577
|
+
hasJpmsModuleInfo: false,
|
|
35578
|
+
hasSpiServices: false,
|
|
35579
|
+
hasMainMethod: false
|
|
35580
|
+
};
|
|
35581
|
+
const javaRoot = join(moduleRoot, "src", "main", "java");
|
|
35582
|
+
const resourcesRoot = join(moduleRoot, "src", "main", "resources");
|
|
35583
|
+
try {
|
|
35584
|
+
await stat(join(javaRoot, "module-info.java"));
|
|
35585
|
+
signals.hasJpmsModuleInfo = true;
|
|
35586
|
+
} catch {}
|
|
35587
|
+
try {
|
|
35588
|
+
const s = await stat(join(resourcesRoot, "META-INF", "services"));
|
|
35589
|
+
if (s.isDirectory())
|
|
35590
|
+
signals.hasSpiServices = true;
|
|
35591
|
+
} catch {}
|
|
35592
|
+
try {
|
|
35593
|
+
signals.hasMainMethod = await scanForMainMethod(javaRoot, 200);
|
|
35594
|
+
} catch {}
|
|
35595
|
+
return signals;
|
|
35596
|
+
}
|
|
35597
|
+
var MAIN_METHOD_RE = /\bpublic\s+static\s+void\s+main\s*\(\s*(?:final\s+)?String\s*(?:\[\s*\]|\.{3})/;
|
|
35598
|
+
async function scanForMainMethod(root, maxFiles) {
|
|
35599
|
+
let visited = 0;
|
|
35600
|
+
const stack = [root];
|
|
35601
|
+
while (stack.length > 0 && visited < maxFiles) {
|
|
35602
|
+
const dir = stack.pop();
|
|
35603
|
+
let entries;
|
|
35604
|
+
try {
|
|
35605
|
+
entries = await readdir(dir);
|
|
35606
|
+
} catch {
|
|
35607
|
+
continue;
|
|
35608
|
+
}
|
|
35609
|
+
for (const e of entries) {
|
|
35610
|
+
const full = join(dir, e);
|
|
35611
|
+
let s;
|
|
35612
|
+
try {
|
|
35613
|
+
s = await stat(full);
|
|
35614
|
+
} catch {
|
|
35615
|
+
continue;
|
|
35616
|
+
}
|
|
35617
|
+
if (s.isDirectory()) {
|
|
35618
|
+
stack.push(full);
|
|
35619
|
+
} else if (s.isFile() && e.endsWith(".java")) {
|
|
35620
|
+
visited++;
|
|
35621
|
+
try {
|
|
35622
|
+
const text = await readFile(full, "utf-8");
|
|
35623
|
+
if (MAIN_METHOD_RE.test(text))
|
|
35624
|
+
return true;
|
|
35625
|
+
} catch {}
|
|
35626
|
+
if (visited >= maxFiles)
|
|
35627
|
+
break;
|
|
35628
|
+
}
|
|
35629
|
+
}
|
|
35630
|
+
}
|
|
35631
|
+
return false;
|
|
35632
|
+
}
|
|
35633
|
+
async function enumerateScanFiles(scanRoot) {
|
|
35634
|
+
const out2 = [];
|
|
35635
|
+
await enumerate(scanRoot, out2);
|
|
35636
|
+
return out2;
|
|
35637
|
+
}
|
|
35638
|
+
async function enumerate(dir, out2) {
|
|
35639
|
+
let entries;
|
|
35640
|
+
try {
|
|
35641
|
+
entries = await readdir(dir);
|
|
35642
|
+
} catch {
|
|
35643
|
+
return;
|
|
35644
|
+
}
|
|
35645
|
+
for (const e of entries) {
|
|
35646
|
+
if (SKIP_DIRS.has(e) || e.startsWith("."))
|
|
35647
|
+
continue;
|
|
35648
|
+
const full = join(dir, e);
|
|
35649
|
+
let s;
|
|
35650
|
+
try {
|
|
35651
|
+
s = await stat(full);
|
|
35652
|
+
} catch {
|
|
35653
|
+
continue;
|
|
35654
|
+
}
|
|
35655
|
+
if (s.isDirectory()) {
|
|
35656
|
+
await enumerate(full, out2);
|
|
35657
|
+
} else if (s.isFile()) {
|
|
35658
|
+
out2.push(full);
|
|
35659
|
+
}
|
|
35660
|
+
}
|
|
35661
|
+
}
|
|
35662
|
+
function ownerOf(file, modules) {
|
|
35663
|
+
let best;
|
|
35664
|
+
let bestLen = -1;
|
|
35665
|
+
for (const m of modules) {
|
|
35666
|
+
if (file === m.root || file.startsWith(m.root + "/")) {
|
|
35667
|
+
if (m.root.length > bestLen) {
|
|
35668
|
+
best = m;
|
|
35669
|
+
bestLen = m.root.length;
|
|
35670
|
+
}
|
|
35671
|
+
}
|
|
35672
|
+
}
|
|
35673
|
+
return best;
|
|
35674
|
+
}
|
|
35675
|
+
|
|
35676
|
+
// src/project-profile-detect/publication-detect.ts
|
|
35677
|
+
var PUBLIC_REGISTRY_HOSTS = new Set([
|
|
35678
|
+
"repo.maven.apache.org",
|
|
35679
|
+
"repo1.maven.org",
|
|
35680
|
+
"oss.sonatype.org",
|
|
35681
|
+
"s01.oss.sonatype.org",
|
|
35682
|
+
"central.sonatype.com",
|
|
35683
|
+
"central.sonatype.org",
|
|
35684
|
+
"plugins.gradle.org",
|
|
35685
|
+
"jcenter.bintray.com"
|
|
35686
|
+
]);
|
|
35687
|
+
function isPubliclyPublished(urls) {
|
|
35688
|
+
for (const u of urls) {
|
|
35689
|
+
let host;
|
|
35690
|
+
try {
|
|
35691
|
+
host = new URL(u).hostname.toLowerCase();
|
|
35692
|
+
} catch {
|
|
35693
|
+
continue;
|
|
35694
|
+
}
|
|
35695
|
+
if (PUBLIC_REGISTRY_HOSTS.has(host))
|
|
35696
|
+
return true;
|
|
35697
|
+
}
|
|
35698
|
+
return false;
|
|
35699
|
+
}
|
|
35700
|
+
|
|
35701
|
+
// src/project-profile-detect/shape-resolve.ts
|
|
35702
|
+
function resolveShape(mod) {
|
|
35703
|
+
const sig = mod.signals;
|
|
35704
|
+
const has = (tag) => sig.plugins.includes(tag);
|
|
35705
|
+
const reasons = [];
|
|
35706
|
+
if (has("spring-boot")) {
|
|
35707
|
+
reasons.push("spring-boot plugin");
|
|
35708
|
+
return { shape: "server", reasons };
|
|
35709
|
+
}
|
|
35710
|
+
if (has("war") || has("ear")) {
|
|
35711
|
+
reasons.push(has("war") ? "war packaging" : "ear packaging");
|
|
35712
|
+
return { shape: "server", reasons };
|
|
35713
|
+
}
|
|
35714
|
+
if (has("maven-plugin") || has("gradle-plugin")) {
|
|
35715
|
+
reasons.push(has("maven-plugin") ? "maven-plugin packaging" : "gradle-plugin id");
|
|
35716
|
+
return { shape: "plugin", reasons };
|
|
35717
|
+
}
|
|
35718
|
+
if (has("application")) {
|
|
35719
|
+
reasons.push("application plugin");
|
|
35720
|
+
return { shape: "cli", reasons };
|
|
35721
|
+
}
|
|
35722
|
+
if (sig.hasMainMethod) {
|
|
35723
|
+
reasons.push("main(String[]) found");
|
|
35724
|
+
return { shape: "application", reasons };
|
|
35725
|
+
}
|
|
35726
|
+
const libSignals = [];
|
|
35727
|
+
if (has("java-library"))
|
|
35728
|
+
libSignals.push("java-library plugin");
|
|
35729
|
+
if (sig.hasJpmsModuleInfo)
|
|
35730
|
+
libSignals.push("JPMS module-info.java");
|
|
35731
|
+
if (sig.hasSpiServices)
|
|
35732
|
+
libSignals.push("META-INF/services SPI");
|
|
35733
|
+
if (libSignals.length > 0) {
|
|
35734
|
+
const published = isPubliclyPublished(sig.distributionUrls);
|
|
35735
|
+
if (published) {
|
|
35736
|
+
reasons.push(...libSignals, "public-registry distribution");
|
|
35737
|
+
return { shape: "library", reasons };
|
|
35738
|
+
}
|
|
35739
|
+
reasons.push(...libSignals, "no public-registry distribution (internal helper)");
|
|
35740
|
+
return { shape: "application", reasons };
|
|
35741
|
+
}
|
|
35742
|
+
reasons.push("no shape signals");
|
|
35743
|
+
return { shape: "unknown", reasons };
|
|
35744
|
+
}
|
|
35745
|
+
|
|
35746
|
+
// src/project-profile-detect/env-resolve.ts
|
|
35747
|
+
var TEST_RE = /(?:^|\/)tests?\//;
|
|
35748
|
+
var SAMPLE_RE = /(?:^|\/)(?:samples?|examples?|demos?|fixtures?)\//;
|
|
35749
|
+
var BENCHMARK_RE = /(?:^|\/)benchmarks?\//;
|
|
35750
|
+
var PROD_RE = /(?:^|\/)src\/main\//;
|
|
35751
|
+
function resolveEnv(absoluteFile) {
|
|
35752
|
+
const f = absoluteFile.replace(/\\/g, "/").toLowerCase();
|
|
35753
|
+
if (TEST_RE.test(f))
|
|
35754
|
+
return "test";
|
|
35755
|
+
if (BENCHMARK_RE.test(f))
|
|
35756
|
+
return "benchmark";
|
|
35757
|
+
if (SAMPLE_RE.test(f))
|
|
35758
|
+
return "sample";
|
|
35759
|
+
if (PROD_RE.test(f))
|
|
35760
|
+
return "production";
|
|
35761
|
+
return "dev";
|
|
35762
|
+
}
|
|
35763
|
+
|
|
35764
|
+
// src/project-profile-detect/overrides.ts
|
|
35765
|
+
function compileGlob(glob) {
|
|
35766
|
+
let re = "";
|
|
35767
|
+
let i2 = 0;
|
|
35768
|
+
while (i2 < glob.length) {
|
|
35769
|
+
const c = glob[i2];
|
|
35770
|
+
if (c === "*") {
|
|
35771
|
+
if (glob[i2 + 1] === "*") {
|
|
35772
|
+
re += ".*";
|
|
35773
|
+
i2 += 2;
|
|
35774
|
+
if (glob[i2] === "/")
|
|
35775
|
+
i2++;
|
|
35776
|
+
} else {
|
|
35777
|
+
re += "[^/]*";
|
|
35778
|
+
i2++;
|
|
35779
|
+
}
|
|
35780
|
+
} else if (c === "?") {
|
|
35781
|
+
re += "[^/]";
|
|
35782
|
+
i2++;
|
|
35783
|
+
} else if (/[.+^${}()|[\]\\]/.test(c)) {
|
|
35784
|
+
re += "\\" + c;
|
|
35785
|
+
i2++;
|
|
35786
|
+
} else {
|
|
35787
|
+
re += c;
|
|
35788
|
+
i2++;
|
|
35789
|
+
}
|
|
35790
|
+
}
|
|
35791
|
+
return new RegExp("^" + re + "$");
|
|
35792
|
+
}
|
|
35793
|
+
function compileOverrides(overrides) {
|
|
35794
|
+
if (!overrides)
|
|
35795
|
+
return [];
|
|
35796
|
+
return Object.entries(overrides).map(([glob, profile]) => ({
|
|
35797
|
+
re: compileGlob(glob),
|
|
35798
|
+
profile,
|
|
35799
|
+
glob
|
|
35800
|
+
}));
|
|
35801
|
+
}
|
|
35802
|
+
function applyOverrides(relativePath, compiled) {
|
|
35803
|
+
const p = relativePath.replace(/\\/g, "/");
|
|
35804
|
+
for (const { re, profile, glob } of compiled) {
|
|
35805
|
+
if (re.test(p))
|
|
35806
|
+
return { profile, glob };
|
|
35807
|
+
}
|
|
35808
|
+
return;
|
|
35809
|
+
}
|
|
35810
|
+
|
|
35811
|
+
// src/project-profile-detect/index.ts
|
|
35812
|
+
async function detectProjectProfiles(scanRoot, options = {}) {
|
|
35813
|
+
const modules = await discoverBuildModules(scanRoot);
|
|
35814
|
+
const files = await enumerateScanFiles(scanRoot);
|
|
35815
|
+
const compiledOverrides = compileOverrides(options.overrides);
|
|
35816
|
+
const resolvedByRoot = new Map;
|
|
35817
|
+
const resolvedModules = [];
|
|
35818
|
+
for (const m of modules) {
|
|
35819
|
+
const sr = resolveShape(m);
|
|
35820
|
+
const moduleEnv = resolveEnv(m.root);
|
|
35821
|
+
const profile = sr.shape === "unknown" ? "unknown" : `${sr.shape}/${moduleEnv}`;
|
|
35822
|
+
const r = { module: m, profile, reasons: sr.reasons };
|
|
35823
|
+
resolvedByRoot.set(m.root, r);
|
|
35824
|
+
resolvedModules.push(r);
|
|
35825
|
+
}
|
|
35826
|
+
const profileByFile = new Map;
|
|
35827
|
+
const unknownFiles = [];
|
|
35828
|
+
for (const file of files) {
|
|
35829
|
+
const rel = relative2(scanRoot, file);
|
|
35830
|
+
const ov = applyOverrides(rel, compiledOverrides);
|
|
35831
|
+
if (ov) {
|
|
35832
|
+
profileByFile.set(file, ov.profile);
|
|
35833
|
+
continue;
|
|
35834
|
+
}
|
|
35835
|
+
if (options.forcedProfile && options.forcedProfile !== "unknown") {
|
|
35836
|
+
const [shape2] = options.forcedProfile.split("/");
|
|
35837
|
+
const env2 = resolveEnv(file);
|
|
35838
|
+
profileByFile.set(file, `${shape2}/${env2}`);
|
|
35839
|
+
continue;
|
|
35840
|
+
}
|
|
35841
|
+
const owner = ownerOf(file, modules);
|
|
35842
|
+
if (!owner) {
|
|
35843
|
+
profileByFile.set(file, "unknown");
|
|
35844
|
+
unknownFiles.push(file);
|
|
35845
|
+
continue;
|
|
35846
|
+
}
|
|
35847
|
+
const ownerResolution = resolvedByRoot.get(owner.root);
|
|
35848
|
+
if (!ownerResolution || ownerResolution.profile === "unknown") {
|
|
35849
|
+
profileByFile.set(file, "unknown");
|
|
35850
|
+
unknownFiles.push(file);
|
|
35851
|
+
continue;
|
|
35852
|
+
}
|
|
35853
|
+
const [shape] = ownerResolution.profile.split("/");
|
|
35854
|
+
const env = resolveEnv(file);
|
|
35855
|
+
profileByFile.set(file, `${shape}/${env}`);
|
|
35856
|
+
}
|
|
35857
|
+
return { profileByFile, modules: resolvedModules, unknownFiles };
|
|
35858
|
+
}
|
|
35859
|
+
|
|
35300
35860
|
// src/utils/colors.ts
|
|
35301
35861
|
var RESET = "\x1B[0m";
|
|
35302
35862
|
var BOLD = "\x1B[1m";
|
|
@@ -35318,7 +35878,7 @@ var colors = {
|
|
|
35318
35878
|
};
|
|
35319
35879
|
|
|
35320
35880
|
// src/version.ts
|
|
35321
|
-
var version = "3.
|
|
35881
|
+
var version = "3.106.0";
|
|
35322
35882
|
|
|
35323
35883
|
// src/formatters.ts
|
|
35324
35884
|
var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
|
|
@@ -35650,8 +36210,22 @@ function formatCrossFilePaths(taintPaths) {
|
|
|
35650
36210
|
return lines.join(`
|
|
35651
36211
|
`);
|
|
35652
36212
|
}
|
|
35653
|
-
function formatResults(results, verbose, crossFileData) {
|
|
36213
|
+
function formatResults(results, verbose, crossFileData, profileSummary) {
|
|
35654
36214
|
const lines = [];
|
|
36215
|
+
if (profileSummary && profileSummary.modules.length > 0) {
|
|
36216
|
+
lines.push(colors.bold("Project profile"));
|
|
36217
|
+
for (const m of profileSummary.modules) {
|
|
36218
|
+
const tag = m.root === "" || m.root === "." ? "." : m.root;
|
|
36219
|
+
lines.push(` ${colors.cyan(tag)} → ${colors.bold(m.profile)}`);
|
|
36220
|
+
if (verbose) {
|
|
36221
|
+
lines.push(` reasons: ${m.reasons.join(" → ")}`);
|
|
36222
|
+
}
|
|
36223
|
+
}
|
|
36224
|
+
if (profileSummary.unknownFileCount > 0) {
|
|
36225
|
+
lines.push(colors.dim(` ${profileSummary.unknownFileCount} file(s) outside any module → unknown`));
|
|
36226
|
+
}
|
|
36227
|
+
lines.push("");
|
|
36228
|
+
}
|
|
35655
36229
|
for (const result of results) {
|
|
35656
36230
|
if (result.error) {
|
|
35657
36231
|
lines.push(colors.red(`[ERROR] ${result.file}: ${result.error}`));
|
|
@@ -35696,7 +36270,7 @@ function formatResults(results, verbose, crossFileData) {
|
|
|
35696
36270
|
return lines.join(`
|
|
35697
36271
|
`);
|
|
35698
36272
|
}
|
|
35699
|
-
function formatJSON(results, crossFileData) {
|
|
36273
|
+
function formatJSON(results, crossFileData, profileSummary) {
|
|
35700
36274
|
const output = {
|
|
35701
36275
|
version,
|
|
35702
36276
|
timestamp: new Date().toISOString(),
|
|
@@ -35708,6 +36282,7 @@ function formatJSON(results, crossFileData) {
|
|
|
35708
36282
|
cross_file_taint_paths: crossFileData?.taintPaths ?? [],
|
|
35709
36283
|
cross_file_calls: crossFileData?.crossFileCalls ?? [],
|
|
35710
36284
|
cross_file_budget_exceeded: crossFileData?.budgetExceeded ?? false,
|
|
36285
|
+
...profileSummary ? { project_profile: profileSummary } : {},
|
|
35711
36286
|
summary: {
|
|
35712
36287
|
filesScanned: results.length,
|
|
35713
36288
|
filesWithVulnerabilities: results.filter((r) => r.vulnerabilities.length > 0).length,
|
|
@@ -35719,7 +36294,7 @@ function formatJSON(results, crossFileData) {
|
|
|
35719
36294
|
};
|
|
35720
36295
|
return JSON.stringify(output, null, 2);
|
|
35721
36296
|
}
|
|
35722
|
-
function formatSARIF(results, crossFileData) {
|
|
36297
|
+
function formatSARIF(results, crossFileData, profileSummary) {
|
|
35723
36298
|
const sarif = {
|
|
35724
36299
|
$schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
|
|
35725
36300
|
version: "2.1.0",
|
|
@@ -35733,7 +36308,8 @@ function formatSARIF(results, crossFileData) {
|
|
|
35733
36308
|
rules: generateRules(results, crossFileData)
|
|
35734
36309
|
}
|
|
35735
36310
|
},
|
|
35736
|
-
results: generateSarifResults(results, crossFileData)
|
|
36311
|
+
results: generateSarifResults(results, crossFileData),
|
|
36312
|
+
...profileSummary ? { properties: { projectProfile: profileSummary } } : {}
|
|
35737
36313
|
}
|
|
35738
36314
|
]
|
|
35739
36315
|
};
|
|
@@ -35797,7 +36373,8 @@ function generateSarifResults(results, crossFileData) {
|
|
|
35797
36373
|
cwe: vuln.cwe,
|
|
35798
36374
|
severity: vuln.severity,
|
|
35799
36375
|
...vuln.fix ? { fix: vuln.fix } : {},
|
|
35800
|
-
...vuln.tags && vuln.tags.length > 0 ? { tags: vuln.tags } : {}
|
|
36376
|
+
...vuln.tags && vuln.tags.length > 0 ? { tags: vuln.tags } : {},
|
|
36377
|
+
...vuln.profile ? { profile: vuln.profile } : {}
|
|
35801
36378
|
}
|
|
35802
36379
|
});
|
|
35803
36380
|
}
|
|
@@ -35907,6 +36484,13 @@ SCAN OPTIONS:
|
|
|
35907
36484
|
--disable-pass <passes> Disable specific passes (comma-separated, e.g., "naming-convention,todo-in-prod")
|
|
35908
36485
|
--exclude-tests Exclude test files and directories
|
|
35909
36486
|
--profile <file> Load config from file [default: cognium.config.json]
|
|
36487
|
+
--project-profile <p> Force project profile (shape/env). Disables auto-detection.
|
|
36488
|
+
- shape: library|application|cli|server|plugin
|
|
36489
|
+
- env: production|dev|sample|benchmark|test
|
|
36490
|
+
- Example: --project-profile library/production
|
|
36491
|
+
- Default: auto-detect from build files (pom.xml, build.gradle)
|
|
36492
|
+
--no-project-profile Disable project-profile auto-detection (every file → unknown).
|
|
36493
|
+
--project-profile-explain Print detected per-module profiles + reason chain, then exit.
|
|
35910
36494
|
-o, --output <file> Write results to file
|
|
35911
36495
|
-q, --quiet Suppress progress output
|
|
35912
36496
|
-v, --verbose Show detailed output
|
|
@@ -36095,7 +36679,7 @@ function applySuppressionsToResults(results, suppressions, basePath) {
|
|
|
36095
36679
|
if (suppressions.length === 0)
|
|
36096
36680
|
return results;
|
|
36097
36681
|
return results.map((result) => {
|
|
36098
|
-
const relativeFile =
|
|
36682
|
+
const relativeFile = relative3(basePath, result.file) || result.file;
|
|
36099
36683
|
const filteredVulns = result.vulnerabilities.filter((vuln) => {
|
|
36100
36684
|
for (const supp of suppressions) {
|
|
36101
36685
|
if (supp.pass !== vuln.type)
|
|
@@ -36178,13 +36762,13 @@ function matchesAnyPattern(filePath, patterns) {
|
|
|
36178
36762
|
async function collectFiles(targetPath, options = {}) {
|
|
36179
36763
|
const { language, excludeTests = false, includePatterns, excludePatterns, basePath } = options;
|
|
36180
36764
|
const files = [];
|
|
36181
|
-
const pathStat = await
|
|
36765
|
+
const pathStat = await stat2(targetPath);
|
|
36182
36766
|
if (pathStat.isFile()) {
|
|
36183
36767
|
if (excludeTests && isTestFile2(targetPath)) {
|
|
36184
36768
|
return files;
|
|
36185
36769
|
}
|
|
36186
36770
|
if (fileMatchesLanguage(targetPath, language)) {
|
|
36187
|
-
const relativePath = basePath ?
|
|
36771
|
+
const relativePath = basePath ? relative3(basePath, targetPath) : targetPath;
|
|
36188
36772
|
if (includePatterns && includePatterns.length > 0) {
|
|
36189
36773
|
if (!matchesAnyPattern(relativePath, includePatterns)) {
|
|
36190
36774
|
return files;
|
|
@@ -36196,14 +36780,14 @@ async function collectFiles(targetPath, options = {}) {
|
|
|
36196
36780
|
files.push(targetPath);
|
|
36197
36781
|
}
|
|
36198
36782
|
} else if (pathStat.isDirectory()) {
|
|
36199
|
-
const entries = await
|
|
36783
|
+
const entries = await readdir2(targetPath, { withFileTypes: true });
|
|
36200
36784
|
for (const entry of entries) {
|
|
36201
36785
|
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
36202
36786
|
continue;
|
|
36203
36787
|
if (excludeTests && /^(test|tests|__tests__|spec|__mocks__)$/i.test(entry.name))
|
|
36204
36788
|
continue;
|
|
36205
|
-
const fullPath =
|
|
36206
|
-
const relativePath = basePath ?
|
|
36789
|
+
const fullPath = join2(targetPath, entry.name);
|
|
36790
|
+
const relativePath = basePath ? relative3(basePath, fullPath) : fullPath;
|
|
36207
36791
|
if (excludePatterns && entry.isDirectory()) {
|
|
36208
36792
|
const dirPattern = relativePath + "/";
|
|
36209
36793
|
if (excludePatterns.some((p) => matchesGlob(dirPattern, p) || matchesGlob(relativePath, p))) {
|
|
@@ -36220,7 +36804,8 @@ async function scanFile(filePath, language, analyzeOpts) {
|
|
|
36220
36804
|
const code = readFileSync(filePath, "utf-8");
|
|
36221
36805
|
const result = await analyze(code, filePath, language, {
|
|
36222
36806
|
passOptions: analyzeOpts?.passOptions,
|
|
36223
|
-
disabledPasses: analyzeOpts?.disabledPasses
|
|
36807
|
+
disabledPasses: analyzeOpts?.disabledPasses,
|
|
36808
|
+
...analyzeOpts?.projectProfile !== undefined ? { projectProfile: analyzeOpts.projectProfile } : {}
|
|
36224
36809
|
});
|
|
36225
36810
|
const vulnerabilities = (result.taint.flows || []).map((flow) => ({
|
|
36226
36811
|
type: flow.sink_type,
|
|
@@ -36261,7 +36846,8 @@ async function scanProject(files, language, analyzeOpts, crossFileBudgetMs) {
|
|
|
36261
36846
|
const projectResult = await analyzeProject(filesWithCode, {
|
|
36262
36847
|
passOptions: analyzeOpts?.passOptions,
|
|
36263
36848
|
disabledPasses: analyzeOpts?.disabledPasses,
|
|
36264
|
-
...crossFileBudgetMs !== undefined ? { crossFileBudgetMs } : {}
|
|
36849
|
+
...crossFileBudgetMs !== undefined ? { crossFileBudgetMs } : {},
|
|
36850
|
+
...analyzeOpts?.projectProfile !== undefined ? { projectProfile: analyzeOpts.projectProfile } : {}
|
|
36265
36851
|
});
|
|
36266
36852
|
const results = projectResult.files.map(({ file, analysis }) => {
|
|
36267
36853
|
const vulnerabilities = (analysis.taint.flows || []).map((flow) => ({
|
|
@@ -36299,7 +36885,7 @@ async function scanProject(files, language, analyzeOpts, crossFileBudgetMs) {
|
|
|
36299
36885
|
async function initWasm(spin) {
|
|
36300
36886
|
const isStandalone = import.meta.url.includes("/$bunfs/");
|
|
36301
36887
|
if (isStandalone) {
|
|
36302
|
-
const { dirname: dirname3, join:
|
|
36888
|
+
const { dirname: dirname3, join: join3 } = await import("path");
|
|
36303
36889
|
const binaryDir = dirname3(process.execPath);
|
|
36304
36890
|
const cwd = process.cwd();
|
|
36305
36891
|
let scriptDir = null;
|
|
@@ -36310,34 +36896,34 @@ async function initWasm(spin) {
|
|
|
36310
36896
|
} catch {}
|
|
36311
36897
|
}
|
|
36312
36898
|
const wasmLocations = [
|
|
36313
|
-
|
|
36314
|
-
|
|
36315
|
-
|
|
36899
|
+
join3(binaryDir, "wasm"),
|
|
36900
|
+
join3(cwd, "wasm"),
|
|
36901
|
+
join3(binaryDir, "..", "wasm"),
|
|
36316
36902
|
...scriptDir ? [
|
|
36317
|
-
|
|
36318
|
-
|
|
36319
|
-
|
|
36903
|
+
join3(scriptDir, "wasm"),
|
|
36904
|
+
join3(scriptDir, "..", "wasm"),
|
|
36905
|
+
join3(scriptDir, "..", "node_modules", "circle-ir", "dist", "wasm")
|
|
36320
36906
|
] : []
|
|
36321
36907
|
];
|
|
36322
36908
|
let wasmDir = null;
|
|
36323
36909
|
for (const location of wasmLocations) {
|
|
36324
|
-
if (existsSync(location) && existsSync(
|
|
36910
|
+
if (existsSync(location) && existsSync(join3(location, "web-tree-sitter.wasm"))) {
|
|
36325
36911
|
wasmDir = location;
|
|
36326
36912
|
break;
|
|
36327
36913
|
}
|
|
36328
36914
|
}
|
|
36329
36915
|
if (wasmDir) {
|
|
36330
36916
|
await initAnalyzer({
|
|
36331
|
-
wasmPath:
|
|
36917
|
+
wasmPath: join3(wasmDir, "web-tree-sitter.wasm"),
|
|
36332
36918
|
languagePaths: {
|
|
36333
|
-
bash:
|
|
36334
|
-
go:
|
|
36335
|
-
java:
|
|
36336
|
-
javascript:
|
|
36337
|
-
typescript:
|
|
36338
|
-
python:
|
|
36339
|
-
rust:
|
|
36340
|
-
html:
|
|
36919
|
+
bash: join3(wasmDir, "tree-sitter-bash.wasm"),
|
|
36920
|
+
go: join3(wasmDir, "tree-sitter-go.wasm"),
|
|
36921
|
+
java: join3(wasmDir, "tree-sitter-java.wasm"),
|
|
36922
|
+
javascript: join3(wasmDir, "tree-sitter-javascript.wasm"),
|
|
36923
|
+
typescript: join3(wasmDir, "tree-sitter-javascript.wasm"),
|
|
36924
|
+
python: join3(wasmDir, "tree-sitter-python.wasm"),
|
|
36925
|
+
rust: join3(wasmDir, "tree-sitter-rust.wasm"),
|
|
36926
|
+
html: join3(wasmDir, "tree-sitter-html.wasm")
|
|
36341
36927
|
}
|
|
36342
36928
|
});
|
|
36343
36929
|
} else {
|
|
@@ -36359,7 +36945,7 @@ Please ensure the wasm/ directory is located next to the binary or in your curre
|
|
|
36359
36945
|
} else {
|
|
36360
36946
|
const require2 = createRequire2(import.meta.url);
|
|
36361
36947
|
const circleIrPkg = require2.resolve("circle-ir/package.json");
|
|
36362
|
-
const wasmBasePath =
|
|
36948
|
+
const wasmBasePath = join2(dirname2(circleIrPkg), "dist", "wasm") + "/";
|
|
36363
36949
|
await initAnalyzer({
|
|
36364
36950
|
wasmPath: wasmBasePath + "web-tree-sitter.wasm",
|
|
36365
36951
|
languagePaths: {
|
|
@@ -36375,6 +36961,62 @@ Please ensure the wasm/ directory is located next to the binary or in your curre
|
|
|
36375
36961
|
});
|
|
36376
36962
|
}
|
|
36377
36963
|
}
|
|
36964
|
+
function buildProfileSummary(scanRoot, modules, resolvedProfiles) {
|
|
36965
|
+
if (!resolvedProfiles)
|
|
36966
|
+
return;
|
|
36967
|
+
if (modules.length === 0 && resolvedProfiles.size === 0)
|
|
36968
|
+
return;
|
|
36969
|
+
let unknownFileCount = 0;
|
|
36970
|
+
for (const p of resolvedProfiles.values()) {
|
|
36971
|
+
if (p === "unknown")
|
|
36972
|
+
unknownFileCount++;
|
|
36973
|
+
}
|
|
36974
|
+
return {
|
|
36975
|
+
scanRoot,
|
|
36976
|
+
modules: modules.map((m) => ({
|
|
36977
|
+
root: relative3(scanRoot, m.module.root) || ".",
|
|
36978
|
+
profile: m.profile,
|
|
36979
|
+
reasons: m.reasons,
|
|
36980
|
+
buildSystem: m.module.buildSystem
|
|
36981
|
+
})),
|
|
36982
|
+
unknownFileCount
|
|
36983
|
+
};
|
|
36984
|
+
}
|
|
36985
|
+
function summarizeModules(modules) {
|
|
36986
|
+
if (modules.length === 0)
|
|
36987
|
+
return "no build files detected";
|
|
36988
|
+
const counts = new Map;
|
|
36989
|
+
for (const m of modules) {
|
|
36990
|
+
counts.set(m.profile, (counts.get(m.profile) ?? 0) + 1);
|
|
36991
|
+
}
|
|
36992
|
+
const parts2 = [...counts.entries()].map(([profile, count]) => `${profile}×${count}`).sort();
|
|
36993
|
+
return `${modules.length} module(s): ${parts2.join(", ")}`;
|
|
36994
|
+
}
|
|
36995
|
+
function printProfileExplain(scanRoot, detection) {
|
|
36996
|
+
const out2 = [];
|
|
36997
|
+
out2.push(colors.bold("Project profile detection"));
|
|
36998
|
+
out2.push(` Scan root: ${scanRoot}`);
|
|
36999
|
+
out2.push(` Modules: ${detection.modules.length}`);
|
|
37000
|
+
out2.push(` Unknown files (no enclosing module): ${detection.unknownFiles.length}`);
|
|
37001
|
+
out2.push("");
|
|
37002
|
+
if (detection.modules.length === 0) {
|
|
37003
|
+
out2.push(" (no pom.xml, build.gradle, or build.gradle.kts found)");
|
|
37004
|
+
} else {
|
|
37005
|
+
for (const r of detection.modules) {
|
|
37006
|
+
const rel = relative3(scanRoot, r.module.root) || ".";
|
|
37007
|
+
out2.push(` ${colors.cyan(rel || ".")} → ${colors.bold(r.profile)}`);
|
|
37008
|
+
out2.push(` build: ${r.module.buildSystem} (${relative3(scanRoot, r.module.buildFile)})`);
|
|
37009
|
+
if (r.module.artifactId) {
|
|
37010
|
+
out2.push(` coords: ${r.module.groupId ?? "?"}:${r.module.artifactId}:${r.module.version ?? "?"}`);
|
|
37011
|
+
}
|
|
37012
|
+
out2.push(` reasons: ${r.reasons.join(" → ")}`);
|
|
37013
|
+
out2.push("");
|
|
37014
|
+
}
|
|
37015
|
+
}
|
|
37016
|
+
process.stderr.write(out2.join(`
|
|
37017
|
+
`) + `
|
|
37018
|
+
`);
|
|
37019
|
+
}
|
|
36378
37020
|
async function runScan(targetPath, options) {
|
|
36379
37021
|
const spin = options.quiet ? null : spinner("Initializing analyzer...").start();
|
|
36380
37022
|
const config = loadConfig(options.profile);
|
|
@@ -36428,8 +37070,39 @@ async function runScan(targetPath, options) {
|
|
|
36428
37070
|
}
|
|
36429
37071
|
let results;
|
|
36430
37072
|
let crossFileData;
|
|
36431
|
-
const
|
|
36432
|
-
|
|
37073
|
+
const forcedProfile = options.projectProfile ?? config?.profile;
|
|
37074
|
+
const profileOverrides = config?.profileOverrides;
|
|
37075
|
+
const detectionEnabled = !options.noProjectProfile && (await stat2(absPath)).isDirectory();
|
|
37076
|
+
let resolvedProfiles;
|
|
37077
|
+
let detectedModules = [];
|
|
37078
|
+
if (detectionEnabled) {
|
|
37079
|
+
if (spin)
|
|
37080
|
+
spin.text = "Detecting project profile...";
|
|
37081
|
+
const detection = await detectProjectProfiles(absPath, {
|
|
37082
|
+
forcedProfile,
|
|
37083
|
+
overrides: profileOverrides
|
|
37084
|
+
});
|
|
37085
|
+
resolvedProfiles = detection.profileByFile;
|
|
37086
|
+
detectedModules = detection.modules;
|
|
37087
|
+
if (options.projectProfileExplain) {
|
|
37088
|
+
if (spin)
|
|
37089
|
+
spin.stop();
|
|
37090
|
+
printProfileExplain(absPath, detection);
|
|
37091
|
+
return;
|
|
37092
|
+
}
|
|
37093
|
+
if (!options.quiet && detectedModules.length > 0) {
|
|
37094
|
+
const summary = summarizeModules(detectedModules);
|
|
37095
|
+
console.error(colors.dim(`Project profile: ${summary}`));
|
|
37096
|
+
}
|
|
37097
|
+
} else if (forcedProfile && !options.noProjectProfile) {
|
|
37098
|
+
resolvedProfiles = undefined;
|
|
37099
|
+
}
|
|
37100
|
+
const analyzeOpts = {
|
|
37101
|
+
passOptions,
|
|
37102
|
+
disabledPasses,
|
|
37103
|
+
...resolvedProfiles ? { projectProfile: resolvedProfiles } : forcedProfile && !options.noProjectProfile ? { projectProfile: forcedProfile } : {}
|
|
37104
|
+
};
|
|
37105
|
+
if ((await stat2(absPath)).isDirectory()) {
|
|
36433
37106
|
if (spin)
|
|
36434
37107
|
spin.text = `Running project analysis on ${files.length} file(s)...`;
|
|
36435
37108
|
const projectScan = await scanProject(files, options.language, analyzeOpts, options.crossFileBudgetMs);
|
|
@@ -36441,7 +37114,7 @@ async function runScan(targetPath, options) {
|
|
|
36441
37114
|
results = [];
|
|
36442
37115
|
let processed = 0;
|
|
36443
37116
|
const formatCurrentFile = (file) => {
|
|
36444
|
-
const rel =
|
|
37117
|
+
const rel = relative3(absPath, file) || file;
|
|
36445
37118
|
return rel.length > 80 ? `...${rel.slice(-77)}` : rel;
|
|
36446
37119
|
};
|
|
36447
37120
|
const concurrency = options.threads;
|
|
@@ -36539,17 +37212,27 @@ async function runScan(targetPath, options) {
|
|
|
36539
37212
|
const errors = results.filter((r) => r.error).length;
|
|
36540
37213
|
const crossFilePaths = crossFileData?.taintPaths.length ?? 0;
|
|
36541
37214
|
const shouldOutput = totalVulns > 0 || crossFilePaths > 0 || errors > 0 || options.verbose || options.output || options.format !== "text";
|
|
37215
|
+
const profileSummary = buildProfileSummary(absPath, detectedModules, resolvedProfiles);
|
|
37216
|
+
if (resolvedProfiles) {
|
|
37217
|
+
for (const r of results) {
|
|
37218
|
+
const p = resolvedProfiles.get(r.file);
|
|
37219
|
+
if (!p || p === "unknown")
|
|
37220
|
+
continue;
|
|
37221
|
+
for (const v of r.vulnerabilities)
|
|
37222
|
+
v.profile = p;
|
|
37223
|
+
}
|
|
37224
|
+
}
|
|
36542
37225
|
if (shouldOutput) {
|
|
36543
37226
|
let output;
|
|
36544
37227
|
switch (options.format) {
|
|
36545
37228
|
case "json":
|
|
36546
|
-
output = formatJSON(results, crossFileData);
|
|
37229
|
+
output = formatJSON(results, crossFileData, profileSummary);
|
|
36547
37230
|
break;
|
|
36548
37231
|
case "sarif":
|
|
36549
|
-
output = formatSARIF(results, crossFileData);
|
|
37232
|
+
output = formatSARIF(results, crossFileData, profileSummary);
|
|
36550
37233
|
break;
|
|
36551
37234
|
default:
|
|
36552
|
-
output = formatResults(results, options.verbose, crossFileData);
|
|
37235
|
+
output = formatResults(results, options.verbose, crossFileData, profileSummary);
|
|
36553
37236
|
}
|
|
36554
37237
|
if (options.output) {
|
|
36555
37238
|
const { writeFileSync } = await import("fs");
|
|
@@ -36635,7 +37318,7 @@ async function runMetrics(targetPath, options) {
|
|
|
36635
37318
|
continue;
|
|
36636
37319
|
}
|
|
36637
37320
|
if (spin) {
|
|
36638
|
-
const rel =
|
|
37321
|
+
const rel = relative3(absPath, file) || file;
|
|
36639
37322
|
const maxLen = 80;
|
|
36640
37323
|
const label = rel.length > maxLen ? `...${rel.slice(-(maxLen - 3))}` : rel;
|
|
36641
37324
|
spin.text = `Analyzing ${label}... (${processed}/${totalFiles})`;
|
|
@@ -36680,7 +37363,7 @@ async function runMetrics(targetPath, options) {
|
|
|
36680
37363
|
} else {
|
|
36681
37364
|
const lines = [];
|
|
36682
37365
|
for (const fm of filtered) {
|
|
36683
|
-
const rel =
|
|
37366
|
+
const rel = relative3(absPath, fm.file) || fm.file;
|
|
36684
37367
|
lines.push(rel);
|
|
36685
37368
|
const byCategory = new Map;
|
|
36686
37369
|
for (const m of fm.metrics) {
|
|
@@ -36836,6 +37519,21 @@ function applyFindingsInstrumentation() {
|
|
|
36836
37519
|
setFindingsInstrumentation(true);
|
|
36837
37520
|
}
|
|
36838
37521
|
}
|
|
37522
|
+
function parseProjectProfileArg(raw) {
|
|
37523
|
+
if (raw === undefined || raw === true || raw === "")
|
|
37524
|
+
return;
|
|
37525
|
+
const s = String(raw).toLowerCase();
|
|
37526
|
+
if (s === "unknown")
|
|
37527
|
+
return "unknown";
|
|
37528
|
+
const m = /^(library|application|cli|server|plugin)\/(production|dev|sample|benchmark|test)$/.exec(s);
|
|
37529
|
+
if (!m) {
|
|
37530
|
+
console.error(colors.yellow(`Warning: invalid --project-profile "${s}" — expected one of:
|
|
37531
|
+
` + ` {library,application,cli,server,plugin}/{production,dev,sample,benchmark,test}
|
|
37532
|
+
or "unknown". Falling back to auto-detection.`));
|
|
37533
|
+
return;
|
|
37534
|
+
}
|
|
37535
|
+
return s;
|
|
37536
|
+
}
|
|
36839
37537
|
function parseCrossFileBudgetMs(raw) {
|
|
36840
37538
|
if (raw === undefined || raw === true || raw === "")
|
|
36841
37539
|
return;
|
|
@@ -36908,7 +37606,10 @@ Usage: cognium-dev scan <path> [options]`);
|
|
|
36908
37606
|
excludeCwe: options["exclude-cwe"],
|
|
36909
37607
|
profile: options.profile || options.p,
|
|
36910
37608
|
disablePass: options["disable-pass"],
|
|
36911
|
-
crossFileBudgetMs: parseCrossFileBudgetMs(options["cross-file-budget-ms"])
|
|
37609
|
+
crossFileBudgetMs: parseCrossFileBudgetMs(options["cross-file-budget-ms"]),
|
|
37610
|
+
projectProfile: parseProjectProfileArg(options["project-profile"]),
|
|
37611
|
+
noProjectProfile: options["no-project-profile"] === true,
|
|
37612
|
+
projectProfileExplain: options["project-profile-explain"] === true
|
|
36912
37613
|
};
|
|
36913
37614
|
await runScan(targetPath, scanOptions);
|
|
36914
37615
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.106.0",
|
|
4
4
|
"description": "Static Application Security Testing CLI for detecting security vulnerabilities via taint tracking",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"registry": "https://registry.npmjs.org/"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"circle-ir": "^3.
|
|
68
|
+
"circle-ir": "^3.106.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/node": "^25.5.0",
|