npmguard-cli 0.1.0 → 0.2.1

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.
@@ -2,26 +2,36 @@ import chalk from "chalk";
2
2
  import Table from "cli-table3";
3
3
  import ora from "ora";
4
4
  import { scanProject } from "../scanner.js";
5
+ const IPFS_GATEWAY = "https://gateway.pinata.cloud/ipfs";
5
6
  function verdictLabel(verdict, score) {
6
7
  switch (verdict) {
7
8
  case "SAFE":
8
- return chalk.green(`✅ SAFE (${score})`);
9
+ return chalk.green(`SAFE (${score})`);
9
10
  case "WARNING":
10
- return chalk.yellow(`⚠️ WARNING (${score})`);
11
+ return chalk.yellow(`WARNING (${score})`);
11
12
  case "CRITICAL":
12
- return chalk.red(`❌ CRITICAL (${score})`);
13
+ return chalk.red(`CRITICAL (${score})`);
13
14
  default:
14
- return chalk.gray("UNKNOWN");
15
+ return chalk.gray("UNKNOWN");
15
16
  }
16
17
  }
17
- function reportLink(audit) {
18
- if (audit.reportCid) {
19
- return chalk.gray(` 📄 Report: https://gateway.pinata.cloud/ipfs/${audit.reportCid}`);
20
- }
21
- return "";
18
+ function capsLabel(caps) {
19
+ if (caps.length === 0)
20
+ return chalk.gray("none");
21
+ return caps
22
+ .map((c) => {
23
+ if (["process_spawn", "binary_download", "eval_usage", "obfuscated_code"].includes(c)) {
24
+ return chalk.red(c);
25
+ }
26
+ if (["filesystem"].includes(c)) {
27
+ return chalk.yellow(c);
28
+ }
29
+ return chalk.gray(c);
30
+ })
31
+ .join(", ");
22
32
  }
23
- function npmLink(name, version) {
24
- return chalk.gray(` 📦 https://www.npmjs.com/package/${name}/v/${version}`);
33
+ function shortCid(cid) {
34
+ return cid.slice(0, 8) + "..." + cid.slice(-4);
25
35
  }
26
36
  export async function checkCommand(projectPath, auditSource) {
27
37
  const spinner = ora("Scanning project dependencies...").start();
@@ -32,7 +42,8 @@ export async function checkCommand(projectPath, auditSource) {
32
42
  chalk.bold("Package"),
33
43
  chalk.bold("Installed"),
34
44
  chalk.bold("Latest"),
35
- chalk.bold("NpmGuard Verdict"),
45
+ chalk.bold("Verdict"),
46
+ chalk.bold("Capabilities"),
36
47
  ],
37
48
  style: { head: [], border: [] },
38
49
  });
@@ -40,60 +51,47 @@ export async function checkCommand(projectPath, auditSource) {
40
51
  let warningCount = 0;
41
52
  let criticalCount = 0;
42
53
  let notAuditedCount = 0;
43
- const details = [];
54
+ const links = [];
44
55
  for (const dep of deps) {
45
56
  const versionToCheck = dep.latest ?? dep.installed;
46
- const audit = await auditSource.getAudit(dep.name, versionToCheck);
47
- let verdictCol;
57
+ let audit;
48
58
  if (!dep.hasUpdate) {
49
- const currentAudit = await auditSource.getAudit(dep.name, dep.installed);
50
- if (currentAudit) {
51
- verdictCol = verdictLabel(currentAudit.verdict, currentAudit.score);
52
- if (currentAudit.verdict === "SAFE")
53
- safeCount++;
54
- else if (currentAudit.verdict === "WARNING") {
55
- warningCount++;
56
- details.push(reportLink(currentAudit));
57
- }
58
- else if (currentAudit.verdict === "CRITICAL") {
59
- criticalCount++;
60
- details.push(reportLink(currentAudit));
61
- }
62
- }
63
- else {
64
- verdictCol = chalk.gray("❓ NOT AUDITED");
65
- notAuditedCount++;
66
- details.push(npmLink(dep.name, dep.installed));
67
- }
59
+ audit = await auditSource.getAudit(dep.name, dep.installed);
60
+ }
61
+ else {
62
+ audit = await auditSource.getAudit(dep.name, versionToCheck);
68
63
  }
69
- else if (audit) {
64
+ let verdictCol;
65
+ let capsCol;
66
+ if (audit) {
70
67
  verdictCol = verdictLabel(audit.verdict, audit.score);
68
+ capsCol = capsLabel(audit.capabilities);
71
69
  if (audit.verdict === "SAFE")
72
70
  safeCount++;
73
- else if (audit.verdict === "WARNING") {
71
+ else if (audit.verdict === "WARNING")
74
72
  warningCount++;
75
- details.push(reportLink(audit));
76
- }
77
- else if (audit.verdict === "CRITICAL") {
73
+ else if (audit.verdict === "CRITICAL")
78
74
  criticalCount++;
79
- details.push(reportLink(audit));
75
+ if (audit.reportCid) {
76
+ links.push(` ${dep.name}@${versionToCheck} report: ${IPFS_GATEWAY}/${audit.reportCid}`);
80
77
  }
81
78
  }
82
79
  else {
83
- verdictCol = chalk.gray("NOT AUDITED");
80
+ verdictCol = chalk.gray("NOT AUDITED");
81
+ capsCol = chalk.gray("-");
84
82
  notAuditedCount++;
85
- details.push(npmLink(dep.name, versionToCheck));
86
83
  }
87
84
  table.push([
88
85
  dep.name,
89
86
  dep.installed,
90
87
  dep.latest ?? dep.installed,
91
88
  verdictCol,
89
+ capsCol,
92
90
  ]);
93
91
  }
94
92
  spinner.stop();
95
93
  console.log();
96
- console.log(chalk.bold("📦 NpmGuard Dependency Audit"));
94
+ console.log(chalk.bold("NpmGuard Dependency Audit"));
97
95
  console.log();
98
96
  console.log(table.toString());
99
97
  console.log();
@@ -107,18 +105,16 @@ export async function checkCommand(projectPath, auditSource) {
107
105
  parts.push(chalk.red(`${criticalCount} critical`));
108
106
  if (notAuditedCount > 0)
109
107
  parts.push(chalk.gray(`${notAuditedCount} not audited`));
110
- console.log(` ${parts.join(" · ")}`);
111
- // Show detail links
112
- if (details.length > 0) {
108
+ console.log(` ${parts.join(" | ")}`);
109
+ if (links.length > 0) {
113
110
  console.log();
114
- for (const d of details) {
115
- if (d)
116
- console.log(d);
111
+ for (const link of links) {
112
+ console.log(chalk.gray(link));
117
113
  }
118
114
  }
119
115
  if (criticalCount > 0) {
120
116
  console.log();
121
- console.log(chalk.red.bold(` ${criticalCount} package(s) flagged as CRITICAL — do not update without reviewing the report.`));
117
+ console.log(chalk.red.bold(` ${criticalCount} package(s) flagged as CRITICAL — do not update without reviewing the report.`));
122
118
  }
123
119
  console.log();
124
120
  }
@@ -1,2 +1,2 @@
1
1
  import type { AuditSource } from "../audit-source.js";
2
- export declare function installCommand(packageSpec: string, auditSource: AuditSource): Promise<void>;
2
+ export declare function installCommand(packageSpec: string, auditSource: AuditSource, force?: boolean): Promise<void>;
@@ -1,8 +1,8 @@
1
1
  import chalk from "chalk";
2
2
  import ora from "ora";
3
3
  import { execSync } from "node:child_process";
4
- export async function installCommand(packageSpec, auditSource) {
5
- // Parse package@version or just package
4
+ const IPFS_GATEWAY = "https://gateway.pinata.cloud/ipfs";
5
+ export async function installCommand(packageSpec, auditSource, force = false) {
6
6
  const atIndex = packageSpec.lastIndexOf("@");
7
7
  let packageName;
8
8
  let requestedVersion = null;
@@ -14,7 +14,6 @@ export async function installCommand(packageSpec, auditSource) {
14
14
  packageName = packageSpec;
15
15
  }
16
16
  const spinner = ora(`Checking audit for ${packageName}...`).start();
17
- // Get latest version from npm if not specified
18
17
  if (!requestedVersion) {
19
18
  try {
20
19
  const resp = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
@@ -24,7 +23,7 @@ export async function installCommand(packageSpec, auditSource) {
24
23
  }
25
24
  }
26
25
  catch {
27
- // continue without version
26
+ // continue
28
27
  }
29
28
  }
30
29
  if (!requestedVersion) {
@@ -33,46 +32,58 @@ export async function installCommand(packageSpec, auditSource) {
33
32
  }
34
33
  const audit = await auditSource.getAudit(packageName, requestedVersion);
35
34
  spinner.stop();
35
+ console.log();
36
+ console.log(chalk.bold(` ${packageName}@${requestedVersion}`));
37
+ console.log();
36
38
  if (!audit) {
39
+ console.log(chalk.gray(` NOT AUDITED — no NpmGuard record found for this version.`));
40
+ console.log(chalk.gray(` https://www.npmjs.com/package/${packageName}/v/${requestedVersion}`));
37
41
  console.log();
38
- console.log(chalk.gray(` ❓ ${packageName}@${requestedVersion} has not been audited by NpmGuard.`));
39
42
  console.log(chalk.gray(" Proceeding with standard npm install..."));
40
43
  console.log();
41
44
  execSync(`npm install ${packageSpec}`, { stdio: "inherit" });
42
45
  return;
43
46
  }
44
- console.log();
45
- console.log(chalk.bold(` 📦 ${packageName}@${requestedVersion}`));
47
+ // Show verdict
48
+ if (audit.verdict === "SAFE") {
49
+ console.log(chalk.green(` SAFE (score: ${audit.score})`));
50
+ }
51
+ else if (audit.verdict === "WARNING") {
52
+ console.log(chalk.yellow(` WARNING (score: ${audit.score})`));
53
+ }
54
+ else if (audit.verdict === "CRITICAL") {
55
+ console.log(chalk.red(` CRITICAL (score: ${audit.score})`));
56
+ }
57
+ // Show capabilities
58
+ if (audit.capabilities.length > 0) {
59
+ console.log(chalk.gray(` Capabilities: ${audit.capabilities.join(", ")}`));
60
+ }
61
+ // Show IPFS links
62
+ if (audit.reportCid) {
63
+ console.log(chalk.gray(` Report: ${IPFS_GATEWAY}/${audit.reportCid}`));
64
+ }
65
+ if (audit.sourceCid) {
66
+ console.log(chalk.gray(` Source: ${IPFS_GATEWAY}/${audit.sourceCid}`));
67
+ }
46
68
  console.log();
47
69
  if (audit.verdict === "SAFE") {
48
- console.log(chalk.green(` ✅ SAFE (score: ${audit.score})`));
49
- if (audit.capabilities.length > 0) {
50
- console.log(chalk.gray(` Capabilities: ${audit.capabilities.join(", ")}`));
51
- }
52
- console.log();
53
70
  execSync(`npm install ${packageSpec}`, { stdio: "inherit" });
54
71
  }
55
72
  else if (audit.verdict === "WARNING") {
56
- console.log(chalk.yellow(` ⚠️ WARNING (score: ${audit.score})`));
57
- console.log(chalk.yellow(` Capabilities: ${audit.capabilities.join(", ")}`));
58
- if (audit.reportCid) {
59
- console.log(chalk.gray(` Report: https://gateway.pinata.cloud/ipfs/${audit.reportCid}`));
60
- }
61
- console.log();
62
73
  console.log(chalk.yellow(" Installing with warning..."));
63
74
  console.log();
64
75
  execSync(`npm install ${packageSpec}`, { stdio: "inherit" });
65
76
  }
66
77
  else if (audit.verdict === "CRITICAL") {
67
- console.log(chalk.red(` ❌ CRITICAL (score: ${audit.score})`));
68
- console.log(chalk.red(` Capabilities: ${audit.capabilities.join(", ")}`));
69
- if (audit.reportCid) {
70
- console.log(chalk.gray(` Report: https://gateway.pinata.cloud/ipfs/${audit.reportCid}`));
78
+ if (force) {
79
+ console.log(chalk.red(" --force: Installing despite CRITICAL verdict..."));
80
+ console.log();
81
+ execSync(`npm install ${packageSpec}`, { stdio: "inherit" });
82
+ }
83
+ else {
84
+ console.log(chalk.red.bold(" Installation blocked. This package has critical security issues."));
85
+ console.log(chalk.gray(" Use --force to install anyway."));
86
+ console.log();
71
87
  }
72
- console.log();
73
- console.log(chalk.red.bold(" ⛔ Installation blocked. This package has critical security issues."));
74
- console.log(chalk.gray(" Use --force to install anyway: npmguard install --force " +
75
- packageSpec));
76
- console.log();
77
88
  }
78
89
  }
package/dist/index.js CHANGED
@@ -31,6 +31,6 @@ program
31
31
  const auditSource = opts.mock
32
32
  ? new MockAuditSource()
33
33
  : new ENSAuditSource();
34
- await installCommand(pkg, auditSource);
34
+ await installCommand(pkg, auditSource, opts.force ?? false);
35
35
  });
36
36
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "npmguard-cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
+ "type": "module",
4
5
  "description": "Check npm packages against NpmGuard security audits on ENS before installing",
5
6
  "bin": {
6
7
  "npmguard-cli": "./dist/index.js"