guardrail-cli 1.0.5 → 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/README.md +483 -10
- package/dist/commands/baseline.d.ts +7 -0
- package/dist/commands/baseline.d.ts.map +1 -0
- package/dist/commands/baseline.js +79 -0
- package/dist/commands/baseline.js.map +1 -0
- package/dist/commands/cache.d.ts +13 -0
- package/dist/commands/cache.d.ts.map +1 -0
- package/dist/commands/cache.js +165 -0
- package/dist/commands/cache.js.map +1 -0
- package/dist/commands/evidence.d.ts +45 -0
- package/dist/commands/evidence.d.ts.map +1 -0
- package/dist/commands/evidence.js +197 -0
- package/dist/commands/evidence.js.map +1 -0
- package/dist/commands/index.d.ts +8 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +15 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/scan-secrets.d.ts +47 -0
- package/dist/commands/scan-secrets.d.ts.map +1 -0
- package/dist/commands/scan-secrets.js +225 -0
- package/dist/commands/scan-secrets.js.map +1 -0
- package/dist/commands/scan-vulnerabilities-enhanced.d.ts +41 -0
- package/dist/commands/scan-vulnerabilities-enhanced.d.ts.map +1 -0
- package/dist/commands/scan-vulnerabilities-enhanced.js +368 -0
- package/dist/commands/scan-vulnerabilities-enhanced.js.map +1 -0
- package/dist/commands/scan-vulnerabilities-osv.d.ts +58 -0
- package/dist/commands/scan-vulnerabilities-osv.d.ts.map +1 -0
- package/dist/commands/scan-vulnerabilities-osv.js +716 -0
- package/dist/commands/scan-vulnerabilities-osv.js.map +1 -0
- package/dist/commands/scan-vulnerabilities.d.ts +32 -0
- package/dist/commands/scan-vulnerabilities.d.ts.map +1 -0
- package/dist/commands/scan-vulnerabilities.js +283 -0
- package/dist/commands/scan-vulnerabilities.js.map +1 -0
- package/dist/commands/secrets-allowlist.d.ts +7 -0
- package/dist/commands/secrets-allowlist.d.ts.map +1 -0
- package/dist/commands/secrets-allowlist.js +85 -0
- package/dist/commands/secrets-allowlist.js.map +1 -0
- package/dist/fix/applicator.d.ts +44 -0
- package/dist/fix/applicator.d.ts.map +1 -0
- package/dist/fix/applicator.js +144 -0
- package/dist/fix/applicator.js.map +1 -0
- package/dist/fix/backup.d.ts +38 -0
- package/dist/fix/backup.d.ts.map +1 -0
- package/dist/fix/backup.js +154 -0
- package/dist/fix/backup.js.map +1 -0
- package/dist/fix/engine.d.ts +55 -0
- package/dist/fix/engine.d.ts.map +1 -0
- package/dist/fix/engine.js +285 -0
- package/dist/fix/engine.js.map +1 -0
- package/dist/fix/index.d.ts +5 -0
- package/dist/fix/index.d.ts.map +1 -0
- package/dist/fix/index.js +12 -0
- package/dist/fix/index.js.map +1 -0
- package/dist/fix/interactive.d.ts +22 -0
- package/dist/fix/interactive.d.ts.map +1 -0
- package/dist/fix/interactive.js +172 -0
- package/dist/fix/interactive.js.map +1 -0
- package/dist/formatters/index.d.ts +6 -0
- package/dist/formatters/index.d.ts.map +1 -0
- package/dist/formatters/index.js +11 -0
- package/dist/formatters/index.js.map +1 -0
- package/dist/formatters/sarif-enhanced.d.ts +78 -0
- package/dist/formatters/sarif-enhanced.d.ts.map +1 -0
- package/dist/formatters/sarif-enhanced.js +144 -0
- package/dist/formatters/sarif-enhanced.js.map +1 -0
- package/dist/formatters/sarif-v2.d.ts +121 -0
- package/dist/formatters/sarif-v2.d.ts.map +1 -0
- package/dist/formatters/sarif-v2.js +356 -0
- package/dist/formatters/sarif-v2.js.map +1 -0
- package/dist/formatters/sarif.d.ts +72 -0
- package/dist/formatters/sarif.d.ts.map +1 -0
- package/dist/formatters/sarif.js +146 -0
- package/dist/formatters/sarif.js.map +1 -0
- package/dist/index.js +3362 -1397
- package/dist/index.js.map +1 -1
- package/dist/init/ci-generator.d.ts +18 -0
- package/dist/init/ci-generator.d.ts.map +1 -0
- package/dist/init/ci-generator.js +251 -0
- package/dist/init/ci-generator.js.map +1 -0
- package/dist/init/detect-framework.d.ts +15 -0
- package/dist/init/detect-framework.d.ts.map +1 -0
- package/dist/init/detect-framework.js +299 -0
- package/dist/init/detect-framework.js.map +1 -0
- package/dist/init/hooks-installer.d.ts +22 -0
- package/dist/init/hooks-installer.d.ts.map +1 -0
- package/dist/init/hooks-installer.js +302 -0
- package/dist/init/hooks-installer.js.map +1 -0
- package/dist/init/index.d.ts +8 -0
- package/dist/init/index.d.ts.map +1 -0
- package/dist/init/index.js +22 -0
- package/dist/init/index.js.map +1 -0
- package/dist/init/templates.d.ts +401 -0
- package/dist/init/templates.d.ts.map +1 -0
- package/dist/init/templates.js +240 -0
- package/dist/init/templates.js.map +1 -0
- package/dist/reality/reality-runner.d.ts +76 -0
- package/dist/reality/reality-runner.d.ts.map +1 -0
- package/dist/reality/reality-runner.js +454 -0
- package/dist/reality/reality-runner.js.map +1 -0
- package/dist/runtime/auth-utils.d.ts +43 -0
- package/dist/runtime/auth-utils.d.ts.map +1 -0
- package/dist/runtime/auth-utils.js +126 -0
- package/dist/runtime/auth-utils.js.map +1 -0
- package/dist/runtime/client.d.ts +74 -0
- package/dist/runtime/client.d.ts.map +1 -0
- package/dist/runtime/client.js +222 -0
- package/dist/runtime/client.js.map +1 -0
- package/dist/runtime/creds.d.ts +48 -0
- package/dist/runtime/creds.d.ts.map +1 -0
- package/dist/runtime/creds.js +245 -0
- package/dist/runtime/creds.js.map +1 -0
- package/dist/runtime/exit-codes.d.ts +47 -0
- package/dist/runtime/exit-codes.d.ts.map +1 -0
- package/dist/runtime/exit-codes.js +91 -0
- package/dist/runtime/exit-codes.js.map +1 -0
- package/dist/runtime/index.d.ts +9 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +25 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/semver.d.ts +37 -0
- package/dist/runtime/semver.d.ts.map +1 -0
- package/dist/runtime/semver.js +110 -0
- package/dist/runtime/semver.js.map +1 -0
- package/dist/scanner/baseline.d.ts +52 -0
- package/dist/scanner/baseline.d.ts.map +1 -0
- package/dist/scanner/baseline.js +85 -0
- package/dist/scanner/baseline.js.map +1 -0
- package/dist/scanner/incremental.d.ts +30 -0
- package/dist/scanner/incremental.d.ts.map +1 -0
- package/dist/scanner/incremental.js +82 -0
- package/dist/scanner/incremental.js.map +1 -0
- package/dist/scanner/parallel.d.ts +43 -0
- package/dist/scanner/parallel.d.ts.map +1 -0
- package/dist/scanner/parallel.js +99 -0
- package/dist/scanner/parallel.js.map +1 -0
- package/dist/ui/frame.d.ts +68 -0
- package/dist/ui/frame.d.ts.map +1 -0
- package/dist/ui/frame.js +165 -0
- package/dist/ui/frame.js.map +1 -0
- package/dist/ui/index.d.ts +5 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +16 -0
- package/dist/ui/index.js.map +1 -0
- package/package.json +42 -9
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* scan:vulnerabilities command (OSV Integration)
|
|
4
|
+
*
|
|
5
|
+
* Enterprise-grade vulnerability detection using real-time OSV API
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Real-time OSV API queries with 24h caching
|
|
9
|
+
* - Lockfile parsing (package-lock.json, pnpm-lock.yaml, yarn.lock)
|
|
10
|
+
* - Multi-ecosystem support (npm, PyPI, RubyGems, Go)
|
|
11
|
+
* - CVSS scoring and vectors with optional NVD enrichment
|
|
12
|
+
* - Remediation path analysis
|
|
13
|
+
* - SARIF v2.1.0 output for GitHub code scanning
|
|
14
|
+
* - Direct vs transitive vulnerability grouping
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.scanVulnerabilitiesOSV = scanVulnerabilitiesOSV;
|
|
18
|
+
exports.toSarifVulnerabilitiesOSV = toSarifVulnerabilitiesOSV;
|
|
19
|
+
exports.outputOSVVulnResults = outputOSVVulnResults;
|
|
20
|
+
exports.registerScanVulnerabilitiesOSVCommand = registerScanVulnerabilitiesOSVCommand;
|
|
21
|
+
const path_1 = require("path");
|
|
22
|
+
const fs_1 = require("fs");
|
|
23
|
+
const exit_codes_1 = require("../runtime/exit-codes");
|
|
24
|
+
const vulnerability_db_1 = require("@guardrail/security/supply-chain/vulnerability-db");
|
|
25
|
+
const evidence_1 = require("./evidence");
|
|
26
|
+
const c = {
|
|
27
|
+
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
28
|
+
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
29
|
+
critical: (s) => `\x1b[35m${s}\x1b[0m`,
|
|
30
|
+
high: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
31
|
+
medium: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
32
|
+
low: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
33
|
+
success: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
34
|
+
info: (s) => `\x1b[34m${s}\x1b[0m`,
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Detect ecosystems from project files
|
|
38
|
+
*/
|
|
39
|
+
function detectEcosystems(projectPath) {
|
|
40
|
+
const ecosystems = [];
|
|
41
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(projectPath, 'package.json')) ||
|
|
42
|
+
(0, fs_1.existsSync)((0, path_1.join)(projectPath, 'package-lock.json')) ||
|
|
43
|
+
(0, fs_1.existsSync)((0, path_1.join)(projectPath, 'pnpm-lock.yaml')) ||
|
|
44
|
+
(0, fs_1.existsSync)((0, path_1.join)(projectPath, 'yarn.lock'))) {
|
|
45
|
+
ecosystems.push('npm');
|
|
46
|
+
}
|
|
47
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(projectPath, 'requirements.txt')) ||
|
|
48
|
+
(0, fs_1.existsSync)((0, path_1.join)(projectPath, 'Pipfile')) ||
|
|
49
|
+
(0, fs_1.existsSync)((0, path_1.join)(projectPath, 'poetry.lock')) ||
|
|
50
|
+
(0, fs_1.existsSync)((0, path_1.join)(projectPath, 'pyproject.toml'))) {
|
|
51
|
+
ecosystems.push('PyPI');
|
|
52
|
+
}
|
|
53
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(projectPath, 'Gemfile')) ||
|
|
54
|
+
(0, fs_1.existsSync)((0, path_1.join)(projectPath, 'Gemfile.lock'))) {
|
|
55
|
+
ecosystems.push('RubyGems');
|
|
56
|
+
}
|
|
57
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(projectPath, 'go.mod')) ||
|
|
58
|
+
(0, fs_1.existsSync)((0, path_1.join)(projectPath, 'go.sum'))) {
|
|
59
|
+
ecosystems.push('Go');
|
|
60
|
+
}
|
|
61
|
+
return ecosystems;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Find line number of a dependency in package.json
|
|
65
|
+
*/
|
|
66
|
+
function findPackageJsonLine(content, packageName) {
|
|
67
|
+
const lines = content.split('\n');
|
|
68
|
+
for (let i = 0; i < lines.length; i++) {
|
|
69
|
+
if (lines[i].includes(`"${packageName}"`)) {
|
|
70
|
+
return i + 1;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Parse npm dependencies from package.json and lockfiles
|
|
77
|
+
*/
|
|
78
|
+
function parseNpmDependencies(projectPath) {
|
|
79
|
+
const packages = [];
|
|
80
|
+
const lockfiles = [];
|
|
81
|
+
const packageJsonPath = (0, path_1.join)(projectPath, 'package.json');
|
|
82
|
+
if (!(0, fs_1.existsSync)(packageJsonPath))
|
|
83
|
+
return { packages, lockfiles };
|
|
84
|
+
let packageJsonContent = '';
|
|
85
|
+
try {
|
|
86
|
+
packageJsonContent = (0, fs_1.readFileSync)(packageJsonPath, 'utf-8');
|
|
87
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
88
|
+
const deps = packageJson.dependencies || {};
|
|
89
|
+
const devDeps = packageJson.devDependencies || {};
|
|
90
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
91
|
+
const cleanVersion = String(version).replace(/^[\^~>=<]+/, '');
|
|
92
|
+
const line = findPackageJsonLine(packageJsonContent, name);
|
|
93
|
+
packages.push({
|
|
94
|
+
name,
|
|
95
|
+
version: cleanVersion,
|
|
96
|
+
ecosystem: 'npm',
|
|
97
|
+
isDirect: true,
|
|
98
|
+
location: { file: 'package.json', line }
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
for (const [name, version] of Object.entries(devDeps)) {
|
|
102
|
+
const cleanVersion = String(version).replace(/^[\^~>=<]+/, '');
|
|
103
|
+
const line = findPackageJsonLine(packageJsonContent, name);
|
|
104
|
+
packages.push({
|
|
105
|
+
name,
|
|
106
|
+
version: cleanVersion,
|
|
107
|
+
ecosystem: 'npm',
|
|
108
|
+
isDirect: true,
|
|
109
|
+
location: { file: 'package.json', line }
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return { packages, lockfiles };
|
|
115
|
+
}
|
|
116
|
+
// Parse package-lock.json
|
|
117
|
+
const npmLockPath = (0, path_1.join)(projectPath, 'package-lock.json');
|
|
118
|
+
if ((0, fs_1.existsSync)(npmLockPath)) {
|
|
119
|
+
lockfiles.push('package-lock.json');
|
|
120
|
+
try {
|
|
121
|
+
const lockData = JSON.parse((0, fs_1.readFileSync)(npmLockPath, 'utf-8'));
|
|
122
|
+
const lockPackages = lockData.packages || {};
|
|
123
|
+
for (const [pkgPath, pkgInfo] of Object.entries(lockPackages)) {
|
|
124
|
+
if (typeof pkgInfo === 'object' && pkgInfo !== null) {
|
|
125
|
+
const info = pkgInfo;
|
|
126
|
+
const name = info.name || pkgPath.replace(/^node_modules\//, '');
|
|
127
|
+
const version = info.version;
|
|
128
|
+
if (name && version && !packages.find(p => p.name === name && p.version === version)) {
|
|
129
|
+
packages.push({
|
|
130
|
+
name,
|
|
131
|
+
version,
|
|
132
|
+
ecosystem: 'npm',
|
|
133
|
+
isDirect: false,
|
|
134
|
+
location: { file: 'package-lock.json' }
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// Lockfile parsing failed
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Parse pnpm-lock.yaml
|
|
145
|
+
const pnpmLockPath = (0, path_1.join)(projectPath, 'pnpm-lock.yaml');
|
|
146
|
+
if ((0, fs_1.existsSync)(pnpmLockPath)) {
|
|
147
|
+
lockfiles.push('pnpm-lock.yaml');
|
|
148
|
+
try {
|
|
149
|
+
const content = (0, fs_1.readFileSync)(pnpmLockPath, 'utf-8');
|
|
150
|
+
// Simple YAML parsing for pnpm lockfile
|
|
151
|
+
const lines = content.split('\n');
|
|
152
|
+
let inPackages = false;
|
|
153
|
+
for (const line of lines) {
|
|
154
|
+
if (line.startsWith('packages:')) {
|
|
155
|
+
inPackages = true;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (inPackages && line.match(/^\s{2}'?\/([^@]+)@([^':]+)/)) {
|
|
159
|
+
const match = line.match(/^\s{2}'?\/([^@]+)@([^':]+)/);
|
|
160
|
+
if (match) {
|
|
161
|
+
const name = match[1];
|
|
162
|
+
const version = match[2].replace(/['"]/g, '');
|
|
163
|
+
if (!packages.find(p => p.name === name && p.version === version)) {
|
|
164
|
+
packages.push({
|
|
165
|
+
name,
|
|
166
|
+
version,
|
|
167
|
+
ecosystem: 'npm',
|
|
168
|
+
isDirect: false,
|
|
169
|
+
location: { file: 'pnpm-lock.yaml' }
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
// Lockfile parsing failed
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Parse yarn.lock
|
|
181
|
+
const yarnLockPath = (0, path_1.join)(projectPath, 'yarn.lock');
|
|
182
|
+
if ((0, fs_1.existsSync)(yarnLockPath)) {
|
|
183
|
+
lockfiles.push('yarn.lock');
|
|
184
|
+
try {
|
|
185
|
+
const content = (0, fs_1.readFileSync)(yarnLockPath, 'utf-8');
|
|
186
|
+
const lines = content.split('\n');
|
|
187
|
+
let currentPackage = '';
|
|
188
|
+
for (const line of lines) {
|
|
189
|
+
// Match package header: "package@version:" or package@version:
|
|
190
|
+
const headerMatch = line.match(/^"?([^@]+)@[^"]+:?\s*$/);
|
|
191
|
+
if (headerMatch) {
|
|
192
|
+
currentPackage = headerMatch[1];
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
// Match version line
|
|
196
|
+
if (currentPackage && line.match(/^\s+version\s+"?([^"]+)"?/)) {
|
|
197
|
+
const versionMatch = line.match(/^\s+version\s+"?([^"]+)"?/);
|
|
198
|
+
if (versionMatch) {
|
|
199
|
+
const version = versionMatch[1];
|
|
200
|
+
if (!packages.find(p => p.name === currentPackage && p.version === version)) {
|
|
201
|
+
packages.push({
|
|
202
|
+
name: currentPackage,
|
|
203
|
+
version,
|
|
204
|
+
ecosystem: 'npm',
|
|
205
|
+
isDirect: false,
|
|
206
|
+
location: { file: 'yarn.lock' }
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
currentPackage = '';
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
// Lockfile parsing failed
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return { packages, lockfiles };
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Parse Python dependencies
|
|
222
|
+
*/
|
|
223
|
+
function parsePythonDependencies(projectPath) {
|
|
224
|
+
const packages = [];
|
|
225
|
+
const lockfiles = [];
|
|
226
|
+
const requirementsPath = (0, path_1.join)(projectPath, 'requirements.txt');
|
|
227
|
+
if ((0, fs_1.existsSync)(requirementsPath)) {
|
|
228
|
+
lockfiles.push('requirements.txt');
|
|
229
|
+
try {
|
|
230
|
+
const content = (0, fs_1.readFileSync)(requirementsPath, 'utf-8');
|
|
231
|
+
const lines = content.split('\n');
|
|
232
|
+
for (let i = 0; i < lines.length; i++) {
|
|
233
|
+
const line = lines[i].trim();
|
|
234
|
+
if (!line || line.startsWith('#'))
|
|
235
|
+
continue;
|
|
236
|
+
const match = line.match(/^([a-zA-Z0-9_-]+)(?:==|>=|<=|~=|>|<)?([\d.]+)?/);
|
|
237
|
+
if (match) {
|
|
238
|
+
const name = match[1];
|
|
239
|
+
const version = match[2] || 'latest';
|
|
240
|
+
packages.push({
|
|
241
|
+
name,
|
|
242
|
+
version,
|
|
243
|
+
ecosystem: 'PyPI',
|
|
244
|
+
isDirect: true,
|
|
245
|
+
location: { file: 'requirements.txt', line: i + 1 }
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// Requirements parsing failed
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Parse Pipfile.lock
|
|
255
|
+
const pipfileLockPath = (0, path_1.join)(projectPath, 'Pipfile.lock');
|
|
256
|
+
if ((0, fs_1.existsSync)(pipfileLockPath)) {
|
|
257
|
+
lockfiles.push('Pipfile.lock');
|
|
258
|
+
try {
|
|
259
|
+
const lockData = JSON.parse((0, fs_1.readFileSync)(pipfileLockPath, 'utf-8'));
|
|
260
|
+
const sections = ['default', 'develop'];
|
|
261
|
+
for (const section of sections) {
|
|
262
|
+
const deps = lockData[section] || {};
|
|
263
|
+
for (const [name, info] of Object.entries(deps)) {
|
|
264
|
+
if (typeof info === 'object' && info !== null) {
|
|
265
|
+
const pkgInfo = info;
|
|
266
|
+
const version = pkgInfo.version?.replace(/^==/, '') || 'latest';
|
|
267
|
+
if (!packages.find(p => p.name === name)) {
|
|
268
|
+
packages.push({
|
|
269
|
+
name,
|
|
270
|
+
version,
|
|
271
|
+
ecosystem: 'PyPI',
|
|
272
|
+
isDirect: section === 'default',
|
|
273
|
+
location: { file: 'Pipfile.lock' }
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
// Pipfile.lock parsing failed
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return { packages, lockfiles };
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Parse Ruby dependencies
|
|
288
|
+
*/
|
|
289
|
+
function parseRubyDependencies(projectPath) {
|
|
290
|
+
const packages = [];
|
|
291
|
+
const lockfiles = [];
|
|
292
|
+
// Parse Gemfile.lock for exact versions
|
|
293
|
+
const gemfileLockPath = (0, path_1.join)(projectPath, 'Gemfile.lock');
|
|
294
|
+
if ((0, fs_1.existsSync)(gemfileLockPath)) {
|
|
295
|
+
lockfiles.push('Gemfile.lock');
|
|
296
|
+
try {
|
|
297
|
+
const content = (0, fs_1.readFileSync)(gemfileLockPath, 'utf-8');
|
|
298
|
+
const lines = content.split('\n');
|
|
299
|
+
let inSpecs = false;
|
|
300
|
+
for (const line of lines) {
|
|
301
|
+
if (line.trim() === 'specs:') {
|
|
302
|
+
inSpecs = true;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (inSpecs && line.match(/^\s{4}(\S+)\s+\(([^)]+)\)/)) {
|
|
306
|
+
const match = line.match(/^\s{4}(\S+)\s+\(([^)]+)\)/);
|
|
307
|
+
if (match) {
|
|
308
|
+
packages.push({
|
|
309
|
+
name: match[1],
|
|
310
|
+
version: match[2],
|
|
311
|
+
ecosystem: 'RubyGems',
|
|
312
|
+
isDirect: true,
|
|
313
|
+
location: { file: 'Gemfile.lock' }
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (inSpecs && !line.startsWith(' ') && line.trim() !== '') {
|
|
318
|
+
inSpecs = false;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
// Gemfile.lock parsing failed
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return { packages, lockfiles };
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Parse Go dependencies
|
|
330
|
+
*/
|
|
331
|
+
function parseGoDependencies(projectPath) {
|
|
332
|
+
const packages = [];
|
|
333
|
+
const lockfiles = [];
|
|
334
|
+
// Parse go.sum for exact versions
|
|
335
|
+
const goSumPath = (0, path_1.join)(projectPath, 'go.sum');
|
|
336
|
+
if ((0, fs_1.existsSync)(goSumPath)) {
|
|
337
|
+
lockfiles.push('go.sum');
|
|
338
|
+
try {
|
|
339
|
+
const content = (0, fs_1.readFileSync)(goSumPath, 'utf-8');
|
|
340
|
+
const lines = content.split('\n');
|
|
341
|
+
const seen = new Set();
|
|
342
|
+
for (const line of lines) {
|
|
343
|
+
const match = line.match(/^(\S+)\s+v?([^\s/]+)/);
|
|
344
|
+
if (match) {
|
|
345
|
+
const name = match[1];
|
|
346
|
+
const version = match[2].replace('/go.mod', '');
|
|
347
|
+
const key = `${name}@${version}`;
|
|
348
|
+
if (!seen.has(key)) {
|
|
349
|
+
seen.add(key);
|
|
350
|
+
packages.push({
|
|
351
|
+
name,
|
|
352
|
+
version,
|
|
353
|
+
ecosystem: 'Go',
|
|
354
|
+
isDirect: true,
|
|
355
|
+
location: { file: 'go.sum' }
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
// go.sum parsing failed
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return { packages, lockfiles };
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Scan vulnerabilities with OSV integration
|
|
369
|
+
*/
|
|
370
|
+
async function scanVulnerabilitiesOSV(projectPath, options) {
|
|
371
|
+
const startTime = Date.now();
|
|
372
|
+
const ecosystems = options.ecosystem
|
|
373
|
+
? [options.ecosystem]
|
|
374
|
+
: detectEcosystems(projectPath);
|
|
375
|
+
if (ecosystems.length === 0) {
|
|
376
|
+
return {
|
|
377
|
+
projectPath,
|
|
378
|
+
scanType: 'vulnerabilities',
|
|
379
|
+
ecosystem: 'npm',
|
|
380
|
+
packagesScanned: 0,
|
|
381
|
+
findings: [],
|
|
382
|
+
summary: { critical: 0, high: 0, medium: 0, low: 0 },
|
|
383
|
+
directVulnerabilities: 0,
|
|
384
|
+
transitiveVulnerabilities: 0,
|
|
385
|
+
cacheHitRate: 0,
|
|
386
|
+
scanDuration: Date.now() - startTime,
|
|
387
|
+
nvdEnriched: false,
|
|
388
|
+
lockfilesParsed: [],
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
// Parse dependencies from all detected ecosystems
|
|
392
|
+
let allPackages = [];
|
|
393
|
+
let allLockfiles = [];
|
|
394
|
+
for (const ecosystem of ecosystems) {
|
|
395
|
+
let result;
|
|
396
|
+
switch (ecosystem) {
|
|
397
|
+
case 'npm':
|
|
398
|
+
result = parseNpmDependencies(projectPath);
|
|
399
|
+
break;
|
|
400
|
+
case 'PyPI':
|
|
401
|
+
result = parsePythonDependencies(projectPath);
|
|
402
|
+
break;
|
|
403
|
+
case 'RubyGems':
|
|
404
|
+
result = parseRubyDependencies(projectPath);
|
|
405
|
+
break;
|
|
406
|
+
case 'Go':
|
|
407
|
+
result = parseGoDependencies(projectPath);
|
|
408
|
+
break;
|
|
409
|
+
default:
|
|
410
|
+
result = { packages: [], lockfiles: [] };
|
|
411
|
+
}
|
|
412
|
+
allPackages.push(...result.packages);
|
|
413
|
+
allLockfiles.push(...result.lockfiles);
|
|
414
|
+
}
|
|
415
|
+
// Deduplicate packages
|
|
416
|
+
const seen = new Set();
|
|
417
|
+
allPackages = allPackages.filter(pkg => {
|
|
418
|
+
const key = `${pkg.ecosystem}:${pkg.name}:${pkg.version}`;
|
|
419
|
+
if (seen.has(key))
|
|
420
|
+
return false;
|
|
421
|
+
seen.add(key);
|
|
422
|
+
return true;
|
|
423
|
+
});
|
|
424
|
+
// Configure and query OSV
|
|
425
|
+
const dbOptions = {
|
|
426
|
+
noCache: options.noCache,
|
|
427
|
+
nvdEnrichment: options.nvd,
|
|
428
|
+
cacheDir: (0, path_1.join)(projectPath, '.guardrail', 'cache'),
|
|
429
|
+
};
|
|
430
|
+
const db = new vulnerability_db_1.VulnerabilityDatabase(dbOptions);
|
|
431
|
+
const results = await db.checkPackages(allPackages);
|
|
432
|
+
// Attach location info to results
|
|
433
|
+
const resultsWithLocation = results.map((result, idx) => ({
|
|
434
|
+
...result,
|
|
435
|
+
location: allPackages[idx]?.location,
|
|
436
|
+
}));
|
|
437
|
+
// Calculate summary
|
|
438
|
+
const summary = {
|
|
439
|
+
critical: 0,
|
|
440
|
+
high: 0,
|
|
441
|
+
medium: 0,
|
|
442
|
+
low: 0,
|
|
443
|
+
};
|
|
444
|
+
let directVulnerabilities = 0;
|
|
445
|
+
let transitiveVulnerabilities = 0;
|
|
446
|
+
for (const result of resultsWithLocation) {
|
|
447
|
+
if (result.isVulnerable) {
|
|
448
|
+
for (const vuln of result.vulnerabilities) {
|
|
449
|
+
summary[vuln.severity]++;
|
|
450
|
+
}
|
|
451
|
+
if (result.isDirect) {
|
|
452
|
+
directVulnerabilities += result.vulnerabilities.length;
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
transitiveVulnerabilities += result.vulnerabilities.length;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
const cacheStats = db.getCacheStats();
|
|
460
|
+
return {
|
|
461
|
+
projectPath,
|
|
462
|
+
scanType: 'vulnerabilities',
|
|
463
|
+
ecosystem: ecosystems[0],
|
|
464
|
+
packagesScanned: allPackages.length,
|
|
465
|
+
findings: resultsWithLocation.filter(r => r.isVulnerable),
|
|
466
|
+
summary,
|
|
467
|
+
directVulnerabilities,
|
|
468
|
+
transitiveVulnerabilities,
|
|
469
|
+
cacheHitRate: cacheStats.hitRate,
|
|
470
|
+
scanDuration: Date.now() - startTime,
|
|
471
|
+
nvdEnriched: options.nvd || false,
|
|
472
|
+
lockfilesParsed: [...new Set(allLockfiles)],
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Generate SARIF v2.1.0 output
|
|
477
|
+
*/
|
|
478
|
+
function toSarifVulnerabilitiesOSV(results) {
|
|
479
|
+
const version = '1.0.0';
|
|
480
|
+
const ruleMap = new Map();
|
|
481
|
+
// Build rules from unique vulnerability IDs
|
|
482
|
+
for (const finding of results.findings) {
|
|
483
|
+
for (const vuln of finding.vulnerabilities) {
|
|
484
|
+
if (!ruleMap.has(vuln.id)) {
|
|
485
|
+
const cveId = vuln.aliases?.find(a => a.startsWith('CVE-'));
|
|
486
|
+
ruleMap.set(vuln.id, {
|
|
487
|
+
id: vuln.id,
|
|
488
|
+
name: vuln.title.substring(0, 100),
|
|
489
|
+
shortDescription: { text: vuln.title },
|
|
490
|
+
fullDescription: { text: vuln.description || vuln.title },
|
|
491
|
+
helpUri: vuln.references?.[0] || `https://osv.dev/vulnerability/${vuln.id}`,
|
|
492
|
+
help: {
|
|
493
|
+
text: `Vulnerability ${vuln.id} affects this package.\n\n` +
|
|
494
|
+
`Severity: ${vuln.severity.toUpperCase()}\n` +
|
|
495
|
+
(vuln.cvssScore ? `CVSS Score: ${vuln.cvssScore}\n` : '') +
|
|
496
|
+
(cveId ? `CVE: ${cveId}\n` : '') +
|
|
497
|
+
`\nReferences:\n${vuln.references?.map(r => `- ${r}`).join('\n') || 'None'}`,
|
|
498
|
+
markdown: `## ${vuln.title}\n\n` +
|
|
499
|
+
`**Severity:** ${vuln.severity.toUpperCase()}\n\n` +
|
|
500
|
+
(vuln.cvssScore ? `**CVSS Score:** ${vuln.cvssScore}\n\n` : '') +
|
|
501
|
+
(vuln.cvssVector ? `**CVSS Vector:** \`${vuln.cvssVector}\`\n\n` : '') +
|
|
502
|
+
(cveId ? `**CVE:** [${cveId}](https://nvd.nist.gov/vuln/detail/${cveId})\n\n` : '') +
|
|
503
|
+
`### References\n${vuln.references?.map(r => `- [${r}](${r})`).join('\n') || 'None'}`,
|
|
504
|
+
},
|
|
505
|
+
defaultConfiguration: {
|
|
506
|
+
level: vuln.severity === 'critical' || vuln.severity === 'high' ? 'error' :
|
|
507
|
+
vuln.severity === 'medium' ? 'warning' : 'note',
|
|
508
|
+
},
|
|
509
|
+
properties: {
|
|
510
|
+
'security-severity': vuln.cvssScore?.toString() ||
|
|
511
|
+
(vuln.severity === 'critical' ? '9.0' :
|
|
512
|
+
vuln.severity === 'high' ? '7.0' :
|
|
513
|
+
vuln.severity === 'medium' ? '4.0' : '2.0'),
|
|
514
|
+
tags: ['security', 'vulnerability', 'dependency', vuln.source],
|
|
515
|
+
precision: 'high',
|
|
516
|
+
},
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
const sarifResults = [];
|
|
522
|
+
for (const finding of results.findings) {
|
|
523
|
+
for (const vuln of finding.vulnerabilities) {
|
|
524
|
+
const location = finding.location || { file: 'package.json', line: 1 };
|
|
525
|
+
const remediationText = finding.remediationPath
|
|
526
|
+
? `${finding.remediationPath.description}${finding.remediationPath.breakingChange ? ' (Breaking change)' : ''}`
|
|
527
|
+
: `Upgrade to ${finding.recommendedVersion || 'latest'}`;
|
|
528
|
+
sarifResults.push({
|
|
529
|
+
ruleId: vuln.id,
|
|
530
|
+
ruleIndex: Array.from(ruleMap.keys()).indexOf(vuln.id),
|
|
531
|
+
level: vuln.severity === 'critical' || vuln.severity === 'high' ? 'error' :
|
|
532
|
+
vuln.severity === 'medium' ? 'warning' : 'note',
|
|
533
|
+
message: {
|
|
534
|
+
text: `${vuln.title} in ${finding.package}@${finding.version}. ${remediationText}`,
|
|
535
|
+
},
|
|
536
|
+
locations: [{
|
|
537
|
+
physicalLocation: {
|
|
538
|
+
artifactLocation: {
|
|
539
|
+
uri: location.file,
|
|
540
|
+
uriBaseId: '%SRCROOT%',
|
|
541
|
+
},
|
|
542
|
+
region: {
|
|
543
|
+
startLine: location.line || 1,
|
|
544
|
+
startColumn: 1,
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
}],
|
|
548
|
+
fingerprints: {
|
|
549
|
+
'guardrail/v1': `${vuln.id}:${finding.package}:${finding.version}`,
|
|
550
|
+
},
|
|
551
|
+
partialFingerprints: {
|
|
552
|
+
'primaryLocationLineHash': `${finding.package}:${finding.version}:${vuln.id}`,
|
|
553
|
+
},
|
|
554
|
+
properties: {
|
|
555
|
+
package: finding.package,
|
|
556
|
+
version: finding.version,
|
|
557
|
+
ecosystem: results.ecosystem,
|
|
558
|
+
isDirect: finding.isDirect,
|
|
559
|
+
severity: vuln.severity,
|
|
560
|
+
cvssScore: vuln.cvssScore,
|
|
561
|
+
cvssVector: vuln.cvssVector,
|
|
562
|
+
cwe: vuln.cwe,
|
|
563
|
+
aliases: vuln.aliases,
|
|
564
|
+
source: vuln.source,
|
|
565
|
+
affectedVersions: vuln.affectedVersions,
|
|
566
|
+
patchedVersions: vuln.patchedVersions,
|
|
567
|
+
references: vuln.references,
|
|
568
|
+
remediationPath: finding.remediationPath,
|
|
569
|
+
recommendedVersion: finding.recommendedVersion,
|
|
570
|
+
},
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return {
|
|
575
|
+
$schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
|
|
576
|
+
version: '2.1.0',
|
|
577
|
+
runs: [{
|
|
578
|
+
tool: {
|
|
579
|
+
driver: {
|
|
580
|
+
name: 'guardrail-cli',
|
|
581
|
+
version,
|
|
582
|
+
informationUri: 'https://guardrail.dev',
|
|
583
|
+
rules: Array.from(ruleMap.values()),
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
results: sarifResults,
|
|
587
|
+
invocations: [{
|
|
588
|
+
executionSuccessful: true,
|
|
589
|
+
commandLine: `guardrail scan:vulnerabilities --path ${results.projectPath}`,
|
|
590
|
+
startTimeUtc: new Date().toISOString(),
|
|
591
|
+
workingDirectory: { uri: results.projectPath.replace(/\\/g, '/') },
|
|
592
|
+
}],
|
|
593
|
+
}],
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Output OSV vulnerability results
|
|
598
|
+
*/
|
|
599
|
+
function outputOSVVulnResults(results, options) {
|
|
600
|
+
if (options.format === 'json') {
|
|
601
|
+
console.log(JSON.stringify(results, null, 2));
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (options.format === 'sarif') {
|
|
605
|
+
const sarif = toSarifVulnerabilitiesOSV(results);
|
|
606
|
+
console.log(JSON.stringify(sarif, null, 2));
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
console.log(`\n ${c.info('Ecosystem:')} ${results.ecosystem}`);
|
|
610
|
+
console.log(` ${c.info('Packages scanned:')} ${results.packagesScanned}`);
|
|
611
|
+
console.log(` ${c.info('Lockfiles parsed:')} ${results.lockfilesParsed.join(', ') || 'none'}`);
|
|
612
|
+
console.log(` ${c.info('Cache hit rate:')} ${(results.cacheHitRate * 100).toFixed(1)}%`);
|
|
613
|
+
console.log(` ${c.info('NVD enrichment:')} ${results.nvdEnriched ? 'enabled' : 'disabled'}`);
|
|
614
|
+
console.log(` ${c.info('Scan duration:')} ${(results.scanDuration / 1000).toFixed(2)}s\n`);
|
|
615
|
+
const { summary } = results;
|
|
616
|
+
const total = summary.critical + summary.high + summary.medium + summary.low;
|
|
617
|
+
if (total === 0) {
|
|
618
|
+
console.log(` ${c.success('✓')} ${c.bold('No vulnerabilities found!')}\n`);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
console.log(` ${c.critical('CRITICAL')} ${summary.critical}`);
|
|
622
|
+
console.log(` ${c.high('HIGH')} ${summary.high}`);
|
|
623
|
+
console.log(` ${c.medium('MEDIUM')} ${summary.medium}`);
|
|
624
|
+
console.log(` ${c.low('LOW')} ${summary.low}\n`);
|
|
625
|
+
console.log(` ${c.info('Direct:')} ${results.directVulnerabilities} | ${c.info('Transitive:')} ${results.transitiveVulnerabilities}\n`);
|
|
626
|
+
// Group by direct vs transitive
|
|
627
|
+
const directFindings = results.findings.filter(f => f.isDirect);
|
|
628
|
+
const transitiveFindings = results.findings.filter(f => !f.isDirect);
|
|
629
|
+
if (directFindings.length > 0) {
|
|
630
|
+
console.log(`${c.bold(' DIRECT DEPENDENCIES:')}\n`);
|
|
631
|
+
outputFindingsList(directFindings);
|
|
632
|
+
}
|
|
633
|
+
if (transitiveFindings.length > 0) {
|
|
634
|
+
console.log(`\n${c.bold(' TRANSITIVE DEPENDENCIES:')}\n`);
|
|
635
|
+
outputFindingsList(transitiveFindings);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
function outputFindingsList(findings) {
|
|
639
|
+
for (const finding of findings) {
|
|
640
|
+
for (const vuln of finding.vulnerabilities) {
|
|
641
|
+
const severityLabel = vuln.severity === 'critical' ? c.critical('CRITICAL') :
|
|
642
|
+
vuln.severity === 'high' ? c.high('HIGH') :
|
|
643
|
+
vuln.severity === 'medium' ? c.medium('MEDIUM') :
|
|
644
|
+
c.low('LOW');
|
|
645
|
+
console.log(` ${severityLabel} ${finding.package}@${finding.version}`);
|
|
646
|
+
console.log(` ${c.dim('├─')} ${c.info('ID:')} ${vuln.id}`);
|
|
647
|
+
console.log(` ${c.dim('├─')} ${c.info('Summary:')} ${vuln.title}`);
|
|
648
|
+
if (vuln.cvssScore) {
|
|
649
|
+
console.log(` ${c.dim('├─')} ${c.info('CVSS:')} ${vuln.cvssScore.toFixed(1)}${vuln.cvssVector ? ` (${vuln.cvssVector.substring(0, 30)}...)` : ''}`);
|
|
650
|
+
}
|
|
651
|
+
if (finding.remediationPath) {
|
|
652
|
+
const remed = finding.remediationPath;
|
|
653
|
+
const breakingLabel = remed.breakingChange ? c.medium(' [BREAKING]') : c.success(' [NON-BREAKING]');
|
|
654
|
+
console.log(` ${c.dim('└─')} ${c.info('Fix:')} ${remed.description}${breakingLabel}\n`);
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
console.log(` ${c.dim('└─')} ${c.info('Fix:')} Upgrade to ${finding.recommendedVersion || 'latest'}\n`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Register scan:vulnerabilities command with OSV integration
|
|
664
|
+
*/
|
|
665
|
+
function registerScanVulnerabilitiesOSVCommand(program, requireAuth, printLogo) {
|
|
666
|
+
program
|
|
667
|
+
.command('scan:vulnerabilities')
|
|
668
|
+
.description('Scan dependencies for known vulnerabilities using OSV')
|
|
669
|
+
.option('-p, --path <path>', 'Project path to scan', '.')
|
|
670
|
+
.option('-f, --format <format>', 'Output format: table, json, sarif', 'table')
|
|
671
|
+
.option('-o, --output <file>', 'Output file path')
|
|
672
|
+
.option('--no-cache', 'Bypass cache and fetch fresh data from OSV')
|
|
673
|
+
.option('--nvd', 'Enable NVD enrichment for CVSS scores (slower)')
|
|
674
|
+
.option('--fail-on-critical', 'Exit with error if critical vulnerabilities found', false)
|
|
675
|
+
.option('--fail-on-high', 'Exit with error if high+ vulnerabilities found', false)
|
|
676
|
+
.option('--evidence', 'Generate signed evidence pack', false)
|
|
677
|
+
.option('--ecosystem <ecosystem>', 'Filter by ecosystem: npm, PyPI, RubyGems, Go')
|
|
678
|
+
.action(async (opts) => {
|
|
679
|
+
requireAuth();
|
|
680
|
+
printLogo();
|
|
681
|
+
console.log(`\n${c.bold('🛡️ VULNERABILITY SCAN (OSV Integration)')}\n`);
|
|
682
|
+
const projectPath = (0, path_1.resolve)(opts.path);
|
|
683
|
+
if (opts.noCache) {
|
|
684
|
+
console.log(` ${c.dim('Cache:')} disabled (--no-cache)\n`);
|
|
685
|
+
}
|
|
686
|
+
if (opts.nvd) {
|
|
687
|
+
console.log(` ${c.dim('NVD enrichment:')} enabled\n`);
|
|
688
|
+
}
|
|
689
|
+
const results = await scanVulnerabilitiesOSV(projectPath, {
|
|
690
|
+
noCache: opts.noCache,
|
|
691
|
+
nvd: opts.nvd,
|
|
692
|
+
ecosystem: opts.ecosystem,
|
|
693
|
+
});
|
|
694
|
+
console.log(`${c.success('✓')} Vulnerability scan complete`);
|
|
695
|
+
outputOSVVulnResults(results, opts);
|
|
696
|
+
// Write output file if specified
|
|
697
|
+
if (opts.output) {
|
|
698
|
+
const { writeFileSync } = require('fs');
|
|
699
|
+
const output = opts.format === 'sarif'
|
|
700
|
+
? toSarifVulnerabilitiesOSV(results)
|
|
701
|
+
: results;
|
|
702
|
+
writeFileSync(opts.output, JSON.stringify(output, null, 2));
|
|
703
|
+
console.log(`\n ${c.success('✓')} Report written to ${opts.output}`);
|
|
704
|
+
}
|
|
705
|
+
if (opts.evidence) {
|
|
706
|
+
await (0, evidence_1.generateEvidence)('vulnerabilities', results, projectPath);
|
|
707
|
+
}
|
|
708
|
+
if (opts.failOnCritical && results.summary.critical > 0) {
|
|
709
|
+
(0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.POLICY_FAIL, `${results.summary.critical} critical vulnerabilities found`);
|
|
710
|
+
}
|
|
711
|
+
if (opts.failOnHigh && (results.summary.critical + results.summary.high) > 0) {
|
|
712
|
+
(0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.POLICY_FAIL, `${results.summary.critical + results.summary.high} high+ vulnerabilities found`);
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
//# sourceMappingURL=scan-vulnerabilities-osv.js.map
|