@aisdlc/core 2.2.4 → 2.3.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/index.cjs +460 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -2
- package/dist/index.d.ts +77 -2
- package/dist/index.js +450 -39
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5685,6 +5685,214 @@ function scanPnpmStore(pnpmDir, directNames, seen) {
|
|
|
5685
5685
|
}
|
|
5686
5686
|
}
|
|
5687
5687
|
}
|
|
5688
|
+
function readTextIfExists(filePath) {
|
|
5689
|
+
if (!fs7__namespace.existsSync(filePath)) return null;
|
|
5690
|
+
try {
|
|
5691
|
+
return fs7__namespace.readFileSync(filePath, "utf-8");
|
|
5692
|
+
} catch {
|
|
5693
|
+
return null;
|
|
5694
|
+
}
|
|
5695
|
+
}
|
|
5696
|
+
function extractTomlField(block, key) {
|
|
5697
|
+
const m = block.match(new RegExp(`^\\s*${key}\\s*=\\s*["']([^"']+)["']`, "m"));
|
|
5698
|
+
return m ? m[1].trim() : void 0;
|
|
5699
|
+
}
|
|
5700
|
+
function extractPyprojectDirectDeps(content) {
|
|
5701
|
+
const direct = /* @__PURE__ */ new Set();
|
|
5702
|
+
const matches = content.matchAll(/^([a-zA-Z0-9_.-]+)\s*=\s*(?:["'{]|true)/gm);
|
|
5703
|
+
for (const m of matches) {
|
|
5704
|
+
const key = m[1].toLowerCase();
|
|
5705
|
+
if (!["name", "version", "description", "license", "python", "readme"].includes(key)) {
|
|
5706
|
+
direct.add(key);
|
|
5707
|
+
}
|
|
5708
|
+
}
|
|
5709
|
+
return direct;
|
|
5710
|
+
}
|
|
5711
|
+
function parseSingleTomlBlock(block, lockPath, directNames) {
|
|
5712
|
+
const name = extractTomlField(block, "name");
|
|
5713
|
+
const version = extractTomlField(block, "version");
|
|
5714
|
+
if (!name || !version) return null;
|
|
5715
|
+
const spdx = normalizeSpdxLicense(extractTomlField(block, "license") || "UNKNOWN");
|
|
5716
|
+
return {
|
|
5717
|
+
name,
|
|
5718
|
+
version,
|
|
5719
|
+
spdxLicense: spdx,
|
|
5720
|
+
purl: `pkg:pypi/${name.toLowerCase()}@${version}`,
|
|
5721
|
+
path: lockPath,
|
|
5722
|
+
isDirect: directNames.size === 0 || directNames.has(name.toLowerCase())
|
|
5723
|
+
};
|
|
5724
|
+
}
|
|
5725
|
+
function parseTomlPackageBlocks(content, lockPath, directNames, seen) {
|
|
5726
|
+
for (const block of content.split(/\[\[package\]\]/g).slice(1)) {
|
|
5727
|
+
const dep = parseSingleTomlBlock(block, lockPath, directNames);
|
|
5728
|
+
if (dep && !seen.has(`pypi:${dep.name}@${dep.version}`)) {
|
|
5729
|
+
seen.set(`pypi:${dep.name}@${dep.version}`, dep);
|
|
5730
|
+
}
|
|
5731
|
+
}
|
|
5732
|
+
}
|
|
5733
|
+
function parsePipfileSection(pkgs, isDirect, lockPath, seen) {
|
|
5734
|
+
for (const [name, meta] of Object.entries(pkgs)) {
|
|
5735
|
+
const rawVer = String(meta.version || "").replace(/^==/, "") || "unknown";
|
|
5736
|
+
const spdx = normalizeSpdxLicense(meta.license || "UNKNOWN");
|
|
5737
|
+
const key = `pypi:${name}@${rawVer}`;
|
|
5738
|
+
if (!seen.has(key)) {
|
|
5739
|
+
seen.set(key, {
|
|
5740
|
+
name,
|
|
5741
|
+
version: rawVer,
|
|
5742
|
+
spdxLicense: spdx,
|
|
5743
|
+
purl: `pkg:pypi/${name.toLowerCase()}@${rawVer}`,
|
|
5744
|
+
path: lockPath,
|
|
5745
|
+
isDirect
|
|
5746
|
+
});
|
|
5747
|
+
}
|
|
5748
|
+
}
|
|
5749
|
+
}
|
|
5750
|
+
function scanPythonDependencies(rootDir, seen) {
|
|
5751
|
+
const pyproject = readTextIfExists(path4__namespace.join(rootDir, "pyproject.toml")) || "";
|
|
5752
|
+
const directNames = extractPyprojectDirectDeps(pyproject);
|
|
5753
|
+
for (const lockName of ["poetry.lock", "uv.lock"]) {
|
|
5754
|
+
const lockPath = path4__namespace.join(rootDir, lockName);
|
|
5755
|
+
const content = readTextIfExists(lockPath);
|
|
5756
|
+
if (content) parseTomlPackageBlocks(content, lockPath, directNames, seen);
|
|
5757
|
+
}
|
|
5758
|
+
const pipfileLockPath = path4__namespace.join(rootDir, "Pipfile.lock");
|
|
5759
|
+
const pipfileRaw = readTextIfExists(pipfileLockPath);
|
|
5760
|
+
if (!pipfileRaw) return;
|
|
5761
|
+
try {
|
|
5762
|
+
const data = JSON.parse(pipfileRaw);
|
|
5763
|
+
parsePipfileSection(data.default || {}, true, pipfileLockPath, seen);
|
|
5764
|
+
parsePipfileSection(data.develop || {}, false, pipfileLockPath, seen);
|
|
5765
|
+
} catch {
|
|
5766
|
+
}
|
|
5767
|
+
}
|
|
5768
|
+
function parseGoRequireLine(line, goModPath) {
|
|
5769
|
+
const cleaned = line.replace(/^require\s+/, "").trim();
|
|
5770
|
+
const commentIdx = cleaned.indexOf("//");
|
|
5771
|
+
const codePart = (commentIdx >= 0 ? cleaned.slice(0, commentIdx) : cleaned).trim();
|
|
5772
|
+
const commentPart = commentIdx >= 0 ? cleaned.slice(commentIdx + 2).trim() : "";
|
|
5773
|
+
const parts = codePart.split(/\s+/);
|
|
5774
|
+
if (parts.length < 2) return null;
|
|
5775
|
+
const modName = parts[0];
|
|
5776
|
+
const rawVer = parts[1].replace(/^v/, "");
|
|
5777
|
+
const isIndirect = /\bindirect\b/i.test(commentPart);
|
|
5778
|
+
const licenseMatch = commentPart.match(/(?:spdx|license)\s*:\s*([A-Za-z0-9.+-]+)/i);
|
|
5779
|
+
const spdx = licenseMatch ? normalizeSpdxLicense(licenseMatch[1]) : "UNKNOWN";
|
|
5780
|
+
return {
|
|
5781
|
+
name: modName,
|
|
5782
|
+
version: rawVer,
|
|
5783
|
+
spdxLicense: spdx,
|
|
5784
|
+
purl: `pkg:golang/${modName}@v${rawVer}`,
|
|
5785
|
+
path: goModPath,
|
|
5786
|
+
isDirect: !isIndirect
|
|
5787
|
+
};
|
|
5788
|
+
}
|
|
5789
|
+
function extractGoCandidateLines(content) {
|
|
5790
|
+
const out = [];
|
|
5791
|
+
let inBlock = false;
|
|
5792
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
5793
|
+
const trimmed = rawLine.trim();
|
|
5794
|
+
if (/^require\s*\($/.test(trimmed)) inBlock = true;
|
|
5795
|
+
else if (inBlock && trimmed === ")") inBlock = false;
|
|
5796
|
+
else if (inBlock || trimmed.startsWith("require ")) out.push(trimmed);
|
|
5797
|
+
}
|
|
5798
|
+
return out;
|
|
5799
|
+
}
|
|
5800
|
+
function scanGoDependencies(rootDir, seen) {
|
|
5801
|
+
const goModPath = path4__namespace.join(rootDir, "go.mod");
|
|
5802
|
+
const content = readTextIfExists(goModPath);
|
|
5803
|
+
if (!content) return;
|
|
5804
|
+
for (const line of extractGoCandidateLines(content)) {
|
|
5805
|
+
const dep = parseGoRequireLine(line, goModPath);
|
|
5806
|
+
if (dep && !seen.has(`golang:${dep.name}@${dep.version}`)) {
|
|
5807
|
+
seen.set(`golang:${dep.name}@${dep.version}`, dep);
|
|
5808
|
+
}
|
|
5809
|
+
}
|
|
5810
|
+
}
|
|
5811
|
+
function extractCargoDirectDeps(cargoToml) {
|
|
5812
|
+
const map = /* @__PURE__ */ new Map();
|
|
5813
|
+
let inDeps = false;
|
|
5814
|
+
for (const line of cargoToml.split(/\r?\n/)) {
|
|
5815
|
+
const trimmed = line.trim();
|
|
5816
|
+
if (trimmed.startsWith("[")) {
|
|
5817
|
+
inDeps = /^\[(?:dev-|build-)?dependencies\]$/.test(trimmed);
|
|
5818
|
+
continue;
|
|
5819
|
+
}
|
|
5820
|
+
if (!inDeps || !trimmed || trimmed.startsWith("#")) continue;
|
|
5821
|
+
const m = trimmed.match(/^([a-zA-Z0-9_-]+)\s*=\s*(.+)$/);
|
|
5822
|
+
if (m) {
|
|
5823
|
+
const licMatch = m[2].match(/license\s*=\s*["']([^"']+)["']/);
|
|
5824
|
+
map.set(m[1], licMatch ? licMatch[1] : "UNKNOWN");
|
|
5825
|
+
}
|
|
5826
|
+
}
|
|
5827
|
+
return map;
|
|
5828
|
+
}
|
|
5829
|
+
function parseCargoLockBlock(block, rootName, directMeta, cargoLockPath) {
|
|
5830
|
+
const name = extractTomlField(block, "name");
|
|
5831
|
+
const version = extractTomlField(block, "version");
|
|
5832
|
+
if (!name || !version || name === rootName) return null;
|
|
5833
|
+
const blockLic = extractTomlField(block, "license") || directMeta.get(name) || "UNKNOWN";
|
|
5834
|
+
return {
|
|
5835
|
+
name,
|
|
5836
|
+
version,
|
|
5837
|
+
spdxLicense: normalizeSpdxLicense(blockLic),
|
|
5838
|
+
purl: `pkg:cargo/${name}@${version}`,
|
|
5839
|
+
path: cargoLockPath,
|
|
5840
|
+
isDirect: directMeta.size === 0 || directMeta.has(name)
|
|
5841
|
+
};
|
|
5842
|
+
}
|
|
5843
|
+
function scanRustDependencies(rootDir, seen) {
|
|
5844
|
+
const cargoLockPath = path4__namespace.join(rootDir, "Cargo.lock");
|
|
5845
|
+
const lockContent = readTextIfExists(cargoLockPath);
|
|
5846
|
+
if (!lockContent) return;
|
|
5847
|
+
const cargoToml = readTextIfExists(path4__namespace.join(rootDir, "Cargo.toml")) || "";
|
|
5848
|
+
const directMeta = extractCargoDirectDeps(cargoToml);
|
|
5849
|
+
const rootName = extractTomlField(cargoToml, "name");
|
|
5850
|
+
for (const block of lockContent.split(/\[\[package\]\]/g).slice(1)) {
|
|
5851
|
+
const dep = parseCargoLockBlock(block, rootName, directMeta, cargoLockPath);
|
|
5852
|
+
if (dep && !seen.has(`cargo:${dep.name}@${dep.version}`)) {
|
|
5853
|
+
seen.set(`cargo:${dep.name}@${dep.version}`, dep);
|
|
5854
|
+
}
|
|
5855
|
+
}
|
|
5856
|
+
}
|
|
5857
|
+
function extractXmlTag(xml, tag) {
|
|
5858
|
+
const m = xml.match(new RegExp(`<${tag}>([^<]+)</${tag}>`));
|
|
5859
|
+
return m ? m[1].trim() : void 0;
|
|
5860
|
+
}
|
|
5861
|
+
function resolveMavenBlockLicense(block, defaultLic) {
|
|
5862
|
+
const tagLic = extractXmlTag(block, "license");
|
|
5863
|
+
if (tagLic) return tagLic;
|
|
5864
|
+
const commentLic = block.match(/<!--\s*(?:spdx|license)\s*:\s*([A-Za-z0-9.+-]+)\s*-->/i)?.[1];
|
|
5865
|
+
return commentLic || defaultLic;
|
|
5866
|
+
}
|
|
5867
|
+
function parseMavenDepBlock(block, defaultLic, pomPath) {
|
|
5868
|
+
const groupId = extractXmlTag(block, "groupId");
|
|
5869
|
+
const artifactId = extractXmlTag(block, "artifactId");
|
|
5870
|
+
if (!groupId || !artifactId) return null;
|
|
5871
|
+
const version = extractXmlTag(block, "version") || "unknown";
|
|
5872
|
+
const inlineLic = resolveMavenBlockLicense(block, defaultLic);
|
|
5873
|
+
return {
|
|
5874
|
+
name: `${groupId}:${artifactId}`,
|
|
5875
|
+
version,
|
|
5876
|
+
spdxLicense: normalizeSpdxLicense(inlineLic),
|
|
5877
|
+
purl: `pkg:maven/${groupId}/${artifactId}@${version}`,
|
|
5878
|
+
path: pomPath,
|
|
5879
|
+
isDirect: true
|
|
5880
|
+
};
|
|
5881
|
+
}
|
|
5882
|
+
function scanMavenDependencies(rootDir, seen) {
|
|
5883
|
+
const pomPath = path4__namespace.join(rootDir, "pom.xml");
|
|
5884
|
+
const content = readTextIfExists(pomPath);
|
|
5885
|
+
if (!content) return;
|
|
5886
|
+
const projectLicBlock = content.match(/<licenses>([\s\S]*?)<\/licenses>/)?.[1] || "";
|
|
5887
|
+
const defaultLic = extractXmlTag(projectLicBlock, "name") || "UNKNOWN";
|
|
5888
|
+
const depBlocks = content.match(/<dependency>[\s\S]*?<\/dependency>/g) || [];
|
|
5889
|
+
for (const block of depBlocks) {
|
|
5890
|
+
const dep = parseMavenDepBlock(block, defaultLic, pomPath);
|
|
5891
|
+
if (dep && !seen.has(`maven:${dep.name}@${dep.version}`)) {
|
|
5892
|
+
seen.set(`maven:${dep.name}@${dep.version}`, dep);
|
|
5893
|
+
}
|
|
5894
|
+
}
|
|
5895
|
+
}
|
|
5688
5896
|
function scanWithNativeFs(rootDir, options = {}) {
|
|
5689
5897
|
const directNames = extractDirectDepNames(rootDir, options.productionOnly);
|
|
5690
5898
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -5695,6 +5903,10 @@ function scanWithNativeFs(rootDir, options = {}) {
|
|
|
5695
5903
|
scanPnpmStore(path4__namespace.join(nmDir, ".pnpm"), directNames, seen);
|
|
5696
5904
|
}
|
|
5697
5905
|
}
|
|
5906
|
+
scanPythonDependencies(rootDir, seen);
|
|
5907
|
+
scanGoDependencies(rootDir, seen);
|
|
5908
|
+
scanRustDependencies(rootDir, seen);
|
|
5909
|
+
scanMavenDependencies(rootDir, seen);
|
|
5698
5910
|
const allDeps = Array.from(seen.values());
|
|
5699
5911
|
if (options.depth === "direct") {
|
|
5700
5912
|
return allDeps.filter((d) => d.isDirect);
|
|
@@ -5764,10 +5976,17 @@ var SEVERITY_RANK = {
|
|
|
5764
5976
|
LOW: 1,
|
|
5765
5977
|
UNKNOWN: 0
|
|
5766
5978
|
};
|
|
5979
|
+
function isExemptByVex(v, vexStatements) {
|
|
5980
|
+
if (!vexStatements || vexStatements.length === 0) return false;
|
|
5981
|
+
return vexStatements.some(
|
|
5982
|
+
(stmt) => stmt.id === v.id && (stmt.state === "not_affected" || stmt.state === "fixed") && (!stmt.package || stmt.package === v.packageName)
|
|
5983
|
+
);
|
|
5984
|
+
}
|
|
5767
5985
|
function evaluateVulnerabilityThresholds(vulnerabilities, options) {
|
|
5768
5986
|
const minRank = options?.failOnSeverity ? SEVERITY_RANK[options.failOnSeverity] ?? 3 : void 0;
|
|
5769
5987
|
const maxScore = options?.maxCvssScore;
|
|
5770
5988
|
const failing = vulnerabilities.filter((v) => {
|
|
5989
|
+
if (isExemptByVex(v, options?.vexStatements)) return false;
|
|
5771
5990
|
const rankMatch = minRank !== void 0 && SEVERITY_RANK[v.severity] >= minRank;
|
|
5772
5991
|
const scoreMatch = maxScore !== void 0 && v.cvssV3Score !== void 0 && v.cvssV3Score >= maxScore;
|
|
5773
5992
|
return rankMatch || scoreMatch;
|
|
@@ -5897,42 +6116,77 @@ function buildCycloneDxComponent(dep) {
|
|
|
5897
6116
|
}
|
|
5898
6117
|
return comp;
|
|
5899
6118
|
}
|
|
5900
|
-
function
|
|
6119
|
+
function buildVexAnalysis(vex) {
|
|
6120
|
+
if (!vex) return void 0;
|
|
6121
|
+
return {
|
|
6122
|
+
state: vex.state,
|
|
6123
|
+
justification: vex.justification,
|
|
6124
|
+
detail: vex.detail,
|
|
6125
|
+
response: vex.response
|
|
6126
|
+
};
|
|
6127
|
+
}
|
|
6128
|
+
function mapScaVulnToCycloneDx(v, vexStatements) {
|
|
6129
|
+
const matchingVex = vexStatements.find(
|
|
6130
|
+
(stmt) => stmt.id === v.id && (!stmt.package || stmt.package === v.packageName)
|
|
6131
|
+
);
|
|
6132
|
+
return {
|
|
6133
|
+
id: v.id,
|
|
6134
|
+
source: v.primaryUrl ? { name: "NVD/GHSA", url: v.primaryUrl } : void 0,
|
|
6135
|
+
ratings: [{ score: v.cvssV3Score, severity: v.severity.toLowerCase(), method: "CVSSv3", vector: v.cvssV3Vector }],
|
|
6136
|
+
analysis: buildVexAnalysis(matchingVex),
|
|
6137
|
+
affects: [{ ref: `pkg:npm/${v.packageName}@${v.installedVersion}` }]
|
|
6138
|
+
};
|
|
6139
|
+
}
|
|
6140
|
+
function mapStandaloneVex(stmt) {
|
|
6141
|
+
return {
|
|
6142
|
+
id: stmt.id,
|
|
6143
|
+
analysis: buildVexAnalysis(stmt),
|
|
6144
|
+
affects: stmt.package ? [{ ref: stmt.package }] : void 0
|
|
6145
|
+
};
|
|
6146
|
+
}
|
|
6147
|
+
function buildCycloneDxVulnerabilities(vulnerabilities = [], vexStatements = []) {
|
|
6148
|
+
const out = vulnerabilities.map((v) => mapScaVulnToCycloneDx(v, vexStatements));
|
|
6149
|
+
const seenIds = new Set(out.map((item) => item.id));
|
|
6150
|
+
for (const stmt of vexStatements) {
|
|
6151
|
+
if (!seenIds.has(stmt.id)) {
|
|
6152
|
+
out.push(mapStandaloneVex(stmt));
|
|
6153
|
+
seenIds.add(stmt.id);
|
|
6154
|
+
}
|
|
6155
|
+
}
|
|
6156
|
+
return out;
|
|
6157
|
+
}
|
|
6158
|
+
function resolveProjectIdentity(options) {
|
|
5901
6159
|
let name = options.projectName || "ai-sdlc-project";
|
|
5902
6160
|
let version = options.projectVersion || "1.0.0";
|
|
5903
|
-
if (options.rootDir) {
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
}
|
|
5912
|
-
}
|
|
6161
|
+
if (!options.rootDir) return { name, version };
|
|
6162
|
+
const pkgPath = path4__namespace.join(options.rootDir, "package.json");
|
|
6163
|
+
if (!fs7__namespace.existsSync(pkgPath)) return { name, version };
|
|
6164
|
+
try {
|
|
6165
|
+
const pkg = JSON.parse(fs7__namespace.readFileSync(pkgPath, "utf-8"));
|
|
6166
|
+
if (pkg.name) name = pkg.name;
|
|
6167
|
+
if (pkg.version) version = pkg.version;
|
|
6168
|
+
} catch {
|
|
5913
6169
|
}
|
|
5914
|
-
|
|
6170
|
+
return { name, version };
|
|
6171
|
+
}
|
|
6172
|
+
function buildBomMetadata(name, version) {
|
|
6173
|
+
return {
|
|
6174
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6175
|
+
tools: [{ vendor: "AI-SDLC", name: "@aisdlc/core", version: "2.3.0" }],
|
|
6176
|
+
component: { type: "application", name, version }
|
|
6177
|
+
};
|
|
6178
|
+
}
|
|
6179
|
+
function generateCycloneDxSbom(dependencies, options = {}) {
|
|
6180
|
+
const { name, version } = resolveProjectIdentity(options);
|
|
6181
|
+
const cdxVulns = buildCycloneDxVulnerabilities(options.vulnerabilities, options.vexStatements);
|
|
5915
6182
|
return {
|
|
5916
6183
|
bomFormat: "CycloneDX",
|
|
5917
6184
|
specVersion: "1.5",
|
|
5918
6185
|
version: 1,
|
|
5919
6186
|
serialNumber: `urn:uuid:${crypto__namespace.randomUUID()}`,
|
|
5920
|
-
metadata:
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
{
|
|
5924
|
-
vendor: "AI-SDLC",
|
|
5925
|
-
name: "@aisdlc/core",
|
|
5926
|
-
version: "1.0.0"
|
|
5927
|
-
}
|
|
5928
|
-
],
|
|
5929
|
-
component: {
|
|
5930
|
-
type: "application",
|
|
5931
|
-
name,
|
|
5932
|
-
version
|
|
5933
|
-
}
|
|
5934
|
-
},
|
|
5935
|
-
components
|
|
6187
|
+
metadata: buildBomMetadata(name, version),
|
|
6188
|
+
components: dependencies.map(buildCycloneDxComponent),
|
|
6189
|
+
...cdxVulns.length > 0 ? { vulnerabilities: cdxVulns } : {}
|
|
5936
6190
|
};
|
|
5937
6191
|
}
|
|
5938
6192
|
function tryAtomicWrite(filePath, content) {
|
|
@@ -6100,17 +6354,24 @@ function parseExceptions(exceptions) {
|
|
|
6100
6354
|
}
|
|
6101
6355
|
return map;
|
|
6102
6356
|
}
|
|
6357
|
+
function parseVexStatements(doc) {
|
|
6358
|
+
const rawList = doc?.vex_statements || doc?.exceptions?.vex_statements || [];
|
|
6359
|
+
if (!Array.isArray(rawList)) return [];
|
|
6360
|
+
return rawList.filter((item) => Boolean(item && typeof item.id === "string" && typeof item.state === "string"));
|
|
6361
|
+
}
|
|
6103
6362
|
function parseLicensePolicy(content) {
|
|
6104
6363
|
try {
|
|
6105
6364
|
const doc = yaml3__default.default.load(content);
|
|
6106
6365
|
const { permitted, restricted, blocked } = classifyCategories(doc?.categories);
|
|
6107
6366
|
const exception_packages = parseExceptions(doc?.exceptions);
|
|
6367
|
+
const vex_statements = parseVexStatements(doc);
|
|
6108
6368
|
return {
|
|
6109
6369
|
version: doc?.version || "1.0",
|
|
6110
6370
|
permitted: permitted.length > 0 ? permitted : DEFAULT_PERMITTED,
|
|
6111
6371
|
restricted: restricted.length > 0 ? restricted : DEFAULT_RESTRICTED,
|
|
6112
6372
|
blocked: blocked.length > 0 ? blocked : DEFAULT_BLOCKED,
|
|
6113
6373
|
exception_packages,
|
|
6374
|
+
vex_statements,
|
|
6114
6375
|
rawPolicy: doc
|
|
6115
6376
|
};
|
|
6116
6377
|
} catch {
|
|
@@ -6119,7 +6380,8 @@ function parseLicensePolicy(content) {
|
|
|
6119
6380
|
permitted: DEFAULT_PERMITTED,
|
|
6120
6381
|
restricted: DEFAULT_RESTRICTED,
|
|
6121
6382
|
blocked: DEFAULT_BLOCKED,
|
|
6122
|
-
exception_packages: {}
|
|
6383
|
+
exception_packages: {},
|
|
6384
|
+
vex_statements: []
|
|
6123
6385
|
};
|
|
6124
6386
|
}
|
|
6125
6387
|
}
|
|
@@ -6294,10 +6556,10 @@ function shouldGenerateSbom(options, rawPolicy) {
|
|
|
6294
6556
|
if (options.generateSbom === false) return false;
|
|
6295
6557
|
return Boolean(options.generateSbom || options.sbomPath || rawPolicy?.ci_gates?.generate_sbom);
|
|
6296
6558
|
}
|
|
6297
|
-
function maybeGenerateSbom(options, rootDir, rawPolicy, dependencies) {
|
|
6559
|
+
function maybeGenerateSbom(options, rootDir, rawPolicy, dependencies, vulnerabilities, vexStatements) {
|
|
6298
6560
|
if (!shouldGenerateSbom(options, rawPolicy)) return void 0;
|
|
6299
6561
|
const sbomPath = options.sbomPath || path4__namespace.join(rootDir, "reports", "sbom.cdx.json");
|
|
6300
|
-
const sbom = generateCycloneDxSbom(dependencies, { rootDir });
|
|
6562
|
+
const sbom = generateCycloneDxSbom(dependencies, { rootDir, vulnerabilities, vexStatements });
|
|
6301
6563
|
writeCycloneDxSbom(sbom, sbomPath);
|
|
6302
6564
|
return sbomPath;
|
|
6303
6565
|
}
|
|
@@ -6310,9 +6572,9 @@ function maybeGenerateNotices(options, rootDir, rawPolicy, dependencies) {
|
|
|
6310
6572
|
writeThirdPartyNotices(noticesContent, noticesPath);
|
|
6311
6573
|
return noticesPath;
|
|
6312
6574
|
}
|
|
6313
|
-
function handleDynamicScaArtifacts(options, rootDir, rawPolicy, dependencies) {
|
|
6575
|
+
function handleDynamicScaArtifacts(options, rootDir, rawPolicy, dependencies, vulnerabilities, vexStatements) {
|
|
6314
6576
|
return {
|
|
6315
|
-
sbomPath: maybeGenerateSbom(options, rootDir, rawPolicy, dependencies),
|
|
6577
|
+
sbomPath: maybeGenerateSbom(options, rootDir, rawPolicy, dependencies, vulnerabilities, vexStatements),
|
|
6316
6578
|
noticesPath: maybeGenerateNotices(options, rootDir, rawPolicy, dependencies)
|
|
6317
6579
|
};
|
|
6318
6580
|
}
|
|
@@ -6341,7 +6603,7 @@ function buildSkippedVulnResult() {
|
|
|
6341
6603
|
skipReason: "Trivy scanner is not installed on host environment. Vulnerability check gracefully skipped."
|
|
6342
6604
|
};
|
|
6343
6605
|
}
|
|
6344
|
-
function checkVulnerabilities(rootDir, options) {
|
|
6606
|
+
function checkVulnerabilities(rootDir, options, vexStatements) {
|
|
6345
6607
|
if (options.scanVulnerabilities === false) {
|
|
6346
6608
|
return { vulnerabilities: [], blocked: false };
|
|
6347
6609
|
}
|
|
@@ -6349,7 +6611,10 @@ function checkVulnerabilities(rootDir, options) {
|
|
|
6349
6611
|
if (!res.executed) {
|
|
6350
6612
|
return buildSkippedVulnResult();
|
|
6351
6613
|
}
|
|
6352
|
-
const evalResult = evaluateVulnerabilityThresholds(res.vulnerabilities,
|
|
6614
|
+
const evalResult = evaluateVulnerabilityThresholds(res.vulnerabilities, {
|
|
6615
|
+
...options.vulnerabilityThresholds,
|
|
6616
|
+
vexStatements
|
|
6617
|
+
});
|
|
6353
6618
|
return { vulnerabilities: res.vulnerabilities, blocked: evalResult.blocked };
|
|
6354
6619
|
}
|
|
6355
6620
|
function assembleDynamicLicenseResult(dependencies, permittedCount, violations, reportMarkdown, artifacts, vulnData) {
|
|
@@ -6373,7 +6638,8 @@ function toCleanPolicy(policyWithRaw) {
|
|
|
6373
6638
|
permitted: policyWithRaw.permitted,
|
|
6374
6639
|
restricted: policyWithRaw.restricted,
|
|
6375
6640
|
blocked: policyWithRaw.blocked,
|
|
6376
|
-
exception_packages: policyWithRaw.exception_packages
|
|
6641
|
+
exception_packages: policyWithRaw.exception_packages,
|
|
6642
|
+
vex_statements: policyWithRaw.vex_statements
|
|
6377
6643
|
};
|
|
6378
6644
|
}
|
|
6379
6645
|
function scanDependenciesForVerification(options, rootDir, rawPolicy) {
|
|
@@ -6393,8 +6659,15 @@ function verifyLicenses(options = {}) {
|
|
|
6393
6659
|
}
|
|
6394
6660
|
const dependencies = scanDependenciesForVerification(options, rootDir, policyWithRaw.rawPolicy);
|
|
6395
6661
|
const { violations, permittedCount } = evaluateDependencyList(dependencies, policy);
|
|
6396
|
-
const
|
|
6397
|
-
const
|
|
6662
|
+
const vulnData = checkVulnerabilities(rootDir, options, policy.vex_statements);
|
|
6663
|
+
const artifacts = handleDynamicScaArtifacts(
|
|
6664
|
+
options,
|
|
6665
|
+
rootDir,
|
|
6666
|
+
policyWithRaw.rawPolicy,
|
|
6667
|
+
dependencies,
|
|
6668
|
+
vulnData.vulnerabilities,
|
|
6669
|
+
policy.vex_statements
|
|
6670
|
+
);
|
|
6398
6671
|
const reportMarkdown = generateLicenseReportMarkdown(dependencies, violations, policy, artifacts.sbomPath);
|
|
6399
6672
|
return assembleDynamicLicenseResult(dependencies, permittedCount, violations, reportMarkdown, artifacts, vulnData);
|
|
6400
6673
|
}
|
|
@@ -15934,9 +16207,148 @@ function initProject(options = {}) {
|
|
|
15934
16207
|
tryInstallGitHook(destDir, options.dryRun)
|
|
15935
16208
|
);
|
|
15936
16209
|
}
|
|
16210
|
+
var DEFAULT_DLP_RULES = [
|
|
16211
|
+
{
|
|
16212
|
+
id: "DLP-001-PRIVATE-KEY",
|
|
16213
|
+
description: "PEM / RSA / EC / OPENSSH Private Key Block",
|
|
16214
|
+
pattern: "-----BEGIN (?:[A-Z0-9_-]+ )?PRIVATE KEY-----[\\s\\S]*?-----END (?:[A-Z0-9_-]+ )?PRIVATE KEY-----",
|
|
16215
|
+
replacement: "[REDACTED:PRIVATE_KEY]",
|
|
16216
|
+
enabled: true
|
|
16217
|
+
},
|
|
16218
|
+
{
|
|
16219
|
+
id: "DLP-002-API-TOKEN",
|
|
16220
|
+
description: "Cloud, GitHub, Slack, OpenAI, Stripe, and AWS Access Tokens",
|
|
16221
|
+
pattern: "\\b(?:gh[pousr]_[A-Za-z0-9_]{36,255}|github_pat_[0-9a-zA-Z_]{82}|sk-(?:proj-|live-)?[a-zA-Z0-9_-]{32,}|AKIA[0-9A-Z]{16}|xox[baprs]-[0-9]{10,13}-[0-9]{10,13}[a-zA-Z0-9-]*|(?:sk|rk)_(?:live|test)_[0-9a-zA-Z]{24,99})\\b",
|
|
16222
|
+
replacement: "[REDACTED:API_TOKEN]",
|
|
16223
|
+
enabled: true
|
|
16224
|
+
},
|
|
16225
|
+
{
|
|
16226
|
+
id: "DLP-003-JWT",
|
|
16227
|
+
description: "Plaintext JSON Web Token (JWT)",
|
|
16228
|
+
pattern: "\\beyJ[A-Za-z0-9_-]{10,}\\.eyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\b",
|
|
16229
|
+
replacement: "[REDACTED:JWT_TOKEN]",
|
|
16230
|
+
enabled: true
|
|
16231
|
+
},
|
|
16232
|
+
{
|
|
16233
|
+
id: "DLP-004-CREDIT-CARD",
|
|
16234
|
+
description: "Payment Card Number (13-19 digits formatted)",
|
|
16235
|
+
pattern: "\\b(?:\\d{4}[- ]\\d{4}[- ]\\d{4}[- ]\\d{4}|\\d{16})\\b",
|
|
16236
|
+
replacement: "[REDACTED:CREDIT_CARD]",
|
|
16237
|
+
enabled: true
|
|
16238
|
+
},
|
|
16239
|
+
{
|
|
16240
|
+
id: "DLP-005-EMAIL",
|
|
16241
|
+
description: "Personal or Corporate Email Address",
|
|
16242
|
+
pattern: "\\b[A-Za-z0-9._%+-]+@(?!example\\.com|localhost|test\\.local)[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b",
|
|
16243
|
+
replacement: "[REDACTED:EMAIL]",
|
|
16244
|
+
enabled: true
|
|
16245
|
+
},
|
|
16246
|
+
{
|
|
16247
|
+
id: "DLP-006-INTERNAL-URL",
|
|
16248
|
+
description: "Internal Intranet / Corp Service URL",
|
|
16249
|
+
pattern: "https?://(?:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+@|[A-Za-z0-9.-]+\\.(?:internal|corp|intranet|local)\\b)[^\\s\"'`<>]*",
|
|
16250
|
+
replacement: "[REDACTED:INTERNAL_URL]",
|
|
16251
|
+
enabled: true
|
|
16252
|
+
}
|
|
16253
|
+
];
|
|
16254
|
+
function createDefaultDlpPolicy() {
|
|
16255
|
+
return {
|
|
16256
|
+
version: "1.0",
|
|
16257
|
+
enabled: true,
|
|
16258
|
+
zero_data_retention_required: true,
|
|
16259
|
+
rules: [...DEFAULT_DLP_RULES]
|
|
16260
|
+
};
|
|
16261
|
+
}
|
|
16262
|
+
function normalizeParsedRule(raw, idx) {
|
|
16263
|
+
if (typeof raw.pattern !== "string" || !raw.pattern) return null;
|
|
16264
|
+
return {
|
|
16265
|
+
id: typeof raw.id === "string" ? raw.id : `DLP-CUSTOM-${idx + 1}`,
|
|
16266
|
+
description: typeof raw.description === "string" ? raw.description : "Custom DLP rule",
|
|
16267
|
+
pattern: raw.pattern,
|
|
16268
|
+
replacement: typeof raw.replacement === "string" ? raw.replacement : "[REDACTED]",
|
|
16269
|
+
enabled: raw.enabled !== false
|
|
16270
|
+
};
|
|
16271
|
+
}
|
|
16272
|
+
function parseDlpPolicy(yamlContent) {
|
|
16273
|
+
const fallback = createDefaultDlpPolicy();
|
|
16274
|
+
if (!yamlContent || !yamlContent.trim()) return fallback;
|
|
16275
|
+
try {
|
|
16276
|
+
const doc = yaml3__default.default.load(yamlContent);
|
|
16277
|
+
if (!doc || typeof doc !== "object") return fallback;
|
|
16278
|
+
const rawRules = Array.isArray(doc.rules) ? doc.rules : [];
|
|
16279
|
+
const parsedRules = rawRules.map((r, i) => normalizeParsedRule(r, i)).filter((r) => r !== null);
|
|
16280
|
+
return {
|
|
16281
|
+
version: typeof doc.version === "string" ? doc.version : "1.0",
|
|
16282
|
+
enabled: doc.enabled !== false,
|
|
16283
|
+
zero_data_retention_required: Boolean(doc.zero_data_retention_required ?? true),
|
|
16284
|
+
rules: parsedRules.length > 0 ? parsedRules : fallback.rules
|
|
16285
|
+
};
|
|
16286
|
+
} catch {
|
|
16287
|
+
return fallback;
|
|
16288
|
+
}
|
|
16289
|
+
}
|
|
16290
|
+
function loadDlpPolicy(rootDir, customPolicyPath) {
|
|
16291
|
+
const base = rootDir || process.cwd();
|
|
16292
|
+
const filePath = customPolicyPath ? path4__namespace.isAbsolute(customPolicyPath) ? customPolicyPath : path4__namespace.join(base, customPolicyPath) : path4__namespace.join(base, "dlp-policy.yaml");
|
|
16293
|
+
if (!fs7__namespace.existsSync(filePath)) return createDefaultDlpPolicy();
|
|
16294
|
+
try {
|
|
16295
|
+
return parseDlpPolicy(fs7__namespace.readFileSync(filePath, "utf-8"));
|
|
16296
|
+
} catch {
|
|
16297
|
+
return createDefaultDlpPolicy();
|
|
16298
|
+
}
|
|
16299
|
+
}
|
|
16300
|
+
function applySingleDlpRule(text, rule) {
|
|
16301
|
+
if (rule.enabled === false) return { output: text, matches: 0 };
|
|
16302
|
+
try {
|
|
16303
|
+
const regex = new RegExp(rule.pattern, "g");
|
|
16304
|
+
let matches = 0;
|
|
16305
|
+
const output = text.replace(regex, () => {
|
|
16306
|
+
matches++;
|
|
16307
|
+
return rule.replacement;
|
|
16308
|
+
});
|
|
16309
|
+
return { output, matches };
|
|
16310
|
+
} catch {
|
|
16311
|
+
return { output: text, matches: 0 };
|
|
16312
|
+
}
|
|
16313
|
+
}
|
|
16314
|
+
function sanitizeDlpText(text, policy = createDefaultDlpPolicy()) {
|
|
16315
|
+
if (!policy.enabled || !text) {
|
|
16316
|
+
return { sanitizedText: text, redactionCount: 0, triggeredRuleIds: [] };
|
|
16317
|
+
}
|
|
16318
|
+
let current = text;
|
|
16319
|
+
let totalRedactions = 0;
|
|
16320
|
+
const triggered = [];
|
|
16321
|
+
for (const rule of policy.rules) {
|
|
16322
|
+
const res = applySingleDlpRule(current, rule);
|
|
16323
|
+
if (res.matches > 0) {
|
|
16324
|
+
current = res.output;
|
|
16325
|
+
totalRedactions += res.matches;
|
|
16326
|
+
triggered.push(rule.id);
|
|
16327
|
+
}
|
|
16328
|
+
}
|
|
16329
|
+
return {
|
|
16330
|
+
sanitizedText: current,
|
|
16331
|
+
redactionCount: totalRedactions,
|
|
16332
|
+
triggeredRuleIds: triggered
|
|
16333
|
+
};
|
|
16334
|
+
}
|
|
16335
|
+
function sanitizeDlpPayload(payload, policy = createDefaultDlpPolicy()) {
|
|
16336
|
+
if (!policy.enabled || payload === null || payload === void 0) return payload;
|
|
16337
|
+
if (typeof payload === "string") {
|
|
16338
|
+
return sanitizeDlpText(payload, policy).sanitizedText;
|
|
16339
|
+
}
|
|
16340
|
+
const serialized = JSON.stringify(payload);
|
|
16341
|
+
const { sanitizedText } = sanitizeDlpText(serialized, policy);
|
|
16342
|
+
try {
|
|
16343
|
+
return JSON.parse(sanitizedText);
|
|
16344
|
+
} catch {
|
|
16345
|
+
return payload;
|
|
16346
|
+
}
|
|
16347
|
+
}
|
|
15937
16348
|
|
|
15938
16349
|
exports.ALL_ARCHITECTURE_TEMPLATES = ALL_ARCHITECTURE_TEMPLATES;
|
|
15939
16350
|
exports.BRANCH_PATTERNS = BRANCH_PATTERNS;
|
|
16351
|
+
exports.DEFAULT_DLP_RULES = DEFAULT_DLP_RULES;
|
|
15940
16352
|
exports.DEFAULT_EXCLUDED_PATTERNS = DEFAULT_EXCLUDED_PATTERNS;
|
|
15941
16353
|
exports.DEFAULT_EXCLUDES = DEFAULT_EXCLUDES;
|
|
15942
16354
|
exports.DEFAULT_EXTENSIONS = DEFAULT_EXTENSIONS;
|
|
@@ -16082,6 +16494,7 @@ exports.analyzeBddVariantsForRequirement = analyzeBddVariantsForRequirement;
|
|
|
16082
16494
|
exports.appendRevisionHistoryRow = appendRevisionHistoryRow;
|
|
16083
16495
|
exports.applyRatchetMode = applyRatchetMode;
|
|
16084
16496
|
exports.buildCycloneDxComponent = buildCycloneDxComponent;
|
|
16497
|
+
exports.buildCycloneDxVulnerabilities = buildCycloneDxVulnerabilities;
|
|
16085
16498
|
exports.buildFeatureFileContent = buildFeatureFileContent;
|
|
16086
16499
|
exports.buildGraphElements = buildGraphElements;
|
|
16087
16500
|
exports.calculateJaccardSimilarity = calculateJaccardSimilarity;
|
|
@@ -16098,6 +16511,7 @@ exports.collectHistoricalKpis = collectHistoricalKpis;
|
|
|
16098
16511
|
exports.computeCanonicalSha256 = computeCanonicalSha256;
|
|
16099
16512
|
exports.computeSha256 = computeSha256;
|
|
16100
16513
|
exports.computeViolationSignature = computeViolationSignature;
|
|
16514
|
+
exports.createDefaultDlpPolicy = createDefaultDlpPolicy;
|
|
16101
16515
|
exports.createDefaultQualityPolicy = createDefaultQualityPolicy;
|
|
16102
16516
|
exports.depositProductHandoffSidecar = depositProductHandoffSidecar;
|
|
16103
16517
|
exports.detectActiveChangeForIntegration = detectActiveChangeForIntegration;
|
|
@@ -16163,6 +16577,7 @@ exports.isPlaceholderOrIgnored = isPlaceholderOrIgnored;
|
|
|
16163
16577
|
exports.isRequirementArtifact = isRequirementArtifact2;
|
|
16164
16578
|
exports.isSemgrepInstalled = isSemgrepInstalled;
|
|
16165
16579
|
exports.isToolAvailable = isToolAvailable;
|
|
16580
|
+
exports.loadDlpPolicy = loadDlpPolicy;
|
|
16166
16581
|
exports.loadPrivacyPolicy = loadPrivacyPolicy;
|
|
16167
16582
|
exports.loadProductHandoffSidecar = loadProductHandoffSidecar;
|
|
16168
16583
|
exports.loadQualityBaseline = loadQualityBaseline;
|
|
@@ -16172,6 +16587,7 @@ exports.maskSecret = maskSecret;
|
|
|
16172
16587
|
exports.normalizeSpdxLicense = normalizeSpdxLicense;
|
|
16173
16588
|
exports.normalizeUri = normalizeUri;
|
|
16174
16589
|
exports.parseCommitTrailers = parseCommitTrailers;
|
|
16590
|
+
exports.parseDlpPolicy = parseDlpPolicy;
|
|
16175
16591
|
exports.parseFrontmatter = parseFrontmatter;
|
|
16176
16592
|
exports.parseFrontmatterId = parseFrontmatterId;
|
|
16177
16593
|
exports.parseGenericDoc = parseGenericDoc;
|
|
@@ -16213,15 +16629,21 @@ exports.resolveSchemaForArtifact = resolveSchemaForArtifact;
|
|
|
16213
16629
|
exports.resolveTargetFiles = resolveTargetFiles;
|
|
16214
16630
|
exports.runGitleaksCli = runGitleaksCli;
|
|
16215
16631
|
exports.runSemgrepCli = runSemgrepCli;
|
|
16632
|
+
exports.sanitizeDlpPayload = sanitizeDlpPayload;
|
|
16633
|
+
exports.sanitizeDlpText = sanitizeDlpText;
|
|
16216
16634
|
exports.scaffoldSddChange = scaffoldSddChange;
|
|
16217
16635
|
exports.scanAllProductHandoffs = scanAllProductHandoffs;
|
|
16218
16636
|
exports.scanAllTestArtifacts = scanAllTestArtifacts;
|
|
16219
16637
|
exports.scanContentForSecrets = scanContentForSecrets;
|
|
16220
16638
|
exports.scanFileForPrivacyViolations = scanFileForPrivacyViolations;
|
|
16221
16639
|
exports.scanFileForSast = scanFileForSast;
|
|
16640
|
+
exports.scanGoDependencies = scanGoDependencies;
|
|
16222
16641
|
exports.scanInstalledLicenses = scanInstalledLicenses;
|
|
16223
16642
|
exports.scanManualsInWorkspace = scanManualsInWorkspace;
|
|
16643
|
+
exports.scanMavenDependencies = scanMavenDependencies;
|
|
16224
16644
|
exports.scanPnpmStore = scanPnpmStore;
|
|
16645
|
+
exports.scanPythonDependencies = scanPythonDependencies;
|
|
16646
|
+
exports.scanRustDependencies = scanRustDependencies;
|
|
16225
16647
|
exports.scanSubdirectories = scanSubdirectories;
|
|
16226
16648
|
exports.scanWithNativeFs = scanWithNativeFs;
|
|
16227
16649
|
exports.scanWithSyft = scanWithSyft;
|