vuln-scan 0.1.3 → 0.1.4
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/cli.js +50 -27
- package/package.json +1 -1
- package/src/core.js +16 -3
package/cli.js
CHANGED
|
@@ -1,28 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { setDefaultResultOrder } from 'node:dns';
|
|
4
|
-
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import ora from 'ora';
|
|
7
7
|
import Table from 'cli-table3';
|
|
8
8
|
|
|
9
9
|
import { scanProject } from './src/core.js';
|
|
10
10
|
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
const { version: CLI_VERSION } = require('./package.json');
|
|
13
|
+
|
|
14
|
+
function brandingLine() {
|
|
15
|
+
return `${chalk.bold('vuln-scan')}${chalk.gray(' - ')}${chalk.cyan('Debasis')}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
function printHelp() {
|
|
19
|
+
const valueLine = chalk.gray(`v${CLI_VERSION}`);
|
|
20
|
+
const features = chalk.cyan('Fast | Secure | Protect');
|
|
21
|
+
const tagline = chalk.gray('Scan Dependencies - Stay Safe - Fix Quickly');
|
|
22
|
+
|
|
12
23
|
const text = `
|
|
13
|
-
${
|
|
24
|
+
${features}
|
|
25
|
+
${tagline}
|
|
26
|
+
|
|
27
|
+
${chalk.bold('vuln-scan')} ${valueLine}
|
|
14
28
|
|
|
15
29
|
Scans a project's lockfile (npm/pnpm/yarn) for known vulnerabilities using the OSV.dev API.
|
|
16
30
|
|
|
31
|
+
Made by Debasis (https://github.com/DebaA17)
|
|
32
|
+
|
|
17
33
|
Usage:
|
|
18
34
|
npx vuln-scan
|
|
35
|
+
pnpm dlx vuln-scan
|
|
19
36
|
vuln-scan
|
|
20
37
|
|
|
21
38
|
Options:
|
|
22
39
|
--json Output machine-readable JSON
|
|
23
40
|
--help Show this help
|
|
24
41
|
`;
|
|
25
|
-
// eslint-disable-next-line no-console
|
|
26
42
|
console.log(text.trim());
|
|
27
43
|
}
|
|
28
44
|
|
|
@@ -41,13 +57,25 @@ function severityColor(sev) {
|
|
|
41
57
|
}
|
|
42
58
|
}
|
|
43
59
|
|
|
60
|
+
// Compare two semantic versions (returns true if v1 < v2)
|
|
61
|
+
function isVulnerable(installedVersion, fixedVersion) {
|
|
62
|
+
const installedParts = installedVersion.split('.').map(Number);
|
|
63
|
+
const fixedParts = fixedVersion.split('.').map(Number);
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < Math.max(installedParts.length, fixedParts.length); i++) {
|
|
66
|
+
const installed = installedParts[i] || 0;
|
|
67
|
+
const fixed = fixedParts[i] || 0;
|
|
68
|
+
if (installed < fixed) return true;
|
|
69
|
+
if (installed > fixed) return false;
|
|
70
|
+
}
|
|
71
|
+
return false; // versions are equal → not vulnerable
|
|
72
|
+
}
|
|
73
|
+
|
|
44
74
|
async function main() {
|
|
45
|
-
// Some environments have broken/blocked IPv6 routes. Node's fetch (undici) may try IPv6 first
|
|
46
|
-
// and time out even when IPv4 works (e.g., curl succeeds). Prefer IPv4 DNS results first.
|
|
47
75
|
try {
|
|
48
76
|
setDefaultResultOrder('ipv4first');
|
|
49
77
|
} catch {
|
|
50
|
-
// Older Node versions may not support this
|
|
78
|
+
// Older Node versions may not support this
|
|
51
79
|
}
|
|
52
80
|
|
|
53
81
|
const args = new Set(process.argv.slice(2));
|
|
@@ -58,31 +86,34 @@ async function main() {
|
|
|
58
86
|
|
|
59
87
|
const jsonOutput = args.has('--json');
|
|
60
88
|
|
|
61
|
-
const spinner = ora({ text: 'Scanning dependencies
|
|
89
|
+
const spinner = ora({ text: 'Scanning dependencies...', spinner: 'dots' }).start();
|
|
62
90
|
|
|
63
91
|
try {
|
|
64
92
|
const result = await scanProject({
|
|
65
93
|
cwd: process.cwd(),
|
|
66
94
|
onProgress: ({ processed, total }) => {
|
|
67
|
-
spinner.text = `Scanning dependencies
|
|
95
|
+
spinner.text = `Scanning dependencies... ${processed}/${total}`;
|
|
68
96
|
}
|
|
69
97
|
});
|
|
70
98
|
|
|
71
99
|
spinner.stop();
|
|
72
100
|
|
|
101
|
+
// Filter vulnerabilities: only show if installed version < fixed version
|
|
102
|
+
const activeVulns = result.vulnerabilities.filter(v => {
|
|
103
|
+
if (!v.fixed) return true; // no fixed info, assume still vulnerable
|
|
104
|
+
return isVulnerable(v.version, v.fixed);
|
|
105
|
+
});
|
|
106
|
+
|
|
73
107
|
if (jsonOutput) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
console.log(JSON.stringify(result, null, 2));
|
|
77
|
-
// eslint-disable-next-line no-console
|
|
108
|
+
console.log(JSON.stringify({ ...result, vulnerabilities: activeVulns }, null, 2));
|
|
109
|
+
console.error(brandingLine());
|
|
78
110
|
console.error(chalk.green('Scan complete!'));
|
|
79
111
|
return;
|
|
80
112
|
}
|
|
81
113
|
|
|
82
|
-
if (
|
|
83
|
-
// eslint-disable-next-line no-console
|
|
114
|
+
if (activeVulns.length === 0) {
|
|
84
115
|
console.log(chalk.green(`No known vulnerabilities found in ${result.dependencyCount} dependencies.`));
|
|
85
|
-
|
|
116
|
+
console.log(brandingLine());
|
|
86
117
|
console.log(chalk.green('Scan complete!'));
|
|
87
118
|
return;
|
|
88
119
|
}
|
|
@@ -90,33 +121,25 @@ async function main() {
|
|
|
90
121
|
const table = new Table({
|
|
91
122
|
head: ['Package', 'CVE ID', 'Severity', 'Summary'],
|
|
92
123
|
wordWrap: true,
|
|
93
|
-
colWidths: [
|
|
94
|
-
28,
|
|
95
|
-
18,
|
|
96
|
-
10,
|
|
97
|
-
70
|
|
98
|
-
]
|
|
124
|
+
colWidths: [28, 18, 10, 70]
|
|
99
125
|
});
|
|
100
126
|
|
|
101
|
-
for (const v of
|
|
127
|
+
for (const v of activeVulns) {
|
|
102
128
|
const pkg = `${v.package}@${v.version}`;
|
|
103
129
|
const cve = v.cve || v.id;
|
|
104
130
|
const summary = v.summary + (v.fixed ? ` (Fix: ${v.fixed})` : '');
|
|
105
|
-
|
|
106
131
|
table.push([pkg, cve, severityColor(v.severity), summary]);
|
|
107
132
|
}
|
|
108
133
|
|
|
109
|
-
// eslint-disable-next-line no-console
|
|
110
134
|
console.log(table.toString());
|
|
111
|
-
|
|
135
|
+
console.log(brandingLine());
|
|
112
136
|
console.log(chalk.green('Scan complete!'));
|
|
113
137
|
} catch (err) {
|
|
114
138
|
spinner.stop();
|
|
115
139
|
const message = err instanceof Error ? err.message : String(err);
|
|
116
|
-
// eslint-disable-next-line no-console
|
|
117
140
|
console.error(chalk.red(`Error: ${message}`));
|
|
118
141
|
process.exitCode = 1;
|
|
119
142
|
}
|
|
120
143
|
}
|
|
121
144
|
|
|
122
|
-
await main();
|
|
145
|
+
await main();
|
package/package.json
CHANGED
package/src/core.js
CHANGED
|
@@ -18,11 +18,24 @@ const OSV_QUERY_URL = 'https://api.osv.dev/v1/query';
|
|
|
18
18
|
export async function scanProject({ cwd, onProgress }) {
|
|
19
19
|
const projectPackageJsonPath = path.join(cwd, 'package.json');
|
|
20
20
|
const hasPackageJson = await exists(projectPackageJsonPath);
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
|
|
22
|
+
let detected;
|
|
23
|
+
try {
|
|
24
|
+
detected = await detectPackageManager(cwd);
|
|
25
|
+
} catch {
|
|
26
|
+
if (hasPackageJson) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
'No supported lockfile found (package-lock.json, pnpm-lock.yaml, yarn.lock). ' +
|
|
29
|
+
'Run your package manager install command to generate a lockfile, then re-run vuln-scan.'
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
throw new Error(
|
|
33
|
+
'Nothing to scan: no package.json or supported lockfile found (package-lock.json, pnpm-lock.yaml, yarn.lock). '
|
|
34
|
+
+ 'Run vuln-scan from a project directory.'
|
|
35
|
+
);
|
|
23
36
|
}
|
|
24
37
|
|
|
25
|
-
const { packageManager, lockfilePath } =
|
|
38
|
+
const { packageManager, lockfilePath } = detected;
|
|
26
39
|
|
|
27
40
|
const dependencies = await readDependenciesFromLockfile({
|
|
28
41
|
packageManager,
|