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,257 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Typosquatting Detection
|
|
4
|
+
*
|
|
5
|
+
* Detects potential typosquatting attacks against popular packages
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.typosquatDetector = exports.TyposquatDetector = void 0;
|
|
9
|
+
/**
|
|
10
|
+
* Top 100 most popular npm packages (simplified list)
|
|
11
|
+
*/
|
|
12
|
+
const POPULAR_PACKAGES = [
|
|
13
|
+
'react', 'vue', 'angular', 'express', 'next', 'axios', 'lodash', 'webpack',
|
|
14
|
+
'typescript', 'eslint', 'prettier', 'jest', 'mocha', 'chai', 'babel',
|
|
15
|
+
'moment', 'dayjs', 'date-fns', 'redux', 'mobx', 'rxjs', 'socket.io',
|
|
16
|
+
'fastify', 'koa', 'hapi', 'nestjs', 'prisma', 'mongoose', 'sequelize',
|
|
17
|
+
'typeorm', 'knex', 'pg', 'mysql', 'redis', 'mongodb', 'sqlite3',
|
|
18
|
+
'passport', 'jsonwebtoken', 'bcrypt', 'crypto-js', 'uuid', 'nanoid',
|
|
19
|
+
'dotenv', 'config', 'yargs', 'commander', 'inquirer', 'chalk', 'ora',
|
|
20
|
+
'debug', 'winston', 'pino', 'morgan', 'cors', 'helmet', 'compression',
|
|
21
|
+
'multer', 'body-parser', 'cookie-parser', 'express-session', 'passport',
|
|
22
|
+
'nodemailer', 'sendgrid', 'twilio', 'stripe', 'aws-sdk', 'google-cloud',
|
|
23
|
+
'firebase', 'azure', 'docker', 'kubernetes', 'terraform', 'ansible',
|
|
24
|
+
'jenkins', 'gitlab', 'github', 'bitbucket', 'jira', 'confluence',
|
|
25
|
+
'slack', 'discord', 'telegram', 'whatsapp', 'sentry', 'datadog',
|
|
26
|
+
'newrelic', 'prometheus', 'grafana', 'elasticsearch', 'kibana', 'logstash',
|
|
27
|
+
'kafka', 'rabbitmq', 'celery', 'bull', 'agenda', 'cron', 'node-schedule',
|
|
28
|
+
];
|
|
29
|
+
class TyposquatDetector {
|
|
30
|
+
popularPackages;
|
|
31
|
+
constructor() {
|
|
32
|
+
this.popularPackages = new Set(POPULAR_PACKAGES);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Detect typosquatting
|
|
36
|
+
*/
|
|
37
|
+
async detectTyposquatting(packageName) {
|
|
38
|
+
const patterns = [];
|
|
39
|
+
let targetPackage;
|
|
40
|
+
let maxSimilarity = 0;
|
|
41
|
+
// Check against popular packages
|
|
42
|
+
for (const popular of this.popularPackages) {
|
|
43
|
+
// Skip if exact match
|
|
44
|
+
if (packageName === popular) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
// Check various typosquatting techniques
|
|
48
|
+
const techniques = [
|
|
49
|
+
this.checkCharacterSwap(packageName, popular),
|
|
50
|
+
this.checkMissingCharacter(packageName, popular),
|
|
51
|
+
this.checkExtraCharacter(packageName, popular),
|
|
52
|
+
this.checkHomoglyph(packageName, popular),
|
|
53
|
+
this.checkCombosquatting(packageName, popular),
|
|
54
|
+
this.checkLevenshtein(packageName, popular),
|
|
55
|
+
];
|
|
56
|
+
for (const technique of techniques) {
|
|
57
|
+
if (technique.isMatch) {
|
|
58
|
+
patterns.push(technique.pattern);
|
|
59
|
+
if (technique.similarity > maxSimilarity) {
|
|
60
|
+
maxSimilarity = technique.similarity;
|
|
61
|
+
targetPackage = popular;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
isTyposquat: patterns.length > 0,
|
|
68
|
+
suspiciousPackage: packageName,
|
|
69
|
+
targetPackage,
|
|
70
|
+
similarity: maxSimilarity,
|
|
71
|
+
patterns,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Check for character swap (e.g., raect vs react)
|
|
76
|
+
*/
|
|
77
|
+
checkCharacterSwap(pkg, popular) {
|
|
78
|
+
if (Math.abs(pkg.length - popular.length) > 0) {
|
|
79
|
+
return { isMatch: false, pattern: '', similarity: 0 };
|
|
80
|
+
}
|
|
81
|
+
// Try swapping adjacent characters
|
|
82
|
+
for (let i = 0; i < popular.length - 1; i++) {
|
|
83
|
+
const swapped = popular.substring(0, i) +
|
|
84
|
+
popular[i + 1] +
|
|
85
|
+
popular[i] +
|
|
86
|
+
popular.substring(i + 2);
|
|
87
|
+
if (swapped === pkg) {
|
|
88
|
+
return {
|
|
89
|
+
isMatch: true,
|
|
90
|
+
pattern: 'character_swap',
|
|
91
|
+
similarity: 0.95,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { isMatch: false, pattern: '', similarity: 0 };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Check for missing character (e.g., reat vs react)
|
|
99
|
+
*/
|
|
100
|
+
checkMissingCharacter(pkg, popular) {
|
|
101
|
+
if (pkg.length !== popular.length - 1) {
|
|
102
|
+
return { isMatch: false, pattern: '', similarity: 0 };
|
|
103
|
+
}
|
|
104
|
+
// Try removing each character
|
|
105
|
+
for (let i = 0; i < popular.length; i++) {
|
|
106
|
+
const removed = popular.substring(0, i) + popular.substring(i + 1);
|
|
107
|
+
if (removed === pkg) {
|
|
108
|
+
return {
|
|
109
|
+
isMatch: true,
|
|
110
|
+
pattern: 'missing_character',
|
|
111
|
+
similarity: 0.9,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return { isMatch: false, pattern: '', similarity: 0 };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Check for extra character (e.g., reactt vs react)
|
|
119
|
+
*/
|
|
120
|
+
checkExtraCharacter(pkg, popular) {
|
|
121
|
+
if (pkg.length !== popular.length + 1) {
|
|
122
|
+
return { isMatch: false, pattern: '', similarity: 0 };
|
|
123
|
+
}
|
|
124
|
+
// Try removing each character from pkg
|
|
125
|
+
for (let i = 0; i < pkg.length; i++) {
|
|
126
|
+
const removed = pkg.substring(0, i) + pkg.substring(i + 1);
|
|
127
|
+
if (removed === popular) {
|
|
128
|
+
return {
|
|
129
|
+
isMatch: true,
|
|
130
|
+
pattern: 'extra_character',
|
|
131
|
+
similarity: 0.9,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { isMatch: false, pattern: '', similarity: 0 };
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Check for homoglyph substitution (e.g., react with Cyrillic 'а')
|
|
139
|
+
*/
|
|
140
|
+
checkHomoglyph(pkg, popular) {
|
|
141
|
+
// Common homoglyphs
|
|
142
|
+
const homoglyphs = {
|
|
143
|
+
'a': ['а', 'ɑ', 'α'], // Cyrillic/Greek a
|
|
144
|
+
'e': ['е', ' е'], // Cyrillic e
|
|
145
|
+
'o': ['о', 'ο', '0'], // Cyrillic/Greek o, zero
|
|
146
|
+
'i': ['і', 'ı', 'l', '1'], // Cyrillic i, Turkish i, l, one
|
|
147
|
+
'c': ['с', 'ϲ'], // Cyrillic c
|
|
148
|
+
'p': ['р'], // Cyrillic p
|
|
149
|
+
'x': ['х', 'χ'], // Cyrillic/Greek x
|
|
150
|
+
};
|
|
151
|
+
// Normalize both strings
|
|
152
|
+
const normalize = (str) => {
|
|
153
|
+
let normalized = str;
|
|
154
|
+
for (const [latin, alternates] of Object.entries(homoglyphs)) {
|
|
155
|
+
for (const alt of alternates) {
|
|
156
|
+
normalized = normalized.replace(new RegExp(alt, 'g'), latin);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return normalized;
|
|
160
|
+
};
|
|
161
|
+
const normalizedPkg = normalize(pkg);
|
|
162
|
+
if (normalizedPkg === popular && normalizedPkg !== pkg) {
|
|
163
|
+
return {
|
|
164
|
+
isMatch: true,
|
|
165
|
+
pattern: 'homoglyph',
|
|
166
|
+
similarity: 0.95,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return { isMatch: false, pattern: '', similarity: 0 };
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Check for combosquatting (e.g., react-native-safe vs react)
|
|
173
|
+
*/
|
|
174
|
+
checkCombosquatting(pkg, popular) {
|
|
175
|
+
if (pkg.includes(popular) && pkg !== popular) {
|
|
176
|
+
// Check if it's just adding common suffixes/prefixes
|
|
177
|
+
const commonAdditions = ['-js', '-node', '-utils', '-core', '-plugin', '-webpack', '-babel'];
|
|
178
|
+
for (const addition of commonAdditions) {
|
|
179
|
+
if (pkg === popular + addition || pkg === addition + popular) {
|
|
180
|
+
return {
|
|
181
|
+
isMatch: true,
|
|
182
|
+
pattern: 'combosquatting',
|
|
183
|
+
similarity: 0.7,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return { isMatch: false, pattern: '', similarity: 0 };
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Check Levenshtein distance
|
|
192
|
+
*/
|
|
193
|
+
checkLevenshtein(pkg, popular) {
|
|
194
|
+
const distance = this.levenshteinDistance(pkg, popular);
|
|
195
|
+
const maxLength = Math.max(pkg.length, popular.length);
|
|
196
|
+
const similarity = 1 - (distance / maxLength);
|
|
197
|
+
// Consider it suspicious if similarity > 0.8
|
|
198
|
+
if (similarity >= 0.8 && similarity < 1.0) {
|
|
199
|
+
return {
|
|
200
|
+
isMatch: true,
|
|
201
|
+
pattern: 'levenshtein_distance',
|
|
202
|
+
similarity,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
return { isMatch: false, pattern: '', similarity: 0 };
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Calculate Levenshtein distance
|
|
209
|
+
*/
|
|
210
|
+
levenshteinDistance(a, b) {
|
|
211
|
+
const matrix = [];
|
|
212
|
+
// Initialize matrix with proper dimensions
|
|
213
|
+
for (let i = 0; i <= b.length; i++) {
|
|
214
|
+
matrix[i] = [];
|
|
215
|
+
for (let j = 0; j <= a.length; j++) {
|
|
216
|
+
matrix[i][j] = 0;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Set first column
|
|
220
|
+
for (let i = 0; i <= b.length; i++) {
|
|
221
|
+
matrix[i][0] = i;
|
|
222
|
+
}
|
|
223
|
+
// Set first row
|
|
224
|
+
for (let j = 0; j <= a.length; j++) {
|
|
225
|
+
matrix[0][j] = j;
|
|
226
|
+
}
|
|
227
|
+
for (let i = 1; i <= b.length; i++) {
|
|
228
|
+
for (let j = 1; j <= a.length; j++) {
|
|
229
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
230
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
|
|
234
|
+
matrix[i][j - 1] + 1, // insertion
|
|
235
|
+
matrix[i - 1][j] + 1 // deletion
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return matrix[b.length][a.length];
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Get popular packages list
|
|
244
|
+
*/
|
|
245
|
+
async getPopularPackages() {
|
|
246
|
+
return Array.from(this.popularPackages);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Add custom popular package
|
|
250
|
+
*/
|
|
251
|
+
addPopularPackage(packageName) {
|
|
252
|
+
this.popularPackages.add(packageName);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
exports.TyposquatDetector = TyposquatDetector;
|
|
256
|
+
// Export singleton
|
|
257
|
+
exports.typosquatDetector = new TyposquatDetector();
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vulnerability Database Integration
|
|
3
|
+
*
|
|
4
|
+
* Integrates with multiple vulnerability databases:
|
|
5
|
+
* - OSV (Open Source Vulnerabilities)
|
|
6
|
+
* - GitHub Security Advisories
|
|
7
|
+
* - NVD (National Vulnerability Database)
|
|
8
|
+
*/
|
|
9
|
+
export interface Vulnerability {
|
|
10
|
+
id: string;
|
|
11
|
+
source: 'osv' | 'github' | 'nvd' | 'npm';
|
|
12
|
+
severity: 'low' | 'medium' | 'high' | 'critical';
|
|
13
|
+
cvssScore?: number;
|
|
14
|
+
title: string;
|
|
15
|
+
description: string;
|
|
16
|
+
affectedVersions: string[];
|
|
17
|
+
patchedVersions: string[];
|
|
18
|
+
references: string[];
|
|
19
|
+
publishedAt: Date;
|
|
20
|
+
updatedAt: Date;
|
|
21
|
+
cwe?: string[];
|
|
22
|
+
}
|
|
23
|
+
export interface VulnerabilityCheckResult {
|
|
24
|
+
package: string;
|
|
25
|
+
version: string;
|
|
26
|
+
vulnerabilities: Vulnerability[];
|
|
27
|
+
isVulnerable: boolean;
|
|
28
|
+
highestSeverity: 'none' | 'low' | 'medium' | 'high' | 'critical';
|
|
29
|
+
recommendedVersion?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface VulnerabilityReport {
|
|
32
|
+
projectPath: string;
|
|
33
|
+
scanDate: Date;
|
|
34
|
+
totalPackages: number;
|
|
35
|
+
vulnerablePackages: number;
|
|
36
|
+
results: VulnerabilityCheckResult[];
|
|
37
|
+
summary: {
|
|
38
|
+
critical: number;
|
|
39
|
+
high: number;
|
|
40
|
+
medium: number;
|
|
41
|
+
low: number;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export declare class VulnerabilityDatabase {
|
|
45
|
+
private osvApiUrl;
|
|
46
|
+
private npmAuditUrl;
|
|
47
|
+
/**
|
|
48
|
+
* Check a single package for vulnerabilities
|
|
49
|
+
*/
|
|
50
|
+
checkPackage(name: string, version: string): Promise<VulnerabilityCheckResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Check multiple packages in bulk
|
|
53
|
+
*/
|
|
54
|
+
checkPackages(packages: {
|
|
55
|
+
name: string;
|
|
56
|
+
version: string;
|
|
57
|
+
}[]): Promise<VulnerabilityCheckResult[]>;
|
|
58
|
+
/**
|
|
59
|
+
* Query OSV (Open Source Vulnerabilities) API
|
|
60
|
+
*/
|
|
61
|
+
private queryOSV;
|
|
62
|
+
/**
|
|
63
|
+
* Parse OSV API response
|
|
64
|
+
*/
|
|
65
|
+
private parseOSVResponse;
|
|
66
|
+
/**
|
|
67
|
+
* Query npm audit API
|
|
68
|
+
*/
|
|
69
|
+
private queryNpmAudit;
|
|
70
|
+
/**
|
|
71
|
+
* Parse npm audit response
|
|
72
|
+
*/
|
|
73
|
+
private parseNpmAuditResponse;
|
|
74
|
+
/**
|
|
75
|
+
* Map OSV severity to standard levels
|
|
76
|
+
*/
|
|
77
|
+
private mapOSVSeverity;
|
|
78
|
+
/**
|
|
79
|
+
* Extract affected versions from OSV format
|
|
80
|
+
*/
|
|
81
|
+
private extractAffectedVersions;
|
|
82
|
+
/**
|
|
83
|
+
* Extract patched versions from OSV format
|
|
84
|
+
*/
|
|
85
|
+
private extractPatchedVersions;
|
|
86
|
+
/**
|
|
87
|
+
* Deduplicate vulnerabilities by ID
|
|
88
|
+
*/
|
|
89
|
+
private deduplicateVulnerabilities;
|
|
90
|
+
/**
|
|
91
|
+
* Build result object
|
|
92
|
+
*/
|
|
93
|
+
private buildResult;
|
|
94
|
+
/**
|
|
95
|
+
* Generate a full vulnerability report for a project
|
|
96
|
+
*/
|
|
97
|
+
generateReport(projectPath: string, packages: {
|
|
98
|
+
name: string;
|
|
99
|
+
version: string;
|
|
100
|
+
}[]): Promise<VulnerabilityReport>;
|
|
101
|
+
/**
|
|
102
|
+
* Clear vulnerability cache
|
|
103
|
+
*/
|
|
104
|
+
clearCache(): void;
|
|
105
|
+
/**
|
|
106
|
+
* Get cache statistics
|
|
107
|
+
*/
|
|
108
|
+
getCacheStats(): {
|
|
109
|
+
size: number;
|
|
110
|
+
oldestEntry: Date | null;
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export declare const vulnerabilityDatabase: VulnerabilityDatabase;
|
|
114
|
+
//# sourceMappingURL=vulnerability-db.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vulnerability-db.d.ts","sourceRoot":"","sources":["../../src/supply-chain/vulnerability-db.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,KAAK,GAAG,QAAQ,GAAG,KAAK,GAAG,KAAK,CAAC;IACzC,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE,IAAI,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,aAAa,EAAE,CAAC;IACjC,YAAY,EAAE,OAAO,CAAC;IACtB,eAAe,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;IACjE,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,IAAI,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,wBAAwB,EAAE,CAAC;IACpC,OAAO,EAAE;QACP,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;CACH;AAKD,qBAAa,qBAAqB;IAChC,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,WAAW,CAAkE;IAIrF;;OAEG;IACG,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAoCpF;;OAEG;IACG,aAAa,CAAC,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAevG;;OAEG;YACW,QAAQ;IA6BtB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAqBxB;;OAEG;YACW,aAAa;IAyB3B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAmB7B;;OAEG;IACH,OAAO,CAAC,cAAc;IAYtB;;OAEG;IACH,OAAO,CAAC,uBAAuB;IA0B/B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAsB9B;;OAEG;IACH,OAAO,CAAC,0BAA0B;IAWlC;;OAEG;IACH,OAAO,CAAC,WAAW;IAwBnB;;OAEG;IACG,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA4BtH;;OAEG;IACH,UAAU,IAAI,IAAI;IAIlB;;OAEG;IACH,aAAa,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,IAAI,GAAG,IAAI,CAAA;KAAE;CAY5D;AAGD,eAAO,MAAM,qBAAqB,uBAA8B,CAAC"}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Vulnerability Database Integration
|
|
4
|
+
*
|
|
5
|
+
* Integrates with multiple vulnerability databases:
|
|
6
|
+
* - OSV (Open Source Vulnerabilities)
|
|
7
|
+
* - GitHub Security Advisories
|
|
8
|
+
* - NVD (National Vulnerability Database)
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.vulnerabilityDatabase = exports.VulnerabilityDatabase = void 0;
|
|
12
|
+
const vulnerabilityCache = new Map();
|
|
13
|
+
const CACHE_TTL_MS = 6 * 60 * 60 * 1000; // 6 hours
|
|
14
|
+
class VulnerabilityDatabase {
|
|
15
|
+
osvApiUrl = 'https://api.osv.dev/v1';
|
|
16
|
+
npmAuditUrl = 'https://registry.npmjs.org/-/npm/v1/security/advisories/bulk';
|
|
17
|
+
// GitHub API available for future enhancement with authentication
|
|
18
|
+
// private githubApiUrl = 'https://api.github.com/advisories';
|
|
19
|
+
/**
|
|
20
|
+
* Check a single package for vulnerabilities
|
|
21
|
+
*/
|
|
22
|
+
async checkPackage(name, version) {
|
|
23
|
+
const cacheKey = `${name}@${version}`;
|
|
24
|
+
const cached = vulnerabilityCache.get(cacheKey);
|
|
25
|
+
if (cached && (Date.now() - cached.fetchedAt.getTime()) < CACHE_TTL_MS) {
|
|
26
|
+
return this.buildResult(name, version, cached.data);
|
|
27
|
+
}
|
|
28
|
+
const vulnerabilities = [];
|
|
29
|
+
// Query multiple sources in parallel
|
|
30
|
+
const [osvVulns, npmVulns] = await Promise.allSettled([
|
|
31
|
+
this.queryOSV(name, version),
|
|
32
|
+
this.queryNpmAudit(name, version),
|
|
33
|
+
]);
|
|
34
|
+
if (osvVulns.status === 'fulfilled') {
|
|
35
|
+
vulnerabilities.push(...osvVulns.value);
|
|
36
|
+
}
|
|
37
|
+
if (npmVulns.status === 'fulfilled') {
|
|
38
|
+
vulnerabilities.push(...npmVulns.value);
|
|
39
|
+
}
|
|
40
|
+
// Deduplicate by ID
|
|
41
|
+
const uniqueVulns = this.deduplicateVulnerabilities(vulnerabilities);
|
|
42
|
+
// Cache the result
|
|
43
|
+
vulnerabilityCache.set(cacheKey, {
|
|
44
|
+
data: uniqueVulns,
|
|
45
|
+
fetchedAt: new Date(),
|
|
46
|
+
});
|
|
47
|
+
return this.buildResult(name, version, uniqueVulns);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Check multiple packages in bulk
|
|
51
|
+
*/
|
|
52
|
+
async checkPackages(packages) {
|
|
53
|
+
const batchSize = 20;
|
|
54
|
+
const results = [];
|
|
55
|
+
for (let i = 0; i < packages.length; i += batchSize) {
|
|
56
|
+
const batch = packages.slice(i, i + batchSize);
|
|
57
|
+
const batchResults = await Promise.all(batch.map(pkg => this.checkPackage(pkg.name, pkg.version)));
|
|
58
|
+
results.push(...batchResults);
|
|
59
|
+
}
|
|
60
|
+
return results;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Query OSV (Open Source Vulnerabilities) API
|
|
64
|
+
*/
|
|
65
|
+
async queryOSV(packageName, version) {
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetch(`${this.osvApiUrl}/query`, {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: {
|
|
70
|
+
'Content-Type': 'application/json',
|
|
71
|
+
},
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
package: {
|
|
74
|
+
name: packageName,
|
|
75
|
+
ecosystem: 'npm',
|
|
76
|
+
},
|
|
77
|
+
version: version,
|
|
78
|
+
}),
|
|
79
|
+
signal: AbortSignal.timeout(10000),
|
|
80
|
+
});
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
const data = await response.json();
|
|
85
|
+
return this.parseOSVResponse(data);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.error(`OSV query failed for ${packageName}@${version}:`, error);
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Parse OSV API response
|
|
94
|
+
*/
|
|
95
|
+
parseOSVResponse(data) {
|
|
96
|
+
if (!data.vulns || !Array.isArray(data.vulns)) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
return data.vulns.map((vuln) => ({
|
|
100
|
+
id: vuln.id,
|
|
101
|
+
source: 'osv',
|
|
102
|
+
severity: this.mapOSVSeverity(vuln.severity),
|
|
103
|
+
cvssScore: vuln.severity?.[0]?.score,
|
|
104
|
+
title: vuln.summary || vuln.id,
|
|
105
|
+
description: vuln.details || '',
|
|
106
|
+
affectedVersions: this.extractAffectedVersions(vuln.affected),
|
|
107
|
+
patchedVersions: this.extractPatchedVersions(vuln.affected),
|
|
108
|
+
references: (vuln.references || []).map((r) => r.url),
|
|
109
|
+
publishedAt: new Date(vuln.published || Date.now()),
|
|
110
|
+
updatedAt: new Date(vuln.modified || Date.now()),
|
|
111
|
+
cwe: vuln.database_specific?.cwe_ids || [],
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Query npm audit API
|
|
116
|
+
*/
|
|
117
|
+
async queryNpmAudit(packageName, version) {
|
|
118
|
+
try {
|
|
119
|
+
const response = await fetch(this.npmAuditUrl, {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
headers: {
|
|
122
|
+
'Content-Type': 'application/json',
|
|
123
|
+
},
|
|
124
|
+
body: JSON.stringify({
|
|
125
|
+
[packageName]: [version],
|
|
126
|
+
}),
|
|
127
|
+
signal: AbortSignal.timeout(10000),
|
|
128
|
+
});
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
const data = await response.json();
|
|
133
|
+
return this.parseNpmAuditResponse(data, packageName);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
console.error(`npm audit query failed for ${packageName}@${version}:`, error);
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Parse npm audit response
|
|
142
|
+
*/
|
|
143
|
+
parseNpmAuditResponse(data, packageName) {
|
|
144
|
+
const advisories = data[packageName] || [];
|
|
145
|
+
return advisories.map((advisory) => ({
|
|
146
|
+
id: `npm-${advisory.id || advisory.github_advisory_id}`,
|
|
147
|
+
source: 'npm',
|
|
148
|
+
severity: advisory.severity || 'medium',
|
|
149
|
+
cvssScore: advisory.cvss?.score,
|
|
150
|
+
title: advisory.title || 'Security Advisory',
|
|
151
|
+
description: advisory.overview || advisory.recommendation || '',
|
|
152
|
+
affectedVersions: [advisory.vulnerable_versions || '*'],
|
|
153
|
+
patchedVersions: [advisory.patched_versions || 'No patch available'],
|
|
154
|
+
references: [advisory.url].filter(Boolean),
|
|
155
|
+
publishedAt: new Date(advisory.created || Date.now()),
|
|
156
|
+
updatedAt: new Date(advisory.updated || Date.now()),
|
|
157
|
+
cwe: advisory.cwe ? [advisory.cwe] : [],
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Map OSV severity to standard levels
|
|
162
|
+
*/
|
|
163
|
+
mapOSVSeverity(severity) {
|
|
164
|
+
if (!severity || !Array.isArray(severity) || severity.length === 0) {
|
|
165
|
+
return 'medium';
|
|
166
|
+
}
|
|
167
|
+
const score = severity[0]?.score || 0;
|
|
168
|
+
if (score >= 9.0)
|
|
169
|
+
return 'critical';
|
|
170
|
+
if (score >= 7.0)
|
|
171
|
+
return 'high';
|
|
172
|
+
if (score >= 4.0)
|
|
173
|
+
return 'medium';
|
|
174
|
+
return 'low';
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Extract affected versions from OSV format
|
|
178
|
+
*/
|
|
179
|
+
extractAffectedVersions(affected) {
|
|
180
|
+
if (!affected || !Array.isArray(affected)) {
|
|
181
|
+
return ['*'];
|
|
182
|
+
}
|
|
183
|
+
const versions = [];
|
|
184
|
+
for (const aff of affected) {
|
|
185
|
+
if (aff.versions) {
|
|
186
|
+
versions.push(...aff.versions);
|
|
187
|
+
}
|
|
188
|
+
if (aff.ranges) {
|
|
189
|
+
for (const range of aff.ranges) {
|
|
190
|
+
if (range.events) {
|
|
191
|
+
const introduced = range.events.find((e) => e.introduced);
|
|
192
|
+
const fixed = range.events.find((e) => e.fixed);
|
|
193
|
+
if (introduced) {
|
|
194
|
+
versions.push(`>=${introduced.introduced}${fixed ? ` <${fixed.fixed}` : ''}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return versions.length > 0 ? versions : ['*'];
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Extract patched versions from OSV format
|
|
204
|
+
*/
|
|
205
|
+
extractPatchedVersions(affected) {
|
|
206
|
+
if (!affected || !Array.isArray(affected)) {
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
const versions = [];
|
|
210
|
+
for (const aff of affected) {
|
|
211
|
+
if (aff.ranges) {
|
|
212
|
+
for (const range of aff.ranges) {
|
|
213
|
+
if (range.events) {
|
|
214
|
+
const fixed = range.events.find((e) => e.fixed);
|
|
215
|
+
if (fixed) {
|
|
216
|
+
versions.push(fixed.fixed);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return versions;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Deduplicate vulnerabilities by ID
|
|
226
|
+
*/
|
|
227
|
+
deduplicateVulnerabilities(vulns) {
|
|
228
|
+
const seen = new Set();
|
|
229
|
+
return vulns.filter(vuln => {
|
|
230
|
+
if (seen.has(vuln.id)) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
seen.add(vuln.id);
|
|
234
|
+
return true;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Build result object
|
|
239
|
+
*/
|
|
240
|
+
buildResult(name, version, vulnerabilities) {
|
|
241
|
+
const severityOrder = { critical: 4, high: 3, medium: 2, low: 1, none: 0 };
|
|
242
|
+
let highestSeverity = 'none';
|
|
243
|
+
for (const vuln of vulnerabilities) {
|
|
244
|
+
if (severityOrder[vuln.severity] > severityOrder[highestSeverity]) {
|
|
245
|
+
highestSeverity = vuln.severity;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Find recommended version from patches
|
|
249
|
+
const patchedVersions = vulnerabilities.flatMap(v => v.patchedVersions).filter(Boolean);
|
|
250
|
+
const recommendedVersion = patchedVersions.length > 0 ? patchedVersions[0] : undefined;
|
|
251
|
+
return {
|
|
252
|
+
package: name,
|
|
253
|
+
version,
|
|
254
|
+
vulnerabilities,
|
|
255
|
+
isVulnerable: vulnerabilities.length > 0,
|
|
256
|
+
highestSeverity,
|
|
257
|
+
recommendedVersion,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Generate a full vulnerability report for a project
|
|
262
|
+
*/
|
|
263
|
+
async generateReport(projectPath, packages) {
|
|
264
|
+
const results = await this.checkPackages(packages);
|
|
265
|
+
const summary = {
|
|
266
|
+
critical: 0,
|
|
267
|
+
high: 0,
|
|
268
|
+
medium: 0,
|
|
269
|
+
low: 0,
|
|
270
|
+
};
|
|
271
|
+
for (const result of results) {
|
|
272
|
+
for (const vuln of result.vulnerabilities) {
|
|
273
|
+
summary[vuln.severity]++;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const vulnerablePackages = results.filter(r => r.isVulnerable).length;
|
|
277
|
+
return {
|
|
278
|
+
projectPath,
|
|
279
|
+
scanDate: new Date(),
|
|
280
|
+
totalPackages: packages.length,
|
|
281
|
+
vulnerablePackages,
|
|
282
|
+
results,
|
|
283
|
+
summary,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Clear vulnerability cache
|
|
288
|
+
*/
|
|
289
|
+
clearCache() {
|
|
290
|
+
vulnerabilityCache.clear();
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Get cache statistics
|
|
294
|
+
*/
|
|
295
|
+
getCacheStats() {
|
|
296
|
+
let oldest = null;
|
|
297
|
+
for (const entry of vulnerabilityCache.values()) {
|
|
298
|
+
if (!oldest || entry.fetchedAt < oldest) {
|
|
299
|
+
oldest = entry.fetchedAt;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
size: vulnerabilityCache.size,
|
|
304
|
+
oldestEntry: oldest,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
exports.VulnerabilityDatabase = VulnerabilityDatabase;
|
|
309
|
+
// Export singleton
|
|
310
|
+
exports.vulnerabilityDatabase = new VulnerabilityDatabase();
|