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.
Files changed (3) hide show
  1. package/cli.js +50 -27
  2. package/package.json +1 -1
  3. 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
- ${chalk.bold('vuln-scan')}
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; best-effort.
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', spinner: 'dots' }).start();
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 ${processed}/${total}`;
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
- // Keep stdout clean for piping/automation.
75
- // eslint-disable-next-line no-console
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 (result.vulnerabilities.length === 0) {
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
- // eslint-disable-next-line no-console
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 result.vulnerabilities) {
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
- // eslint-disable-next-line no-console
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vuln-scan",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Node.js CLI to scan dependency lockfiles for vulnerabilities using OSV.dev",
5
5
  "type": "module",
6
6
  "bin": {
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
- if (!hasPackageJson) {
22
- throw new Error('Missing package.json in the current directory.');
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 } = await detectPackageManager(cwd);
38
+ const { packageManager, lockfilePath } = detected;
26
39
 
27
40
  const dependencies = await readDependenciesFromLockfile({
28
41
  packageManager,