cognium-dev 3.201.0 → 3.203.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 +471 -9
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -3,9 +3,9 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4
4
|
|
|
5
5
|
// src/cli.ts
|
|
6
|
-
import { readFileSync, existsSync } from "fs";
|
|
6
|
+
import { readFileSync, existsSync, writeFileSync } from "fs";
|
|
7
7
|
import { stat as stat2, readdir as readdir2 } from "fs/promises";
|
|
8
|
-
import { join as join2, dirname as dirname3, extname, resolve as resolve2, relative as relative4 } from "path";
|
|
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
10
|
|
|
11
11
|
// ../../node_modules/web-tree-sitter/web-tree-sitter.js
|
|
@@ -45904,6 +45904,341 @@ function deriveProjectRoot(paths) {
|
|
|
45904
45904
|
}
|
|
45905
45905
|
return common.join("/") || "/";
|
|
45906
45906
|
}
|
|
45907
|
+
// ../circle-ir/dist/analysis/sbom.js
|
|
45908
|
+
function purlVersion(spec) {
|
|
45909
|
+
const stripped = spec.trim().replace(/^[\s^~=<>]+/, "");
|
|
45910
|
+
if (/^v?[0-9][\w.\-+]*$/.test(stripped))
|
|
45911
|
+
return stripped;
|
|
45912
|
+
return;
|
|
45913
|
+
}
|
|
45914
|
+
function encodePurlName(name2) {
|
|
45915
|
+
return name2.split("/").map((seg) => encodeURIComponent(seg)).join("/");
|
|
45916
|
+
}
|
|
45917
|
+
function makePurl(ecosystem, name2, version) {
|
|
45918
|
+
const v = purlVersion(version);
|
|
45919
|
+
const path = ecosystem === "maven" ? name2.replace(":", "/") : name2;
|
|
45920
|
+
const base = `pkg:${ecosystem}/${encodePurlName(path)}`;
|
|
45921
|
+
return v ? `${base}@${encodeURIComponent(v)}` : base;
|
|
45922
|
+
}
|
|
45923
|
+
function dep(ecosystem, name2, version, scope) {
|
|
45924
|
+
const v = version.trim() || "unknown";
|
|
45925
|
+
return { name: name2, version: v, ecosystem, scope, purl: makePurl(ecosystem, name2, v) };
|
|
45926
|
+
}
|
|
45927
|
+
function parseNpmDependencies(packageJson) {
|
|
45928
|
+
if (!packageJson)
|
|
45929
|
+
return [];
|
|
45930
|
+
let parsed;
|
|
45931
|
+
try {
|
|
45932
|
+
parsed = JSON.parse(packageJson);
|
|
45933
|
+
} catch {
|
|
45934
|
+
return [];
|
|
45935
|
+
}
|
|
45936
|
+
if (!parsed || typeof parsed !== "object")
|
|
45937
|
+
return [];
|
|
45938
|
+
const obj = parsed;
|
|
45939
|
+
const out2 = [];
|
|
45940
|
+
const sections = [
|
|
45941
|
+
["dependencies", "required"],
|
|
45942
|
+
["optionalDependencies", "optional"],
|
|
45943
|
+
["peerDependencies", "optional"],
|
|
45944
|
+
["devDependencies", "dev"]
|
|
45945
|
+
];
|
|
45946
|
+
for (const [key, scope] of sections) {
|
|
45947
|
+
const block = obj[key];
|
|
45948
|
+
if (!block || typeof block !== "object")
|
|
45949
|
+
continue;
|
|
45950
|
+
for (const [name2, spec] of Object.entries(block)) {
|
|
45951
|
+
out2.push(dep("npm", name2, typeof spec === "string" ? spec : "unknown", scope));
|
|
45952
|
+
}
|
|
45953
|
+
}
|
|
45954
|
+
return out2;
|
|
45955
|
+
}
|
|
45956
|
+
function parsePep508(spec) {
|
|
45957
|
+
let s = spec.trim();
|
|
45958
|
+
s = s.split(";")[0].trim();
|
|
45959
|
+
if (!s)
|
|
45960
|
+
return null;
|
|
45961
|
+
const m = s.match(/^([A-Za-z0-9._-]+)\s*(?:\[[^\]]*\])?\s*(.*)$/);
|
|
45962
|
+
if (!m)
|
|
45963
|
+
return null;
|
|
45964
|
+
const rest = m[2].trim();
|
|
45965
|
+
const verMatch = rest.replace(/^\(/, "").match(/^(?:==|>=|<=|~=|!=|>|<|===)?\s*([^,\s)]+)/);
|
|
45966
|
+
const version = verMatch && verMatch[1] ? verMatch[1] : "unknown";
|
|
45967
|
+
return { name: m[1], version };
|
|
45968
|
+
}
|
|
45969
|
+
function parsePypiDependencies(requirementsTxt) {
|
|
45970
|
+
if (!requirementsTxt)
|
|
45971
|
+
return [];
|
|
45972
|
+
const out2 = [];
|
|
45973
|
+
for (const raw of requirementsTxt.split(/\r?\n/)) {
|
|
45974
|
+
let line = raw.trim();
|
|
45975
|
+
if (!line || line.startsWith("#"))
|
|
45976
|
+
continue;
|
|
45977
|
+
if (line.startsWith("-") || /^[a-z]+:\/\//i.test(line))
|
|
45978
|
+
continue;
|
|
45979
|
+
line = line.split("#")[0].trim();
|
|
45980
|
+
if (!line)
|
|
45981
|
+
continue;
|
|
45982
|
+
const parsed = parsePep508(line);
|
|
45983
|
+
if (parsed)
|
|
45984
|
+
out2.push(dep("pypi", parsed.name, parsed.version, "required"));
|
|
45985
|
+
}
|
|
45986
|
+
return out2;
|
|
45987
|
+
}
|
|
45988
|
+
function parsePyprojectDependencies(pyprojectToml) {
|
|
45989
|
+
if (!pyprojectToml)
|
|
45990
|
+
return [];
|
|
45991
|
+
const out2 = [];
|
|
45992
|
+
const lines = pyprojectToml.split(/\r?\n/);
|
|
45993
|
+
let table = "";
|
|
45994
|
+
let poetryScope = null;
|
|
45995
|
+
let inArray = false;
|
|
45996
|
+
let arrayScope = "required";
|
|
45997
|
+
const pushReq = (raw, scope) => {
|
|
45998
|
+
const cleaned = raw.trim().replace(/^["']|["'],?$/g, "").replace(/,$/, "").trim();
|
|
45999
|
+
if (!cleaned)
|
|
46000
|
+
return;
|
|
46001
|
+
const parsed = parsePep508(cleaned);
|
|
46002
|
+
if (parsed && parsed.name.toLowerCase() !== "python")
|
|
46003
|
+
out2.push(dep("pypi", parsed.name, parsed.version, scope));
|
|
46004
|
+
};
|
|
46005
|
+
for (const raw of lines) {
|
|
46006
|
+
const line = raw.trim();
|
|
46007
|
+
if (!line || line.startsWith("#"))
|
|
46008
|
+
continue;
|
|
46009
|
+
if (inArray) {
|
|
46010
|
+
if (line.startsWith("]")) {
|
|
46011
|
+
inArray = false;
|
|
46012
|
+
continue;
|
|
46013
|
+
}
|
|
46014
|
+
pushReq(line, arrayScope);
|
|
46015
|
+
continue;
|
|
46016
|
+
}
|
|
46017
|
+
const header = line.match(/^\[([^\]]+)\]$/);
|
|
46018
|
+
if (header) {
|
|
46019
|
+
table = header[1];
|
|
46020
|
+
if (table === "tool.poetry.dependencies")
|
|
46021
|
+
poetryScope = "required";
|
|
46022
|
+
else if (/^tool\.poetry\.(dev-dependencies|group\..+\.dependencies)$/.test(table))
|
|
46023
|
+
poetryScope = "dev";
|
|
46024
|
+
else
|
|
46025
|
+
poetryScope = null;
|
|
46026
|
+
continue;
|
|
46027
|
+
}
|
|
46028
|
+
const arrayOpen = line.match(/^(dependencies|[A-Za-z0-9._-]+)\s*=\s*\[(.*)$/);
|
|
46029
|
+
if (arrayOpen && (arrayOpen[1] === "dependencies" || table === "project.optional-dependencies")) {
|
|
46030
|
+
arrayScope = arrayOpen[1] === "dependencies" ? "required" : "optional";
|
|
46031
|
+
const inline = arrayOpen[2];
|
|
46032
|
+
if (inline.includes("]")) {
|
|
46033
|
+
for (const item of inline.slice(0, inline.indexOf("]")).split(","))
|
|
46034
|
+
pushReq(item, arrayScope);
|
|
46035
|
+
} else {
|
|
46036
|
+
inArray = true;
|
|
46037
|
+
if (inline.trim())
|
|
46038
|
+
pushReq(inline, arrayScope);
|
|
46039
|
+
}
|
|
46040
|
+
continue;
|
|
46041
|
+
}
|
|
46042
|
+
if (poetryScope) {
|
|
46043
|
+
const kv = line.match(/^([A-Za-z0-9._-]+)\s*=\s*(.+)$/);
|
|
46044
|
+
if (!kv || kv[1].toLowerCase() === "python")
|
|
46045
|
+
continue;
|
|
46046
|
+
let version = "unknown";
|
|
46047
|
+
const strv = kv[2].match(/^["']([^"']+)["']/);
|
|
46048
|
+
if (strv)
|
|
46049
|
+
version = strv[1];
|
|
46050
|
+
else {
|
|
46051
|
+
const inlinev = kv[2].match(/version\s*=\s*["']([^"']+)["']/);
|
|
46052
|
+
if (inlinev)
|
|
46053
|
+
version = inlinev[1];
|
|
46054
|
+
}
|
|
46055
|
+
out2.push(dep("pypi", kv[1], version, poetryScope));
|
|
46056
|
+
}
|
|
46057
|
+
}
|
|
46058
|
+
return out2;
|
|
46059
|
+
}
|
|
46060
|
+
function parseGradleDependencies(buildGradle) {
|
|
46061
|
+
if (!buildGradle)
|
|
46062
|
+
return [];
|
|
46063
|
+
const out2 = [];
|
|
46064
|
+
const re = /\b(implementation|api|compileOnly|compileOnlyApi|runtimeOnly|testImplementation|testCompileOnly|testRuntimeOnly|annotationProcessor|kapt|classpath)\s*[(\s]\s*['"]([\w.-]+:[\w.-]+(?::[\w.\-+]+)?)['"]/g;
|
|
46065
|
+
let m;
|
|
46066
|
+
while ((m = re.exec(buildGradle)) !== null) {
|
|
46067
|
+
const config = m[1];
|
|
46068
|
+
const coord = m[2].split(":");
|
|
46069
|
+
if (coord.length < 2)
|
|
46070
|
+
continue;
|
|
46071
|
+
const name2 = `${coord[0]}:${coord[1]}`;
|
|
46072
|
+
const version = coord[2] ?? "unknown";
|
|
46073
|
+
const scope = config.startsWith("test") ? "dev" : "required";
|
|
46074
|
+
out2.push(dep("maven", name2, version, scope));
|
|
46075
|
+
}
|
|
46076
|
+
return out2;
|
|
46077
|
+
}
|
|
46078
|
+
function parseMavenDependencies(pomXml) {
|
|
46079
|
+
if (!pomXml)
|
|
46080
|
+
return [];
|
|
46081
|
+
const out2 = [];
|
|
46082
|
+
const depRe = /<dependency>([\s\S]*?)<\/dependency>/g;
|
|
46083
|
+
let m;
|
|
46084
|
+
while ((m = depRe.exec(pomXml)) !== null) {
|
|
46085
|
+
const block = m[1];
|
|
46086
|
+
const gid = block.match(/<groupId>\s*([^<\s]+)\s*<\/groupId>/)?.[1];
|
|
46087
|
+
const aid = block.match(/<artifactId>\s*([^<\s]+)\s*<\/artifactId>/)?.[1];
|
|
46088
|
+
if (!gid || !aid)
|
|
46089
|
+
continue;
|
|
46090
|
+
let version = block.match(/<version>\s*([^<\s]+)\s*<\/version>/)?.[1] ?? "unknown";
|
|
46091
|
+
if (version.startsWith("${"))
|
|
46092
|
+
version = "unknown";
|
|
46093
|
+
const mvnScope = block.match(/<scope>\s*([^<\s]+)\s*<\/scope>/)?.[1];
|
|
46094
|
+
const optional = /<optional>\s*true\s*<\/optional>/.test(block);
|
|
46095
|
+
const scope = mvnScope === "test" || mvnScope === "provided" ? "dev" : optional ? "optional" : "required";
|
|
46096
|
+
out2.push(dep("maven", `${gid}:${aid}`, version, scope));
|
|
46097
|
+
}
|
|
46098
|
+
return out2;
|
|
46099
|
+
}
|
|
46100
|
+
function parseCargoDependencies(cargoToml) {
|
|
46101
|
+
if (!cargoToml)
|
|
46102
|
+
return [];
|
|
46103
|
+
const out2 = [];
|
|
46104
|
+
let scope = null;
|
|
46105
|
+
for (const raw of cargoToml.split(/\r?\n/)) {
|
|
46106
|
+
const line = raw.trim();
|
|
46107
|
+
if (!line || line.startsWith("#"))
|
|
46108
|
+
continue;
|
|
46109
|
+
const section = line.match(/^\[([^\]]+)\]$/);
|
|
46110
|
+
if (section) {
|
|
46111
|
+
const s = section[1];
|
|
46112
|
+
if (s === "dependencies")
|
|
46113
|
+
scope = "required";
|
|
46114
|
+
else if (s === "dev-dependencies")
|
|
46115
|
+
scope = "dev";
|
|
46116
|
+
else if (s === "build-dependencies")
|
|
46117
|
+
scope = "optional";
|
|
46118
|
+
else
|
|
46119
|
+
scope = null;
|
|
46120
|
+
continue;
|
|
46121
|
+
}
|
|
46122
|
+
if (!scope)
|
|
46123
|
+
continue;
|
|
46124
|
+
const kv = line.match(/^([A-Za-z0-9._-]+)\s*=\s*(.+)$/);
|
|
46125
|
+
if (!kv)
|
|
46126
|
+
continue;
|
|
46127
|
+
const name2 = kv[1];
|
|
46128
|
+
const value = kv[2].trim();
|
|
46129
|
+
let version = "unknown";
|
|
46130
|
+
if (value.startsWith('"') || value.startsWith("'")) {
|
|
46131
|
+
version = value.replace(/^["']|["'].*$/g, "");
|
|
46132
|
+
} else {
|
|
46133
|
+
const inline = value.match(/version\s*=\s*["']([^"']+)["']/);
|
|
46134
|
+
if (inline)
|
|
46135
|
+
version = inline[1];
|
|
46136
|
+
}
|
|
46137
|
+
out2.push(dep("cargo", name2, version, scope));
|
|
46138
|
+
}
|
|
46139
|
+
return out2;
|
|
46140
|
+
}
|
|
46141
|
+
function parseGoDependencies(goMod) {
|
|
46142
|
+
if (!goMod)
|
|
46143
|
+
return [];
|
|
46144
|
+
const out2 = [];
|
|
46145
|
+
let inBlock = false;
|
|
46146
|
+
for (const raw of goMod.split(/\r?\n/)) {
|
|
46147
|
+
const line = raw.trim();
|
|
46148
|
+
if (!line || line.startsWith("//"))
|
|
46149
|
+
continue;
|
|
46150
|
+
if (!inBlock && /^require\s*\($/.test(line)) {
|
|
46151
|
+
inBlock = true;
|
|
46152
|
+
continue;
|
|
46153
|
+
}
|
|
46154
|
+
if (inBlock && line === ")") {
|
|
46155
|
+
inBlock = false;
|
|
46156
|
+
continue;
|
|
46157
|
+
}
|
|
46158
|
+
let spec;
|
|
46159
|
+
if (inBlock)
|
|
46160
|
+
spec = line;
|
|
46161
|
+
else if (/^require\s+/.test(line))
|
|
46162
|
+
spec = line.replace(/^require\s+/, "");
|
|
46163
|
+
else
|
|
46164
|
+
continue;
|
|
46165
|
+
const indirect = /\/\/\s*indirect/.test(spec);
|
|
46166
|
+
spec = spec.replace(/\/\/.*$/, "").trim();
|
|
46167
|
+
const m = spec.match(/^(\S+)\s+(\S+)$/);
|
|
46168
|
+
if (!m)
|
|
46169
|
+
continue;
|
|
46170
|
+
out2.push(dep("golang", m[1], m[2], indirect ? "optional" : "required"));
|
|
46171
|
+
}
|
|
46172
|
+
return out2;
|
|
46173
|
+
}
|
|
46174
|
+
function toCycloneDx(deps, meta = {}) {
|
|
46175
|
+
const bom = {
|
|
46176
|
+
bomFormat: "CycloneDX",
|
|
46177
|
+
specVersion: "1.5",
|
|
46178
|
+
version: 1
|
|
46179
|
+
};
|
|
46180
|
+
if (meta.serialNumber)
|
|
46181
|
+
bom.serialNumber = meta.serialNumber;
|
|
46182
|
+
const metadata2 = {};
|
|
46183
|
+
if (meta.timestamp)
|
|
46184
|
+
metadata2.timestamp = meta.timestamp;
|
|
46185
|
+
metadata2.tools = [{ vendor: "Cognium", name: meta.tool ?? "circle-ir" }];
|
|
46186
|
+
if (meta.name) {
|
|
46187
|
+
metadata2.component = {
|
|
46188
|
+
type: "application",
|
|
46189
|
+
name: meta.name,
|
|
46190
|
+
...meta.version ? { version: meta.version } : {}
|
|
46191
|
+
};
|
|
46192
|
+
}
|
|
46193
|
+
bom.metadata = metadata2;
|
|
46194
|
+
bom.components = deps.map((d) => ({
|
|
46195
|
+
type: "library",
|
|
46196
|
+
name: d.name,
|
|
46197
|
+
version: d.version,
|
|
46198
|
+
purl: d.purl,
|
|
46199
|
+
scope: d.scope === "dev" ? "excluded" : d.scope === "optional" ? "optional" : "required",
|
|
46200
|
+
"bom-ref": d.purl
|
|
46201
|
+
}));
|
|
46202
|
+
return bom;
|
|
46203
|
+
}
|
|
46204
|
+
function spdxId(raw) {
|
|
46205
|
+
return raw.replace(/[^a-zA-Z0-9.-]/g, "-");
|
|
46206
|
+
}
|
|
46207
|
+
function toSpdx(deps, meta = {}) {
|
|
46208
|
+
const docName = meta.name ?? "document";
|
|
46209
|
+
const packages = deps.map((d, i2) => ({
|
|
46210
|
+
SPDXID: `SPDXRef-Package-${spdxId(d.ecosystem)}-${spdxId(d.name)}-${i2}`,
|
|
46211
|
+
name: d.name,
|
|
46212
|
+
versionInfo: d.version,
|
|
46213
|
+
downloadLocation: "NOASSERTION",
|
|
46214
|
+
filesAnalyzed: false,
|
|
46215
|
+
externalRefs: [
|
|
46216
|
+
{
|
|
46217
|
+
referenceCategory: "PACKAGE-MANAGER",
|
|
46218
|
+
referenceType: "purl",
|
|
46219
|
+
referenceLocator: d.purl
|
|
46220
|
+
}
|
|
46221
|
+
]
|
|
46222
|
+
}));
|
|
46223
|
+
const relationships = packages.map((p) => ({
|
|
46224
|
+
spdxElementId: "SPDXRef-DOCUMENT",
|
|
46225
|
+
relatedSpdxElement: p.SPDXID,
|
|
46226
|
+
relationshipType: "DESCRIBES"
|
|
46227
|
+
}));
|
|
46228
|
+
return {
|
|
46229
|
+
spdxVersion: "SPDX-2.3",
|
|
46230
|
+
dataLicense: "CC0-1.0",
|
|
46231
|
+
SPDXID: "SPDXRef-DOCUMENT",
|
|
46232
|
+
name: docName,
|
|
46233
|
+
documentNamespace: meta.namespace ?? `https://cognium.dev/spdxdocs/${spdxId(docName)}`,
|
|
46234
|
+
creationInfo: {
|
|
46235
|
+
created: meta.timestamp ?? "1970-01-01T00:00:00Z",
|
|
46236
|
+
creators: [`Tool: ${meta.tool ?? "circle-ir"}`]
|
|
46237
|
+
},
|
|
46238
|
+
packages,
|
|
46239
|
+
relationships
|
|
46240
|
+
};
|
|
46241
|
+
}
|
|
45907
46242
|
// ../project-profile-detect/dist/index.js
|
|
45908
46243
|
import { relative as relative3 } from "path";
|
|
45909
46244
|
|
|
@@ -46515,7 +46850,7 @@ var colors = {
|
|
|
46515
46850
|
};
|
|
46516
46851
|
|
|
46517
46852
|
// src/version.ts
|
|
46518
|
-
var version = "3.
|
|
46853
|
+
var version = "3.203.0";
|
|
46519
46854
|
|
|
46520
46855
|
// src/formatters.ts
|
|
46521
46856
|
var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
|
|
@@ -47116,6 +47451,7 @@ USAGE:
|
|
|
47116
47451
|
COMMANDS:
|
|
47117
47452
|
scan <path> Scan files or directories for security vulnerabilities
|
|
47118
47453
|
metrics <path> Report software quality metrics for files or directories
|
|
47454
|
+
sbom <path> Generate a Software Bill of Materials from project manifests
|
|
47119
47455
|
list-passes [cat] List all analysis passes (optionally filter by category)
|
|
47120
47456
|
init Initialize a configuration file in your project
|
|
47121
47457
|
version Display version information
|
|
@@ -47164,6 +47500,18 @@ METRICS OPTIONS:
|
|
|
47164
47500
|
-o, --output <file> Write results to file
|
|
47165
47501
|
-q, --quiet Suppress progress output
|
|
47166
47502
|
|
|
47503
|
+
SBOM OPTIONS:
|
|
47504
|
+
-f, --format <format> SBOM format (cyclonedx|spdx) [default: cyclonedx]
|
|
47505
|
+
- cyclonedx: CycloneDX 1.5 JSON (OWASP)
|
|
47506
|
+
- spdx: SPDX 2.3 JSON (Linux Foundation)
|
|
47507
|
+
--name <name> Project name in the document [default: package.json name or dir]
|
|
47508
|
+
--prod-only Exclude dev/test dependencies
|
|
47509
|
+
--deterministic Omit timestamp / serialNumber / namespace for reproducible output
|
|
47510
|
+
-o, --output <file> Write the document to a file (default: stdout)
|
|
47511
|
+
Discovers package.json / requirements.txt / pyproject.toml /
|
|
47512
|
+
pom.xml / build.gradle(.kts) / Cargo.toml / go.mod.
|
|
47513
|
+
CVE matching is out of scope (deterministic SAST — no network).
|
|
47514
|
+
|
|
47167
47515
|
EXAMPLES:
|
|
47168
47516
|
cognium-dev scan src/
|
|
47169
47517
|
cognium-dev scan app.java -f json -o results.json
|
|
@@ -47181,6 +47529,9 @@ EXAMPLES:
|
|
|
47181
47529
|
cognium-dev metrics src/
|
|
47182
47530
|
cognium-dev metrics src/ --category complexity
|
|
47183
47531
|
cognium-dev metrics src/ --format json --profile custom-config.json
|
|
47532
|
+
cognium-dev sbom . # CycloneDX 1.5 to stdout
|
|
47533
|
+
cognium-dev sbom . -f spdx -o sbom.spdx.json # SPDX 2.3 to a file
|
|
47534
|
+
cognium-dev sbom . --prod-only --deterministic # reproducible, no dev deps
|
|
47184
47535
|
cognium-dev list-passes
|
|
47185
47536
|
cognium-dev list-passes reliability
|
|
47186
47537
|
cognium-dev init
|
|
@@ -47886,8 +48237,8 @@ async function runScan(targetPath, options) {
|
|
|
47886
48237
|
output = formatResults(results, options.verbose, crossFileData, profileSummary);
|
|
47887
48238
|
}
|
|
47888
48239
|
if (options.output) {
|
|
47889
|
-
const { writeFileSync } = await import("fs");
|
|
47890
|
-
|
|
48240
|
+
const { writeFileSync: writeFileSync2 } = await import("fs");
|
|
48241
|
+
writeFileSync2(options.output, output);
|
|
47891
48242
|
console.error(colors.green(`Results written to ${options.output}`));
|
|
47892
48243
|
} else if (output.trim()) {
|
|
47893
48244
|
console.log(output);
|
|
@@ -48040,8 +48391,8 @@ async function runMetrics(targetPath, options) {
|
|
|
48040
48391
|
`);
|
|
48041
48392
|
}
|
|
48042
48393
|
if (options.output) {
|
|
48043
|
-
const { writeFileSync } = await import("fs");
|
|
48044
|
-
|
|
48394
|
+
const { writeFileSync: writeFileSync2 } = await import("fs");
|
|
48395
|
+
writeFileSync2(options.output, output);
|
|
48045
48396
|
console.error(colors.green(`Results written to ${options.output}`));
|
|
48046
48397
|
} else {
|
|
48047
48398
|
console.log(output);
|
|
@@ -48149,8 +48500,8 @@ async function handleInit() {
|
|
|
48149
48500
|
severity: "low",
|
|
48150
48501
|
categories: ["security", "reliability", "performance", "maintainability", "architecture"]
|
|
48151
48502
|
};
|
|
48152
|
-
const { writeFileSync } = await import("fs");
|
|
48153
|
-
|
|
48503
|
+
const { writeFileSync: writeFileSync2 } = await import("fs");
|
|
48504
|
+
writeFileSync2(configPath, JSON.stringify(config, null, 2));
|
|
48154
48505
|
console.log(colors.green(`Created ${configPath}`));
|
|
48155
48506
|
}
|
|
48156
48507
|
function applyLogLevel(cliValue) {
|
|
@@ -48196,6 +48547,95 @@ function parseCrossFileBudgetMs(raw) {
|
|
|
48196
48547
|
}
|
|
48197
48548
|
return n;
|
|
48198
48549
|
}
|
|
48550
|
+
var SBOM_MANIFESTS = {
|
|
48551
|
+
"package.json": parseNpmDependencies,
|
|
48552
|
+
"requirements.txt": parsePypiDependencies,
|
|
48553
|
+
"pyproject.toml": parsePyprojectDependencies,
|
|
48554
|
+
"pom.xml": parseMavenDependencies,
|
|
48555
|
+
"build.gradle": parseGradleDependencies,
|
|
48556
|
+
"build.gradle.kts": parseGradleDependencies,
|
|
48557
|
+
"Cargo.toml": parseCargoDependencies,
|
|
48558
|
+
"go.mod": parseGoDependencies
|
|
48559
|
+
};
|
|
48560
|
+
var SBOM_SKIP_DIRS = /^(node_modules|vendor|target|dist|build|out|coverage)$/;
|
|
48561
|
+
async function collectManifestFiles(targetPath) {
|
|
48562
|
+
const found = [];
|
|
48563
|
+
const pathStat = await stat2(targetPath);
|
|
48564
|
+
if (pathStat.isFile()) {
|
|
48565
|
+
if (SBOM_MANIFESTS[basename(targetPath)])
|
|
48566
|
+
found.push(targetPath);
|
|
48567
|
+
return found;
|
|
48568
|
+
}
|
|
48569
|
+
const walk2 = async (dir) => {
|
|
48570
|
+
for (const e of await readdir2(dir, { withFileTypes: true })) {
|
|
48571
|
+
if (e.name.startsWith("."))
|
|
48572
|
+
continue;
|
|
48573
|
+
if (e.isDirectory()) {
|
|
48574
|
+
if (SBOM_SKIP_DIRS.test(e.name))
|
|
48575
|
+
continue;
|
|
48576
|
+
await walk2(join2(dir, e.name));
|
|
48577
|
+
} else if (SBOM_MANIFESTS[e.name]) {
|
|
48578
|
+
found.push(join2(dir, e.name));
|
|
48579
|
+
}
|
|
48580
|
+
}
|
|
48581
|
+
};
|
|
48582
|
+
await walk2(targetPath);
|
|
48583
|
+
return found;
|
|
48584
|
+
}
|
|
48585
|
+
async function runSbom(targetPath, options) {
|
|
48586
|
+
const absPath = resolve2(targetPath);
|
|
48587
|
+
if (!existsSync(absPath)) {
|
|
48588
|
+
console.error(colors.red(`Error: path not found: ${targetPath}`));
|
|
48589
|
+
process.exit(2);
|
|
48590
|
+
}
|
|
48591
|
+
const manifests = await collectManifestFiles(absPath);
|
|
48592
|
+
if (manifests.length === 0) {
|
|
48593
|
+
console.error(colors.red("Error: no supported manifests found (package.json, requirements.txt, pyproject.toml, pom.xml, build.gradle, Cargo.toml, go.mod)"));
|
|
48594
|
+
process.exit(1);
|
|
48595
|
+
}
|
|
48596
|
+
let deps = [];
|
|
48597
|
+
let projectName = options.name;
|
|
48598
|
+
for (const m of manifests) {
|
|
48599
|
+
const content = readFileSync(m, "utf-8");
|
|
48600
|
+
deps.push(...SBOM_MANIFESTS[basename(m)](content));
|
|
48601
|
+
if (!projectName && basename(m) === "package.json") {
|
|
48602
|
+
try {
|
|
48603
|
+
const n = JSON.parse(content).name;
|
|
48604
|
+
if (typeof n === "string" && n)
|
|
48605
|
+
projectName = n;
|
|
48606
|
+
} catch {}
|
|
48607
|
+
}
|
|
48608
|
+
}
|
|
48609
|
+
const seen = new Set;
|
|
48610
|
+
deps = deps.filter((d) => {
|
|
48611
|
+
const key = `${d.ecosystem}|${d.name}|${d.version}|${d.scope}`;
|
|
48612
|
+
if (seen.has(key))
|
|
48613
|
+
return false;
|
|
48614
|
+
seen.add(key);
|
|
48615
|
+
return true;
|
|
48616
|
+
});
|
|
48617
|
+
if (options.prodOnly)
|
|
48618
|
+
deps = deps.filter((d) => d.scope !== "dev");
|
|
48619
|
+
if (!projectName)
|
|
48620
|
+
projectName = basename(absPath) || "project";
|
|
48621
|
+
const meta = { name: projectName, tool: "cognium-dev" };
|
|
48622
|
+
if (!options.deterministic) {
|
|
48623
|
+
meta.timestamp = new Date().toISOString();
|
|
48624
|
+
const { randomUUID } = await import("crypto");
|
|
48625
|
+
meta.serialNumber = `urn:uuid:${randomUUID()}`;
|
|
48626
|
+
meta.namespace = `https://cognium.dev/spdxdocs/${projectName}-${randomUUID()}`;
|
|
48627
|
+
}
|
|
48628
|
+
const doc = options.format === "spdx" ? toSpdx(deps, meta) : toCycloneDx(deps, meta);
|
|
48629
|
+
const out2 = JSON.stringify(doc, null, 2);
|
|
48630
|
+
if (options.output) {
|
|
48631
|
+
writeFileSync(options.output, out2);
|
|
48632
|
+
console.error(colors.green(`SBOM written to ${options.output} — ${deps.length} dependencies from ${manifests.length} manifest(s)`));
|
|
48633
|
+
} else {
|
|
48634
|
+
console.log(out2);
|
|
48635
|
+
console.error(colors.green(`SBOM: ${deps.length} dependencies from ${manifests.length} manifest(s)`));
|
|
48636
|
+
}
|
|
48637
|
+
process.exit(0);
|
|
48638
|
+
}
|
|
48199
48639
|
async function main() {
|
|
48200
48640
|
const { command, args: args2, options } = parseArgs(process.argv.slice(2));
|
|
48201
48641
|
applyLogLevel(options["log-level"]);
|
|
@@ -48236,6 +48676,28 @@ Usage: cognium-dev metrics <path> [options]`);
|
|
|
48236
48676
|
await runMetrics(targetPath, metricsOptions);
|
|
48237
48677
|
return;
|
|
48238
48678
|
}
|
|
48679
|
+
if (command === "sbom") {
|
|
48680
|
+
if (args2.length === 0) {
|
|
48681
|
+
console.error(colors.red("Error: sbom command requires a path argument"));
|
|
48682
|
+
console.error(`
|
|
48683
|
+
Usage: cognium-dev sbom <path> [--format cyclonedx|spdx] [--output <file>]`);
|
|
48684
|
+
process.exit(1);
|
|
48685
|
+
}
|
|
48686
|
+
const rawFormat = (options.format || options.f || "cyclonedx").toLowerCase();
|
|
48687
|
+
if (rawFormat !== "cyclonedx" && rawFormat !== "spdx") {
|
|
48688
|
+
console.error(colors.red(`Error: unknown SBOM format '${rawFormat}' (expected 'cyclonedx' or 'spdx')`));
|
|
48689
|
+
process.exit(1);
|
|
48690
|
+
}
|
|
48691
|
+
const sbomOptions = {
|
|
48692
|
+
format: rawFormat,
|
|
48693
|
+
output: options.output || options.o,
|
|
48694
|
+
name: options.name,
|
|
48695
|
+
prodOnly: options["prod-only"] === true || options.prod === true,
|
|
48696
|
+
deterministic: options.deterministic === true
|
|
48697
|
+
};
|
|
48698
|
+
await runSbom(args2[0], sbomOptions);
|
|
48699
|
+
return;
|
|
48700
|
+
}
|
|
48239
48701
|
if (command === "scan") {
|
|
48240
48702
|
if (args2.length === 0) {
|
|
48241
48703
|
console.error(colors.red("Error: scan command requires a path argument"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.203.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",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@cognium/project-profile-detect": "^1.1.0",
|
|
69
|
-
"circle-ir": "^3.
|
|
69
|
+
"circle-ir": "^3.203.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/node": "^25.5.0",
|