guardrail-security 1.0.1 → 2.0.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/attack-surface/analyzer.d.ts.map +1 -1
- package/dist/attack-surface/analyzer.js +3 -2
- package/dist/license/engine.d.ts.map +1 -1
- package/dist/license/engine.js +3 -2
- package/dist/sbom/generator.d.ts +42 -0
- package/dist/sbom/generator.d.ts.map +1 -1
- package/dist/sbom/generator.js +168 -7
- package/dist/secrets/allowlist.d.ts +38 -0
- package/dist/secrets/allowlist.d.ts.map +1 -0
- package/dist/secrets/allowlist.js +131 -0
- package/dist/secrets/config-loader.d.ts +25 -0
- package/dist/secrets/config-loader.d.ts.map +1 -0
- package/dist/secrets/config-loader.js +103 -0
- package/dist/secrets/contextual-risk.d.ts +19 -0
- package/dist/secrets/contextual-risk.d.ts.map +1 -0
- package/dist/secrets/contextual-risk.js +88 -0
- package/dist/secrets/git-scanner.d.ts +29 -0
- package/dist/secrets/git-scanner.d.ts.map +1 -0
- package/dist/secrets/git-scanner.js +109 -0
- package/dist/secrets/guardian.d.ts +70 -57
- package/dist/secrets/guardian.d.ts.map +1 -1
- package/dist/secrets/guardian.js +532 -240
- package/dist/secrets/index.d.ts +4 -0
- package/dist/secrets/index.d.ts.map +1 -1
- package/dist/secrets/index.js +11 -1
- package/dist/secrets/patterns.d.ts +39 -10
- package/dist/secrets/patterns.d.ts.map +1 -1
- package/dist/secrets/patterns.js +129 -71
- package/dist/secrets/pre-commit.d.ts.map +1 -1
- package/dist/secrets/pre-commit.js +1 -1
- package/dist/secrets/vault-integration.d.ts.map +1 -1
- package/dist/secrets/vault-integration.js +1 -0
- package/dist/supply-chain/detector.d.ts.map +1 -1
- package/dist/supply-chain/detector.js +4 -3
- package/dist/supply-chain/vulnerability-db.d.ts +89 -16
- package/dist/supply-chain/vulnerability-db.d.ts.map +1 -1
- package/dist/supply-chain/vulnerability-db.js +404 -115
- package/dist/utils/semver.d.ts +37 -0
- package/dist/utils/semver.d.ts.map +1 -0
- package/dist/utils/semver.js +109 -0
- package/package.json +17 -4
- package/src/__tests__/license/engine.test.ts +0 -250
- package/src/__tests__/supply-chain/typosquat.test.ts +0 -191
- package/src/attack-surface/analyzer.ts +0 -152
- package/src/attack-surface/index.ts +0 -5
- package/src/index.ts +0 -21
- package/src/languages/index.ts +0 -91
- package/src/languages/java-analyzer.ts +0 -490
- package/src/languages/python-analyzer.ts +0 -498
- package/src/license/compatibility-matrix.ts +0 -366
- package/src/license/engine.ts +0 -345
- package/src/license/index.ts +0 -6
- package/src/sbom/generator.ts +0 -355
- package/src/sbom/index.ts +0 -5
- package/src/secrets/guardian.ts +0 -448
- package/src/secrets/index.ts +0 -10
- package/src/secrets/patterns.ts +0 -186
- package/src/secrets/pre-commit.ts +0 -158
- package/src/secrets/vault-integration.ts +0 -360
- package/src/secrets/vault-providers.ts +0 -446
- package/src/supply-chain/detector.ts +0 -252
- package/src/supply-chain/index.ts +0 -11
- package/src/supply-chain/malicious-db.ts +0 -103
- package/src/supply-chain/script-analyzer.ts +0 -194
- package/src/supply-chain/typosquat.ts +0 -302
- package/src/supply-chain/vulnerability-db.ts +0 -386
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../src/attack-surface/analyzer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../src/attack-surface/analyzer.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,GAAG,MAAM,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,aAAa,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;CACvC;AAED,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE;QACP,gBAAgB,EAAE,MAAM,CAAC;QACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACtC,CAAC;IACF,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,WAAW,EAAE,kBAAkB,EAAE,CAAC;CACnC;AAED,qBAAa,qBAAqB;IAC1B,cAAc,CAClB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,2BAA2B,CAAC;YA0CzB,iBAAiB;YAKjB,gBAAgB;YA8BhB,gBAAgB;IAOxB,qBAAqB,CACzB,QAAQ,EAAE,2BAA2B,GACpC,OAAO,CAAC,MAAM,CAAC;CAWnB;AAED,eAAO,MAAM,qBAAqB,uBAA8B,CAAC"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.attackSurfaceAnalyzer = exports.AttackSurfaceAnalyzer = void 0;
|
|
4
|
-
|
|
4
|
+
// Stub prisma for standalone use
|
|
5
|
+
const prisma = null;
|
|
5
6
|
class AttackSurfaceAnalyzer {
|
|
6
7
|
async analyzeProject(projectPath, projectId) {
|
|
7
8
|
const entryPoints = await this.scanHTTPEndpoints(projectPath);
|
|
@@ -27,7 +28,7 @@ class AttackSurfaceAnalyzer {
|
|
|
27
28
|
attackPaths,
|
|
28
29
|
apiFindings,
|
|
29
30
|
};
|
|
30
|
-
await
|
|
31
|
+
await prisma.attackSurfaceAnalysis.create({
|
|
31
32
|
data: {
|
|
32
33
|
projectId,
|
|
33
34
|
summary: JSON.parse(JSON.stringify(result.summary)),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/license/engine.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/license/engine.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,SAAS,GAAG,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE;QACP,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACnC,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,YAAY,EAAE,kBAAkB,EAAE,CAAC;IACnC,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,aAAa,EAAE,WAAW,GAAG,SAAS,GAAG,WAAW,CAAC;CACtD;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,uBAAuB;IAC5B,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;YAoCtG,mBAAmB;IAsCjC;;OAEG;YACW,wBAAwB;IAuCtC;;OAEG;IACH,OAAO,CAAC,6BAA6B;IAyBrC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IA0B5B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAkBzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IASzB;;OAEG;IACH,UAAU,IAAI,IAAI;IAIlB;;OAEG;IACH,aAAa,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,IAAI,GAAG,IAAI,CAAA;KAAE;IAa3D,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB;IAkBnF,OAAO,CAAC,sBAAsB;YAoBhB,wBAAwB;IAKhC,wBAAwB,CAAC,QAAQ,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC;CAiBjF;AAED,eAAO,MAAM,uBAAuB,yBAAgC,CAAC"}
|
package/dist/license/engine.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.licenseComplianceEngine = exports.LicenseComplianceEngine = void 0;
|
|
4
|
-
|
|
4
|
+
// Stub prisma for standalone use
|
|
5
|
+
const prisma = null;
|
|
5
6
|
const compatibility_matrix_1 = require("./compatibility-matrix");
|
|
6
7
|
const fs_1 = require("fs");
|
|
7
8
|
const path_1 = require("path");
|
|
@@ -35,7 +36,7 @@ class LicenseComplianceEngine {
|
|
|
35
36
|
overallStatus,
|
|
36
37
|
};
|
|
37
38
|
// @ts-ignore - licenseAnalysis may not exist in schema yet
|
|
38
|
-
const analysis = await
|
|
39
|
+
const analysis = await prisma.licenseAnalysis.findUnique({
|
|
39
40
|
where: { id: projectId }
|
|
40
41
|
});
|
|
41
42
|
return result;
|
package/dist/sbom/generator.d.ts
CHANGED
|
@@ -57,6 +57,24 @@ export interface SBOMGeneratorOptions {
|
|
|
57
57
|
includeLicenses?: boolean;
|
|
58
58
|
includeHashes?: boolean;
|
|
59
59
|
outputPath?: string;
|
|
60
|
+
vex?: boolean;
|
|
61
|
+
sign?: boolean;
|
|
62
|
+
}
|
|
63
|
+
export interface VEXDocument {
|
|
64
|
+
'@context': string;
|
|
65
|
+
'@id': string;
|
|
66
|
+
author: string;
|
|
67
|
+
timestamp: string;
|
|
68
|
+
version: string;
|
|
69
|
+
statements: VEXStatement[];
|
|
70
|
+
}
|
|
71
|
+
export interface VEXStatement {
|
|
72
|
+
vulnerability: string;
|
|
73
|
+
products: string[];
|
|
74
|
+
status: 'not_affected' | 'affected' | 'fixed' | 'under_investigation';
|
|
75
|
+
justification?: string;
|
|
76
|
+
actionStatement?: string;
|
|
77
|
+
actionStatementTimestamp?: string;
|
|
60
78
|
}
|
|
61
79
|
export declare class SBOMGenerator {
|
|
62
80
|
private readonly toolInfo;
|
|
@@ -64,6 +82,10 @@ export declare class SBOMGenerator {
|
|
|
64
82
|
* Generate SBOM for a project
|
|
65
83
|
*/
|
|
66
84
|
generate(projectPath: string, options: SBOMGeneratorOptions): Promise<SBOM>;
|
|
85
|
+
/**
|
|
86
|
+
* Generate SBOM for a container image using syft
|
|
87
|
+
*/
|
|
88
|
+
generateContainerSBOM(imageName: string, options: SBOMGeneratorOptions): Promise<SBOM>;
|
|
67
89
|
/**
|
|
68
90
|
* Extract components from package.json
|
|
69
91
|
*/
|
|
@@ -103,6 +125,26 @@ export declare class SBOMGenerator {
|
|
|
103
125
|
valid: boolean;
|
|
104
126
|
errors: string[];
|
|
105
127
|
};
|
|
128
|
+
/**
|
|
129
|
+
* Compute hashes for a package
|
|
130
|
+
*/
|
|
131
|
+
private computePackageHashes;
|
|
132
|
+
/**
|
|
133
|
+
* Generate VEX document
|
|
134
|
+
*/
|
|
135
|
+
private generateVEX;
|
|
136
|
+
/**
|
|
137
|
+
* Sign SBOM using cosign
|
|
138
|
+
*/
|
|
139
|
+
private signSBOM;
|
|
140
|
+
/**
|
|
141
|
+
* Check if a tool is available
|
|
142
|
+
*/
|
|
143
|
+
private checkToolAvailable;
|
|
144
|
+
/**
|
|
145
|
+
* Normalize container SBOM from syft output
|
|
146
|
+
*/
|
|
147
|
+
private normalizeContainerSBOM;
|
|
106
148
|
}
|
|
107
149
|
export declare const sbomGenerator: SBOMGenerator;
|
|
108
150
|
//# sourceMappingURL=generator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/sbom/generator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/sbom/generator.ts"],"names":[],"mappings":"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"}
|
package/dist/sbom/generator.js
CHANGED
|
@@ -9,6 +9,7 @@ exports.sbomGenerator = exports.SBOMGenerator = void 0;
|
|
|
9
9
|
const fs_1 = require("fs");
|
|
10
10
|
const path_1 = require("path");
|
|
11
11
|
const crypto_1 = require("crypto");
|
|
12
|
+
const child_process_1 = require("child_process");
|
|
12
13
|
class SBOMGenerator {
|
|
13
14
|
toolInfo = {
|
|
14
15
|
vendor: 'Guardrail AI',
|
|
@@ -46,9 +47,55 @@ class SBOMGenerator {
|
|
|
46
47
|
};
|
|
47
48
|
if (options.outputPath) {
|
|
48
49
|
await this.writeSBOM(sbom, options.outputPath, options.format);
|
|
50
|
+
if (options.vex) {
|
|
51
|
+
const vexPath = options.outputPath.replace(/\.(json|xml)$/, '.vex.json');
|
|
52
|
+
await this.generateVEX(sbom, vexPath);
|
|
53
|
+
}
|
|
54
|
+
if (options.sign) {
|
|
55
|
+
await this.signSBOM(options.outputPath);
|
|
56
|
+
}
|
|
49
57
|
}
|
|
50
58
|
return sbom;
|
|
51
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Generate SBOM for a container image using syft
|
|
62
|
+
*/
|
|
63
|
+
async generateContainerSBOM(imageName, options) {
|
|
64
|
+
if (!this.checkToolAvailable('syft')) {
|
|
65
|
+
throw new Error('syft is not installed. Install it with:\n' +
|
|
66
|
+
' curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin\n' +
|
|
67
|
+
' Or visit: https://github.com/anchore/syft#installation');
|
|
68
|
+
}
|
|
69
|
+
const format = options.format === 'spdx' ? 'spdx-json' : 'cyclonedx-json';
|
|
70
|
+
const tempFile = (0, path_1.join)(process.cwd(), `.sbom-${Date.now()}.json`);
|
|
71
|
+
try {
|
|
72
|
+
(0, child_process_1.execSync)(`syft ${imageName} -o ${format} --file ${tempFile}`, {
|
|
73
|
+
stdio: 'pipe',
|
|
74
|
+
encoding: 'utf-8',
|
|
75
|
+
});
|
|
76
|
+
const rawSBOM = JSON.parse((0, fs_1.readFileSync)(tempFile, 'utf-8'));
|
|
77
|
+
const sbom = this.normalizeContainerSBOM(rawSBOM, options.format, imageName);
|
|
78
|
+
if (options.outputPath) {
|
|
79
|
+
await this.writeSBOM(sbom, options.outputPath, options.format);
|
|
80
|
+
if (options.vex) {
|
|
81
|
+
const vexPath = options.outputPath.replace(/\.(json|xml)$/, '.vex.json');
|
|
82
|
+
await this.generateVEX(sbom, vexPath);
|
|
83
|
+
}
|
|
84
|
+
if (options.sign) {
|
|
85
|
+
await this.signSBOM(options.outputPath);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return sbom;
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
if ((0, fs_1.existsSync)(tempFile)) {
|
|
92
|
+
try {
|
|
93
|
+
require('fs').unlinkSync(tempFile);
|
|
94
|
+
}
|
|
95
|
+
catch { }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
52
99
|
/**
|
|
53
100
|
* Extract components from package.json
|
|
54
101
|
*/
|
|
@@ -76,13 +123,9 @@ class SBOMGenerator {
|
|
|
76
123
|
}
|
|
77
124
|
// Generate hashes if requested
|
|
78
125
|
if (options.includeHashes) {
|
|
79
|
-
const
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
component.hashes = [
|
|
83
|
-
{ algorithm: 'SHA-256', content: this.hashContent(content, 'sha256') },
|
|
84
|
-
{ algorithm: 'SHA-512', content: this.hashContent(content, 'sha512') },
|
|
85
|
-
];
|
|
126
|
+
const hashes = await this.computePackageHashes(projectPath, name);
|
|
127
|
+
if (hashes.length > 0) {
|
|
128
|
+
component.hashes = hashes;
|
|
86
129
|
}
|
|
87
130
|
}
|
|
88
131
|
components.push(component);
|
|
@@ -265,6 +308,124 @@ class SBOMGenerator {
|
|
|
265
308
|
errors,
|
|
266
309
|
};
|
|
267
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* Compute hashes for a package
|
|
313
|
+
*/
|
|
314
|
+
async computePackageHashes(projectPath, packageName) {
|
|
315
|
+
const hashes = [];
|
|
316
|
+
const packagePath = (0, path_1.join)(projectPath, 'node_modules', packageName, 'package.json');
|
|
317
|
+
if ((0, fs_1.existsSync)(packagePath)) {
|
|
318
|
+
const content = (0, fs_1.readFileSync)(packagePath, 'utf-8');
|
|
319
|
+
hashes.push({ algorithm: 'SHA-256', content: this.hashContent(content, 'sha256') });
|
|
320
|
+
}
|
|
321
|
+
return hashes;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Generate VEX document
|
|
325
|
+
*/
|
|
326
|
+
async generateVEX(sbom, outputPath) {
|
|
327
|
+
const vex = {
|
|
328
|
+
'@context': 'https://openvex.dev/ns',
|
|
329
|
+
'@id': `${sbom.serialNumber}/vex`,
|
|
330
|
+
author: this.toolInfo.vendor,
|
|
331
|
+
timestamp: new Date().toISOString(),
|
|
332
|
+
version: '1',
|
|
333
|
+
statements: sbom.components.map(component => ({
|
|
334
|
+
vulnerability: 'PLACEHOLDER',
|
|
335
|
+
products: [component.purl || `${component.name}@${component.version}`],
|
|
336
|
+
status: 'not_affected',
|
|
337
|
+
justification: 'No known vulnerabilities at time of SBOM generation',
|
|
338
|
+
})),
|
|
339
|
+
};
|
|
340
|
+
(0, fs_1.writeFileSync)(outputPath, JSON.stringify(vex, null, 2), 'utf-8');
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Sign SBOM using cosign
|
|
344
|
+
*/
|
|
345
|
+
async signSBOM(sbomPath) {
|
|
346
|
+
if (!this.checkToolAvailable('cosign')) {
|
|
347
|
+
throw new Error('cosign is not installed. Install it with:\n' +
|
|
348
|
+
' brew install cosign (macOS)\n' +
|
|
349
|
+
' Or visit: https://docs.sigstore.dev/cosign/installation/');
|
|
350
|
+
}
|
|
351
|
+
try {
|
|
352
|
+
(0, child_process_1.execSync)(`cosign sign-blob --yes ${sbomPath} --output-signature ${sbomPath}.sig`, {
|
|
353
|
+
stdio: 'pipe',
|
|
354
|
+
encoding: 'utf-8',
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
throw new Error(`Failed to sign SBOM: ${error.message}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Check if a tool is available
|
|
363
|
+
*/
|
|
364
|
+
checkToolAvailable(tool) {
|
|
365
|
+
try {
|
|
366
|
+
(0, child_process_1.execSync)(`${tool} --version`, { stdio: 'pipe' });
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Normalize container SBOM from syft output
|
|
375
|
+
*/
|
|
376
|
+
normalizeContainerSBOM(rawSBOM, format, imageName) {
|
|
377
|
+
if (format === 'cyclonedx') {
|
|
378
|
+
return {
|
|
379
|
+
format: 'cyclonedx',
|
|
380
|
+
specVersion: rawSBOM.specVersion || '1.5',
|
|
381
|
+
serialNumber: rawSBOM.serialNumber || `urn:uuid:${this.generateUUID()}`,
|
|
382
|
+
version: rawSBOM.version || 1,
|
|
383
|
+
metadata: {
|
|
384
|
+
timestamp: rawSBOM.metadata?.timestamp || new Date().toISOString(),
|
|
385
|
+
tools: [this.toolInfo],
|
|
386
|
+
component: {
|
|
387
|
+
type: 'container',
|
|
388
|
+
name: imageName,
|
|
389
|
+
version: 'latest',
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
components: (rawSBOM.components || []).map((c) => ({
|
|
393
|
+
type: c.type || 'library',
|
|
394
|
+
name: c.name,
|
|
395
|
+
version: c.version,
|
|
396
|
+
purl: c.purl,
|
|
397
|
+
licenses: (c.licenses || []).map((l) => l.license?.id || l.expression || 'unknown'),
|
|
398
|
+
hashes: c.hashes?.map((h) => ({ algorithm: h.alg, content: h.content })),
|
|
399
|
+
})),
|
|
400
|
+
dependencies: rawSBOM.dependencies || [],
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
return {
|
|
405
|
+
format: 'spdx',
|
|
406
|
+
specVersion: '2.3',
|
|
407
|
+
serialNumber: `urn:uuid:${this.generateUUID()}`,
|
|
408
|
+
version: 1,
|
|
409
|
+
metadata: {
|
|
410
|
+
timestamp: new Date().toISOString(),
|
|
411
|
+
tools: [this.toolInfo],
|
|
412
|
+
component: {
|
|
413
|
+
type: 'container',
|
|
414
|
+
name: imageName,
|
|
415
|
+
version: 'latest',
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
components: (rawSBOM.packages || []).map((p) => ({
|
|
419
|
+
type: 'library',
|
|
420
|
+
name: p.name,
|
|
421
|
+
version: p.versionInfo,
|
|
422
|
+
purl: p.externalRefs?.find((r) => r.referenceType === 'purl')?.referenceLocator,
|
|
423
|
+
licenses: p.licenseConcluded ? [p.licenseConcluded] : [],
|
|
424
|
+
})),
|
|
425
|
+
dependencies: [],
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
}
|
|
268
429
|
}
|
|
269
430
|
exports.SBOMGenerator = SBOMGenerator;
|
|
270
431
|
// Export singleton
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* allowlist.ts
|
|
3
|
+
* Manage allowlisted secret detections via SHA256 fingerprints
|
|
4
|
+
*/
|
|
5
|
+
export declare class Allowlist {
|
|
6
|
+
private readonly projectPath;
|
|
7
|
+
private fingerprints;
|
|
8
|
+
constructor(projectPath: string);
|
|
9
|
+
/**
|
|
10
|
+
* Load allowlist from .guardrail/secrets.allowlist
|
|
11
|
+
*/
|
|
12
|
+
private load;
|
|
13
|
+
/**
|
|
14
|
+
* Check if a fingerprint is allowlisted
|
|
15
|
+
*/
|
|
16
|
+
isAllowlisted(fingerprint: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Add a fingerprint to the allowlist
|
|
19
|
+
*/
|
|
20
|
+
add(fingerprint: string): void;
|
|
21
|
+
/**
|
|
22
|
+
* Add multiple fingerprints from a baseline file
|
|
23
|
+
*/
|
|
24
|
+
addFromBaseline(baselinePath: string): number;
|
|
25
|
+
/**
|
|
26
|
+
* Save allowlist to disk
|
|
27
|
+
*/
|
|
28
|
+
save(): void;
|
|
29
|
+
/**
|
|
30
|
+
* Get the allowlist file path
|
|
31
|
+
*/
|
|
32
|
+
private getAllowlistPath;
|
|
33
|
+
/**
|
|
34
|
+
* Get count of allowlisted items
|
|
35
|
+
*/
|
|
36
|
+
size(): number;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=allowlist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allowlist.d.ts","sourceRoot":"","sources":["../../src/secrets/allowlist.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,qBAAa,SAAS;IAGR,OAAO,CAAC,QAAQ,CAAC,WAAW;IAFxC,OAAO,CAAC,YAAY,CAAqB;gBAEZ,WAAW,EAAE,MAAM;IAIhD;;OAEG;IACH,OAAO,CAAC,IAAI;IA4BZ;;OAEG;IACH,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAI3C;;OAEG;IACH,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAO9B;;OAEG;IACH,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM;IAyC7C;;OAEG;IACH,IAAI,IAAI,IAAI;IAmBZ;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAIxB;;OAEG;IACH,IAAI,IAAI,MAAM;CAGf"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* allowlist.ts
|
|
4
|
+
* Manage allowlisted secret detections via SHA256 fingerprints
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.Allowlist = void 0;
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
class Allowlist {
|
|
11
|
+
projectPath;
|
|
12
|
+
fingerprints = new Set();
|
|
13
|
+
constructor(projectPath) {
|
|
14
|
+
this.projectPath = projectPath;
|
|
15
|
+
this.load();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Load allowlist from .guardrail/secrets.allowlist
|
|
19
|
+
*/
|
|
20
|
+
load() {
|
|
21
|
+
const allowlistPath = this.getAllowlistPath();
|
|
22
|
+
if (!(0, fs_1.existsSync)(allowlistPath)) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const content = (0, fs_1.readFileSync)(allowlistPath, 'utf-8');
|
|
27
|
+
const lines = content.split('\n');
|
|
28
|
+
for (const line of lines) {
|
|
29
|
+
const trimmed = line.trim();
|
|
30
|
+
// Skip empty lines and comments
|
|
31
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
// Validate SHA256 format (64 hex chars)
|
|
35
|
+
if (/^[a-f0-9]{64}$/i.test(trimmed)) {
|
|
36
|
+
this.fingerprints.add(trimmed.toLowerCase());
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
// Silently ignore read errors
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Check if a fingerprint is allowlisted
|
|
46
|
+
*/
|
|
47
|
+
isAllowlisted(fingerprint) {
|
|
48
|
+
return this.fingerprints.has(fingerprint.toLowerCase());
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Add a fingerprint to the allowlist
|
|
52
|
+
*/
|
|
53
|
+
add(fingerprint) {
|
|
54
|
+
if (!/^[a-f0-9]{64}$/i.test(fingerprint)) {
|
|
55
|
+
throw new Error(`Invalid fingerprint format: ${fingerprint}`);
|
|
56
|
+
}
|
|
57
|
+
this.fingerprints.add(fingerprint.toLowerCase());
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Add multiple fingerprints from a baseline file
|
|
61
|
+
*/
|
|
62
|
+
addFromBaseline(baselinePath) {
|
|
63
|
+
if (!(0, fs_1.existsSync)(baselinePath)) {
|
|
64
|
+
throw new Error(`Baseline file not found: ${baselinePath}`);
|
|
65
|
+
}
|
|
66
|
+
let added = 0;
|
|
67
|
+
try {
|
|
68
|
+
const content = (0, fs_1.readFileSync)(baselinePath, 'utf-8');
|
|
69
|
+
// Try to parse as JSON first (scan results format)
|
|
70
|
+
try {
|
|
71
|
+
const json = JSON.parse(content);
|
|
72
|
+
if (json.findings && Array.isArray(json.findings)) {
|
|
73
|
+
for (const finding of json.findings) {
|
|
74
|
+
if (finding.fingerprint) {
|
|
75
|
+
this.add(finding.fingerprint);
|
|
76
|
+
added++;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return added;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Not JSON, try line-by-line
|
|
84
|
+
}
|
|
85
|
+
// Parse as line-delimited fingerprints
|
|
86
|
+
const lines = content.split('\n');
|
|
87
|
+
for (const line of lines) {
|
|
88
|
+
const trimmed = line.trim();
|
|
89
|
+
if (trimmed && !trimmed.startsWith('#') && /^[a-f0-9]{64}$/i.test(trimmed)) {
|
|
90
|
+
this.add(trimmed);
|
|
91
|
+
added++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
throw new Error(`Failed to read baseline file: ${err.message}`);
|
|
97
|
+
}
|
|
98
|
+
return added;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Save allowlist to disk
|
|
102
|
+
*/
|
|
103
|
+
save() {
|
|
104
|
+
const allowlistPath = this.getAllowlistPath();
|
|
105
|
+
const dir = (0, path_1.dirname)(allowlistPath);
|
|
106
|
+
if (!(0, fs_1.existsSync)(dir)) {
|
|
107
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
108
|
+
}
|
|
109
|
+
const lines = [
|
|
110
|
+
'# Guardrail Secrets Allowlist',
|
|
111
|
+
'# SHA256 fingerprints of approved/suppressed detections',
|
|
112
|
+
'# One fingerprint per line',
|
|
113
|
+
'',
|
|
114
|
+
...Array.from(this.fingerprints).sort(),
|
|
115
|
+
];
|
|
116
|
+
(0, fs_1.writeFileSync)(allowlistPath, lines.join('\n'), 'utf-8');
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get the allowlist file path
|
|
120
|
+
*/
|
|
121
|
+
getAllowlistPath() {
|
|
122
|
+
return (0, path_1.join)(this.projectPath, '.guardrail', 'secrets.allowlist');
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get count of allowlisted items
|
|
126
|
+
*/
|
|
127
|
+
size() {
|
|
128
|
+
return this.fingerprints.size;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
exports.Allowlist = Allowlist;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config-loader.ts
|
|
3
|
+
* Load and validate custom secret patterns from .guardrail/secrets.yaml
|
|
4
|
+
*/
|
|
5
|
+
import { SecretPattern, RiskLevel } from './patterns';
|
|
6
|
+
export interface CustomPatternConfig {
|
|
7
|
+
name: string;
|
|
8
|
+
type: string;
|
|
9
|
+
regex: string;
|
|
10
|
+
minEntropy?: number;
|
|
11
|
+
description?: string;
|
|
12
|
+
risk?: RiskLevel;
|
|
13
|
+
}
|
|
14
|
+
export interface SecretsConfigFile {
|
|
15
|
+
patterns?: CustomPatternConfig[];
|
|
16
|
+
}
|
|
17
|
+
export declare class ConfigValidationError extends Error {
|
|
18
|
+
readonly details?: string[] | undefined;
|
|
19
|
+
constructor(message: string, details?: string[] | undefined);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Load and validate custom patterns from .guardrail/secrets.yaml
|
|
23
|
+
*/
|
|
24
|
+
export declare function loadCustomPatterns(projectPath: string): SecretPattern[];
|
|
25
|
+
//# sourceMappingURL=config-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/secrets/config-loader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAAE,aAAa,EAAc,SAAS,EAAE,MAAM,YAAY,CAAC;AAElE,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,mBAAmB,EAAE,CAAC;CAClC;AAED,qBAAa,qBAAsB,SAAQ,KAAK;aACD,OAAO,CAAC,EAAE,MAAM,EAAE;gBAAnD,OAAO,EAAE,MAAM,EAAkB,OAAO,CAAC,EAAE,MAAM,EAAE,YAAA;CAIhE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,EAAE,CAqGvE"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* config-loader.ts
|
|
4
|
+
* Load and validate custom secret patterns from .guardrail/secrets.yaml
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.ConfigValidationError = void 0;
|
|
8
|
+
exports.loadCustomPatterns = loadCustomPatterns;
|
|
9
|
+
const fs_1 = require("fs");
|
|
10
|
+
const path_1 = require("path");
|
|
11
|
+
const yaml_1 = require("yaml");
|
|
12
|
+
class ConfigValidationError extends Error {
|
|
13
|
+
details;
|
|
14
|
+
constructor(message, details) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.details = details;
|
|
17
|
+
this.name = 'ConfigValidationError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.ConfigValidationError = ConfigValidationError;
|
|
21
|
+
/**
|
|
22
|
+
* Load and validate custom patterns from .guardrail/secrets.yaml
|
|
23
|
+
*/
|
|
24
|
+
function loadCustomPatterns(projectPath) {
|
|
25
|
+
const configPath = (0, path_1.join)(projectPath, '.guardrail', 'secrets.yaml');
|
|
26
|
+
if (!(0, fs_1.existsSync)(configPath)) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
let rawContent;
|
|
30
|
+
try {
|
|
31
|
+
rawContent = (0, fs_1.readFileSync)(configPath, 'utf-8');
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
throw new ConfigValidationError(`Failed to read secrets config: ${configPath}`, [err.message]);
|
|
35
|
+
}
|
|
36
|
+
let parsed;
|
|
37
|
+
try {
|
|
38
|
+
parsed = (0, yaml_1.parse)(rawContent);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
throw new ConfigValidationError('Invalid YAML syntax in secrets.yaml', [err.message]);
|
|
42
|
+
}
|
|
43
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
44
|
+
throw new ConfigValidationError('secrets.yaml must contain an object at root');
|
|
45
|
+
}
|
|
46
|
+
const config = parsed;
|
|
47
|
+
if (!config.patterns) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
if (!Array.isArray(config.patterns)) {
|
|
51
|
+
throw new ConfigValidationError('patterns must be an array');
|
|
52
|
+
}
|
|
53
|
+
const errors = [];
|
|
54
|
+
const customPatterns = [];
|
|
55
|
+
config.patterns.forEach((pattern, idx) => {
|
|
56
|
+
const prefix = `patterns[${idx}]`;
|
|
57
|
+
// Validate required fields
|
|
58
|
+
if (!pattern.name || typeof pattern.name !== 'string') {
|
|
59
|
+
errors.push(`${prefix}.name is required and must be a string`);
|
|
60
|
+
}
|
|
61
|
+
if (!pattern.type || typeof pattern.type !== 'string') {
|
|
62
|
+
errors.push(`${prefix}.type is required and must be a string`);
|
|
63
|
+
}
|
|
64
|
+
if (!pattern.regex || typeof pattern.regex !== 'string') {
|
|
65
|
+
errors.push(`${prefix}.regex is required and must be a string`);
|
|
66
|
+
}
|
|
67
|
+
// Validate optional fields
|
|
68
|
+
if (pattern.minEntropy !== undefined) {
|
|
69
|
+
if (typeof pattern.minEntropy !== 'number' || pattern.minEntropy < 0 || pattern.minEntropy > 8) {
|
|
70
|
+
errors.push(`${prefix}.minEntropy must be a number between 0 and 8`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (pattern.risk !== undefined) {
|
|
74
|
+
if (!['high', 'medium', 'low'].includes(pattern.risk)) {
|
|
75
|
+
errors.push(`${prefix}.risk must be one of: high, medium, low`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Try to compile regex
|
|
79
|
+
let regex;
|
|
80
|
+
try {
|
|
81
|
+
regex = new RegExp(pattern.regex);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
errors.push(`${prefix}.regex is invalid: ${err.message}`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (errors.length === 0) {
|
|
88
|
+
customPatterns.push({
|
|
89
|
+
type: pattern.type,
|
|
90
|
+
name: pattern.name,
|
|
91
|
+
pattern: regex,
|
|
92
|
+
minEntropy: pattern.minEntropy,
|
|
93
|
+
risk: pattern.risk || 'medium',
|
|
94
|
+
description: pattern.description || pattern.name,
|
|
95
|
+
examples: [],
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
if (errors.length > 0) {
|
|
100
|
+
throw new ConfigValidationError('Invalid custom patterns in secrets.yaml', errors);
|
|
101
|
+
}
|
|
102
|
+
return customPatterns;
|
|
103
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* contextual-risk.ts
|
|
3
|
+
* Adjust risk levels based on file context (examples, templates, production code)
|
|
4
|
+
*/
|
|
5
|
+
import { RiskLevel } from './patterns';
|
|
6
|
+
export interface RiskContext {
|
|
7
|
+
filePath: string;
|
|
8
|
+
entropy: number;
|
|
9
|
+
originalRisk: RiskLevel;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Adjust risk based on file context
|
|
13
|
+
*/
|
|
14
|
+
export declare function adjustRiskByContext(context: RiskContext): RiskLevel;
|
|
15
|
+
/**
|
|
16
|
+
* Get context description for reporting
|
|
17
|
+
*/
|
|
18
|
+
export declare function getContextDescription(filePath: string): string;
|
|
19
|
+
//# sourceMappingURL=contextual-risk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contextual-risk.d.ts","sourceRoot":"","sources":["../../src/secrets/contextual-risk.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,SAAS,CAAC;CACzB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,CAyBnE;AA8CD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAY9D"}
|