guardrail-security 1.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 +50 -0
- package/dist/attack-surface/analyzer.d.ts.map +1 -0
- package/dist/attack-surface/analyzer.js +83 -0
- package/dist/attack-surface/index.d.ts +5 -0
- package/dist/attack-surface/index.d.ts.map +1 -0
- package/dist/attack-surface/index.js +20 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/languages/index.d.ts +21 -0
- package/dist/languages/index.d.ts.map +1 -0
- package/dist/languages/index.js +78 -0
- package/dist/languages/java-analyzer.d.ts +72 -0
- package/dist/languages/java-analyzer.d.ts.map +1 -0
- package/dist/languages/java-analyzer.js +417 -0
- package/dist/languages/python-analyzer.d.ts +70 -0
- package/dist/languages/python-analyzer.d.ts.map +1 -0
- package/dist/languages/python-analyzer.js +425 -0
- package/dist/license/compatibility-matrix.d.ts +28 -0
- package/dist/license/compatibility-matrix.d.ts.map +1 -0
- package/dist/license/compatibility-matrix.js +323 -0
- package/dist/license/engine.d.ts +77 -0
- package/dist/license/engine.d.ts.map +1 -0
- package/dist/license/engine.js +264 -0
- package/dist/license/index.d.ts +6 -0
- package/dist/license/index.d.ts.map +1 -0
- package/dist/license/index.js +21 -0
- package/dist/sbom/generator.d.ts +108 -0
- package/dist/sbom/generator.d.ts.map +1 -0
- package/dist/sbom/generator.js +271 -0
- package/dist/sbom/index.d.ts +5 -0
- package/dist/sbom/index.d.ts.map +1 -0
- package/dist/sbom/index.js +20 -0
- package/dist/secrets/guardian.d.ts +113 -0
- package/dist/secrets/guardian.d.ts.map +1 -0
- package/dist/secrets/guardian.js +334 -0
- package/dist/secrets/index.d.ts +10 -0
- package/dist/secrets/index.d.ts.map +1 -0
- package/dist/secrets/index.js +30 -0
- package/dist/secrets/patterns.d.ts +42 -0
- package/dist/secrets/patterns.d.ts.map +1 -0
- package/dist/secrets/patterns.js +165 -0
- package/dist/secrets/pre-commit.d.ts +39 -0
- package/dist/secrets/pre-commit.d.ts.map +1 -0
- package/dist/secrets/pre-commit.js +127 -0
- package/dist/secrets/vault-integration.d.ts +83 -0
- package/dist/secrets/vault-integration.d.ts.map +1 -0
- package/dist/secrets/vault-integration.js +295 -0
- package/dist/secrets/vault-providers.d.ts +110 -0
- package/dist/secrets/vault-providers.d.ts.map +1 -0
- package/dist/secrets/vault-providers.js +417 -0
- package/dist/supply-chain/detector.d.ts +80 -0
- package/dist/supply-chain/detector.d.ts.map +1 -0
- package/dist/supply-chain/detector.js +168 -0
- package/dist/supply-chain/index.d.ts +11 -0
- package/dist/supply-chain/index.d.ts.map +1 -0
- package/dist/supply-chain/index.js +26 -0
- package/dist/supply-chain/malicious-db.d.ts +41 -0
- package/dist/supply-chain/malicious-db.d.ts.map +1 -0
- package/dist/supply-chain/malicious-db.js +82 -0
- package/dist/supply-chain/script-analyzer.d.ts +54 -0
- package/dist/supply-chain/script-analyzer.d.ts.map +1 -0
- package/dist/supply-chain/script-analyzer.js +160 -0
- package/dist/supply-chain/typosquat.d.ts +58 -0
- package/dist/supply-chain/typosquat.d.ts.map +1 -0
- package/dist/supply-chain/typosquat.js +257 -0
- package/dist/supply-chain/vulnerability-db.d.ts +114 -0
- package/dist/supply-chain/vulnerability-db.d.ts.map +1 -0
- package/dist/supply-chain/vulnerability-db.js +310 -0
- package/package.json +34 -0
- package/src/__tests__/license/engine.test.ts +250 -0
- package/src/__tests__/supply-chain/typosquat.test.ts +191 -0
- package/src/attack-surface/analyzer.ts +152 -0
- package/src/attack-surface/index.ts +5 -0
- package/src/index.ts +21 -0
- package/src/languages/index.ts +91 -0
- package/src/languages/java-analyzer.ts +490 -0
- package/src/languages/python-analyzer.ts +498 -0
- package/src/license/compatibility-matrix.ts +366 -0
- package/src/license/engine.ts +345 -0
- package/src/license/index.ts +6 -0
- package/src/sbom/generator.ts +355 -0
- package/src/sbom/index.ts +5 -0
- package/src/secrets/guardian.ts +448 -0
- package/src/secrets/index.ts +10 -0
- package/src/secrets/patterns.ts +186 -0
- package/src/secrets/pre-commit.ts +158 -0
- package/src/secrets/vault-integration.ts +360 -0
- package/src/secrets/vault-providers.ts +446 -0
- package/src/supply-chain/detector.ts +252 -0
- package/src/supply-chain/index.ts +11 -0
- package/src/supply-chain/malicious-db.ts +103 -0
- package/src/supply-chain/script-analyzer.ts +194 -0
- package/src/supply-chain/typosquat.ts +302 -0
- package/src/supply-chain/vulnerability-db.ts +386 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SBOM (Software Bill of Materials) Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates SBOMs in CycloneDX and SPDX formats for compliance and security
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync, existsSync, writeFileSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { createHash } from 'crypto';
|
|
10
|
+
|
|
11
|
+
export type SBOMFormat = 'cyclonedx' | 'spdx' | 'json';
|
|
12
|
+
|
|
13
|
+
export interface SBOMComponent {
|
|
14
|
+
type: 'library' | 'framework' | 'application' | 'file' | 'container';
|
|
15
|
+
name: string;
|
|
16
|
+
version: string;
|
|
17
|
+
purl?: string;
|
|
18
|
+
licenses: string[];
|
|
19
|
+
hashes?: { algorithm: string; content: string }[];
|
|
20
|
+
description?: string;
|
|
21
|
+
author?: string;
|
|
22
|
+
supplier?: string;
|
|
23
|
+
externalReferences?: { type: string; url: string }[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SBOMDependency {
|
|
27
|
+
ref: string;
|
|
28
|
+
dependsOn: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface SBOM {
|
|
32
|
+
format: SBOMFormat;
|
|
33
|
+
specVersion: string;
|
|
34
|
+
serialNumber: string;
|
|
35
|
+
version: number;
|
|
36
|
+
metadata: {
|
|
37
|
+
timestamp: string;
|
|
38
|
+
tools: { vendor: string; name: string; version: string }[];
|
|
39
|
+
component: {
|
|
40
|
+
type: string;
|
|
41
|
+
name: string;
|
|
42
|
+
version: string;
|
|
43
|
+
};
|
|
44
|
+
authors?: { name: string; email?: string }[];
|
|
45
|
+
};
|
|
46
|
+
components: SBOMComponent[];
|
|
47
|
+
dependencies: SBOMDependency[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface SBOMGeneratorOptions {
|
|
51
|
+
format: SBOMFormat;
|
|
52
|
+
includeDevDependencies?: boolean;
|
|
53
|
+
includeLicenses?: boolean;
|
|
54
|
+
includeHashes?: boolean;
|
|
55
|
+
outputPath?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export class SBOMGenerator {
|
|
59
|
+
private readonly toolInfo = {
|
|
60
|
+
vendor: 'Guardrail AI',
|
|
61
|
+
name: 'Guardrail-sbom-generator',
|
|
62
|
+
version: '1.0.0',
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generate SBOM for a project
|
|
67
|
+
*/
|
|
68
|
+
async generate(projectPath: string, options: SBOMGeneratorOptions): Promise<SBOM> {
|
|
69
|
+
const packageJsonPath = join(projectPath, 'package.json');
|
|
70
|
+
|
|
71
|
+
if (!existsSync(packageJsonPath)) {
|
|
72
|
+
throw new Error('package.json not found in project path');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
76
|
+
const components = await this.extractComponents(projectPath, packageJson, options);
|
|
77
|
+
const dependencies = this.buildDependencyGraph(packageJson, options);
|
|
78
|
+
|
|
79
|
+
const sbom: SBOM = {
|
|
80
|
+
format: options.format,
|
|
81
|
+
specVersion: options.format === 'cyclonedx' ? '1.5' : '2.3',
|
|
82
|
+
serialNumber: `urn:uuid:${this.generateUUID()}`,
|
|
83
|
+
version: 1,
|
|
84
|
+
metadata: {
|
|
85
|
+
timestamp: new Date().toISOString(),
|
|
86
|
+
tools: [this.toolInfo],
|
|
87
|
+
component: {
|
|
88
|
+
type: 'application',
|
|
89
|
+
name: packageJson.name || 'unknown',
|
|
90
|
+
version: packageJson.version || '0.0.0',
|
|
91
|
+
},
|
|
92
|
+
authors: packageJson.author ? [{ name: packageJson.author }] : undefined,
|
|
93
|
+
},
|
|
94
|
+
components,
|
|
95
|
+
dependencies,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (options.outputPath) {
|
|
99
|
+
await this.writeSBOM(sbom, options.outputPath, options.format);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return sbom;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Extract components from package.json
|
|
107
|
+
*/
|
|
108
|
+
private async extractComponents(
|
|
109
|
+
projectPath: string,
|
|
110
|
+
packageJson: any,
|
|
111
|
+
options: SBOMGeneratorOptions
|
|
112
|
+
): Promise<SBOMComponent[]> {
|
|
113
|
+
const components: SBOMComponent[] = [];
|
|
114
|
+
const deps = { ...packageJson.dependencies };
|
|
115
|
+
|
|
116
|
+
if (options.includeDevDependencies) {
|
|
117
|
+
Object.assign(deps, packageJson.devDependencies);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
121
|
+
const versionStr = String(version).replace(/^[\^~]/, '');
|
|
122
|
+
|
|
123
|
+
const component: SBOMComponent = {
|
|
124
|
+
type: 'library',
|
|
125
|
+
name,
|
|
126
|
+
version: versionStr,
|
|
127
|
+
purl: `pkg:npm/${name}@${versionStr}`,
|
|
128
|
+
licenses: [],
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Try to get license info
|
|
132
|
+
if (options.includeLicenses) {
|
|
133
|
+
const license = await this.getLicenseForPackage(projectPath, name);
|
|
134
|
+
if (license) {
|
|
135
|
+
component.licenses = [license];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Generate hashes if requested
|
|
140
|
+
if (options.includeHashes) {
|
|
141
|
+
const packagePath = join(projectPath, 'node_modules', name, 'package.json');
|
|
142
|
+
if (existsSync(packagePath)) {
|
|
143
|
+
const content = readFileSync(packagePath, 'utf-8');
|
|
144
|
+
component.hashes = [
|
|
145
|
+
{ algorithm: 'SHA-256', content: this.hashContent(content, 'sha256') },
|
|
146
|
+
{ algorithm: 'SHA-512', content: this.hashContent(content, 'sha512') },
|
|
147
|
+
];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
components.push(component);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return components;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Build dependency graph
|
|
159
|
+
*/
|
|
160
|
+
private buildDependencyGraph(packageJson: any, options: SBOMGeneratorOptions): SBOMDependency[] {
|
|
161
|
+
const dependencies: SBOMDependency[] = [];
|
|
162
|
+
const rootRef = `pkg:npm/${packageJson.name}@${packageJson.version}`;
|
|
163
|
+
|
|
164
|
+
const deps = Object.keys(packageJson.dependencies || {});
|
|
165
|
+
const devDeps = options.includeDevDependencies
|
|
166
|
+
? Object.keys(packageJson.devDependencies || {})
|
|
167
|
+
: [];
|
|
168
|
+
|
|
169
|
+
// Root dependency
|
|
170
|
+
dependencies.push({
|
|
171
|
+
ref: rootRef,
|
|
172
|
+
dependsOn: [...deps, ...devDeps].map(name => {
|
|
173
|
+
const version = packageJson.dependencies?.[name] || packageJson.devDependencies?.[name];
|
|
174
|
+
return `pkg:npm/${name}@${String(version).replace(/^[\^~]/, '')}`;
|
|
175
|
+
}),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return dependencies;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get license for a package
|
|
183
|
+
*/
|
|
184
|
+
private async getLicenseForPackage(projectPath: string, packageName: string): Promise<string | null> {
|
|
185
|
+
const packagePath = join(projectPath, 'node_modules', packageName, 'package.json');
|
|
186
|
+
|
|
187
|
+
if (existsSync(packagePath)) {
|
|
188
|
+
try {
|
|
189
|
+
const pkgJson = JSON.parse(readFileSync(packagePath, 'utf-8'));
|
|
190
|
+
return pkgJson.license || null;
|
|
191
|
+
} catch {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Hash content
|
|
201
|
+
*/
|
|
202
|
+
private hashContent(content: string, algorithm: string): string {
|
|
203
|
+
return createHash(algorithm).update(content).digest('hex');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Generate UUID
|
|
208
|
+
*/
|
|
209
|
+
private generateUUID(): string {
|
|
210
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
211
|
+
const r = Math.random() * 16 | 0;
|
|
212
|
+
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
213
|
+
return v.toString(16);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Write SBOM to file
|
|
219
|
+
*/
|
|
220
|
+
private async writeSBOM(sbom: SBOM, outputPath: string, format: SBOMFormat): Promise<void> {
|
|
221
|
+
let content: string;
|
|
222
|
+
|
|
223
|
+
switch (format) {
|
|
224
|
+
case 'cyclonedx':
|
|
225
|
+
content = this.toCycloneDXJSON(sbom);
|
|
226
|
+
break;
|
|
227
|
+
case 'spdx':
|
|
228
|
+
content = this.toSPDXJSON(sbom);
|
|
229
|
+
break;
|
|
230
|
+
default:
|
|
231
|
+
content = JSON.stringify(sbom, null, 2);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
writeFileSync(outputPath, content, 'utf-8');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Convert to CycloneDX JSON format
|
|
239
|
+
*/
|
|
240
|
+
toCycloneDXJSON(sbom: SBOM): string {
|
|
241
|
+
const cyclonedx = {
|
|
242
|
+
bomFormat: 'CycloneDX',
|
|
243
|
+
specVersion: '1.5',
|
|
244
|
+
serialNumber: sbom.serialNumber,
|
|
245
|
+
version: sbom.version,
|
|
246
|
+
metadata: {
|
|
247
|
+
timestamp: sbom.metadata.timestamp,
|
|
248
|
+
tools: sbom.metadata.tools.map(t => ({
|
|
249
|
+
vendor: t.vendor,
|
|
250
|
+
name: t.name,
|
|
251
|
+
version: t.version,
|
|
252
|
+
})),
|
|
253
|
+
component: {
|
|
254
|
+
type: sbom.metadata.component.type,
|
|
255
|
+
name: sbom.metadata.component.name,
|
|
256
|
+
version: sbom.metadata.component.version,
|
|
257
|
+
'bom-ref': `pkg:npm/${sbom.metadata.component.name}@${sbom.metadata.component.version}`,
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
components: sbom.components.map(c => ({
|
|
261
|
+
type: c.type,
|
|
262
|
+
name: c.name,
|
|
263
|
+
version: c.version,
|
|
264
|
+
purl: c.purl,
|
|
265
|
+
'bom-ref': c.purl,
|
|
266
|
+
licenses: c.licenses.map(l => ({ license: { id: l } })),
|
|
267
|
+
hashes: c.hashes?.map(h => ({ alg: h.algorithm, content: h.content })),
|
|
268
|
+
})),
|
|
269
|
+
dependencies: sbom.dependencies.map(d => ({
|
|
270
|
+
ref: d.ref,
|
|
271
|
+
dependsOn: d.dependsOn,
|
|
272
|
+
})),
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
return JSON.stringify(cyclonedx, null, 2);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Convert to SPDX JSON format
|
|
280
|
+
*/
|
|
281
|
+
toSPDXJSON(sbom: SBOM): string {
|
|
282
|
+
const spdx = {
|
|
283
|
+
spdxVersion: 'SPDX-2.3',
|
|
284
|
+
dataLicense: 'CC0-1.0',
|
|
285
|
+
SPDXID: 'SPDXRef-DOCUMENT',
|
|
286
|
+
name: sbom.metadata.component.name,
|
|
287
|
+
documentNamespace: `https://Guardrail.ai/sbom/${sbom.serialNumber}`,
|
|
288
|
+
creationInfo: {
|
|
289
|
+
created: sbom.metadata.timestamp,
|
|
290
|
+
creators: [`Tool: ${sbom.metadata.tools[0]?.name}-${sbom.metadata.tools[0]?.version}`],
|
|
291
|
+
},
|
|
292
|
+
packages: sbom.components.map((c, i) => ({
|
|
293
|
+
SPDXID: `SPDXRef-Package-${i}`,
|
|
294
|
+
name: c.name,
|
|
295
|
+
versionInfo: c.version,
|
|
296
|
+
downloadLocation: `https://registry.npmjs.org/${c.name}/-/${c.name}-${c.version}.tgz`,
|
|
297
|
+
filesAnalyzed: false,
|
|
298
|
+
licenseConcluded: c.licenses[0] || 'NOASSERTION',
|
|
299
|
+
licenseDeclared: c.licenses[0] || 'NOASSERTION',
|
|
300
|
+
copyrightText: 'NOASSERTION',
|
|
301
|
+
externalRefs: c.purl ? [{
|
|
302
|
+
referenceCategory: 'PACKAGE-MANAGER',
|
|
303
|
+
referenceType: 'purl',
|
|
304
|
+
referenceLocator: c.purl,
|
|
305
|
+
}] : [],
|
|
306
|
+
})),
|
|
307
|
+
relationships: [
|
|
308
|
+
{
|
|
309
|
+
spdxElementId: 'SPDXRef-DOCUMENT',
|
|
310
|
+
relatedSpdxElement: 'SPDXRef-Package-0',
|
|
311
|
+
relationshipType: 'DESCRIBES',
|
|
312
|
+
},
|
|
313
|
+
...sbom.components.slice(1).map((_, i) => ({
|
|
314
|
+
spdxElementId: 'SPDXRef-Package-0',
|
|
315
|
+
relatedSpdxElement: `SPDXRef-Package-${i + 1}`,
|
|
316
|
+
relationshipType: 'DEPENDS_ON',
|
|
317
|
+
})),
|
|
318
|
+
],
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
return JSON.stringify(spdx, null, 2);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Validate SBOM structure
|
|
326
|
+
*/
|
|
327
|
+
validateSBOM(sbom: SBOM): { valid: boolean; errors: string[] } {
|
|
328
|
+
const errors: string[] = [];
|
|
329
|
+
|
|
330
|
+
if (!sbom.metadata?.component?.name) {
|
|
331
|
+
errors.push('Missing component name in metadata');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (!sbom.components || sbom.components.length === 0) {
|
|
335
|
+
errors.push('No components found in SBOM');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
for (const component of sbom.components) {
|
|
339
|
+
if (!component.name) {
|
|
340
|
+
errors.push('Component missing name');
|
|
341
|
+
}
|
|
342
|
+
if (!component.version) {
|
|
343
|
+
errors.push(`Component ${component.name} missing version`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
valid: errors.length === 0,
|
|
349
|
+
errors,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Export singleton
|
|
355
|
+
export const sbomGenerator = new SBOMGenerator();
|