install-guard 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/README.md +158 -0
- package/bin/cli.js +35 -0
- package/install-guard-1.0.0.tgz +0 -0
- package/package.json +22 -0
- package/src/analyze.js +34 -0
- package/src/index.js +5 -0
- package/src/install.js +44 -0
- package/src/npm.js +27 -0
- package/src/scan.js +15 -0
- package/src/score.js +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# 🚨 Should You Trust That npm Package Before Installing?
|
|
2
|
+
|
|
3
|
+
**dep-shield** analyzes npm packages and tells you if they are safe to install — before you install them.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<details>
|
|
8
|
+
<summary>Example</summary>
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx dep-shield install some-random-lib
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
📦 Analyzing some-random-lib...
|
|
16
|
+
|
|
17
|
+
Downloads (weekly): 120
|
|
18
|
+
Risk Score: 8/10 🚨 High Risk
|
|
19
|
+
|
|
20
|
+
⚠ Very low downloads
|
|
21
|
+
⚠ Uses install scripts
|
|
22
|
+
⚠ Recently published
|
|
23
|
+
|
|
24
|
+
❓ Do you still want to install this package? (y/n)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
</details>
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 😬 The Problem
|
|
32
|
+
|
|
33
|
+
Installing npm packages blindly is risky.
|
|
34
|
+
|
|
35
|
+
- Malicious packages are published daily
|
|
36
|
+
- Popular packages get compromised
|
|
37
|
+
- `npm audit` only checks known vulnerabilities — not trust
|
|
38
|
+
|
|
39
|
+
You shouldn't have to guess if a package is safe.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 🛡️ The Solution
|
|
44
|
+
|
|
45
|
+
dep-shield gives you a **risk score before you install anything**.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## ⚡ Quick Start
|
|
50
|
+
|
|
51
|
+
Check a package:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx dep-shield axios
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Safely install a package:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx dep-shield install axios
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Scan your project:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx dep-shield scan
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 🧠 How Risk Score Works
|
|
72
|
+
|
|
73
|
+
dep-shield analyzes:
|
|
74
|
+
|
|
75
|
+
- 📉 Weekly downloads (popularity)
|
|
76
|
+
- 🕒 Last update time
|
|
77
|
+
- ⚠ Install/postinstall scripts
|
|
78
|
+
- 📦 Version activity
|
|
79
|
+
|
|
80
|
+
Each factor contributes to a **risk score (0–10)**.
|
|
81
|
+
|
|
82
|
+
Lower score = safer package.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 📊 Example Results
|
|
87
|
+
|
|
88
|
+
### ✅ Safe package
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
axios
|
|
92
|
+
Downloads: 20,000,000+
|
|
93
|
+
Risk: 1/10
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### 🚨 Risky package
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
some-lib
|
|
102
|
+
Downloads: 200
|
|
103
|
+
Risk: 8/10
|
|
104
|
+
|
|
105
|
+
⚠ Low downloads
|
|
106
|
+
⚠ Uses install scripts
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## ✨ Features
|
|
112
|
+
|
|
113
|
+
- 🔍 Analyze any npm package instantly
|
|
114
|
+
- ⚠ Risk scoring (0–10)
|
|
115
|
+
- 🛑 Block risky installs
|
|
116
|
+
- 📦 Project-wide dependency scan
|
|
117
|
+
- ⚡ Zero setup (works with npx)
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 🤔 Why not npm audit?
|
|
122
|
+
|
|
123
|
+
| Feature | npm audit | dep-shield |
|
|
124
|
+
|-----------------------|-----------|------------|
|
|
125
|
+
| Known vulnerabilities | ✅ | ✅ |
|
|
126
|
+
| Trust analysis | ❌ | ✅ |
|
|
127
|
+
| Pre-install check | ❌ | ✅ |
|
|
128
|
+
| Install blocking | ❌ | ✅ |
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 📦 Install globally
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npm install -g dep-shield
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 🔮 Roadmap
|
|
141
|
+
|
|
142
|
+
- 🔍 GitHub activity analysis
|
|
143
|
+
- 🧠 Typosquatting detection
|
|
144
|
+
- 📊 Dependency tree visualization
|
|
145
|
+
- 🔌 CI/CD integration
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 🤝 Contributing
|
|
150
|
+
|
|
151
|
+
PRs welcome! Let's make npm safer together.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## ⭐ Support
|
|
156
|
+
|
|
157
|
+
If you find this useful, consider giving it a star ⭐
|
|
158
|
+
It helps others discover the project!
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { analyzePackage } from "../src/index.js";
|
|
5
|
+
import { scanProject } from "../src/scan.js";
|
|
6
|
+
|
|
7
|
+
const program = new Command();
|
|
8
|
+
|
|
9
|
+
program
|
|
10
|
+
.name("dep-shield")
|
|
11
|
+
.description("Check npm package risk before installing");
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.argument("[package]", "package name to analyze")
|
|
15
|
+
.action(async (pkg) => {
|
|
16
|
+
if (!pkg) {
|
|
17
|
+
console.log("Please provide a package name");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
await analyzePackage(pkg);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
program
|
|
24
|
+
.command("scan")
|
|
25
|
+
.description("Scan current project dependencies")
|
|
26
|
+
.action(scanProject);
|
|
27
|
+
|
|
28
|
+
program
|
|
29
|
+
.command("install <pkg>")
|
|
30
|
+
.description("Analyze and install package safely")
|
|
31
|
+
.action(async (pkg) => {
|
|
32
|
+
const { analyzeAndPrompt } = await import("../src/install.js");
|
|
33
|
+
await analyzeAndPrompt(pkg);
|
|
34
|
+
});
|
|
35
|
+
program.parse();
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "install-guard",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"bin": {
|
|
6
|
+
"npm-guard": "./bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
|
+
},
|
|
13
|
+
"author": "Sarthak Kumar Sahoo",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"description": "Check if an npm package is safe before installing",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"chalk": "^5.6.2",
|
|
18
|
+
"commander": "^14.0.3",
|
|
19
|
+
"ora": "^9.3.0"
|
|
20
|
+
},
|
|
21
|
+
"keywords": ["npm", "security", "cli", "dependency", "audit"]
|
|
22
|
+
}
|
package/src/analyze.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import { getPackageData } from "./npm.js";
|
|
4
|
+
import { calculateRisk } from "./score.js";
|
|
5
|
+
|
|
6
|
+
export async function analyze(pkgName) {
|
|
7
|
+
const spinner = ora(`Analyzing ${pkgName}...`).start();
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const data = await getPackageData(pkgName);
|
|
11
|
+
const { score, warnings } = calculateRisk(data);
|
|
12
|
+
|
|
13
|
+
spinner.stop();
|
|
14
|
+
|
|
15
|
+
console.log(chalk.bold(`\n📦 ${data.name}`));
|
|
16
|
+
console.log(`Version: ${data.version}`);
|
|
17
|
+
console.log(`Downloads (weekly): ${data.downloads.toLocaleString()}`);
|
|
18
|
+
console.log(`Risk Score: ${score}/10`);
|
|
19
|
+
|
|
20
|
+
if (score <= 3) {
|
|
21
|
+
console.log(chalk.green("✅ Low risk"));
|
|
22
|
+
} else if (score <= 6) {
|
|
23
|
+
console.log(chalk.yellow("⚠ Medium risk"));
|
|
24
|
+
} else {
|
|
25
|
+
console.log(chalk.red("🚨 High risk"));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
warnings.forEach((w) => {
|
|
29
|
+
console.log(chalk.yellow(`⚠ ${w}`));
|
|
30
|
+
});
|
|
31
|
+
} catch (err) {
|
|
32
|
+
spinner.fail("Failed to fetch package");
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/index.js
ADDED
package/src/install.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import readline from "readline";
|
|
3
|
+
import { getPackageData } from "./npm.js";
|
|
4
|
+
import { calculateRisk } from "./score.js";
|
|
5
|
+
|
|
6
|
+
function askQuestion(query) {
|
|
7
|
+
const rl = readline.createInterface({
|
|
8
|
+
input: process.stdin,
|
|
9
|
+
output: process.stdout,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
return new Promise((resolve) =>
|
|
13
|
+
rl.question(query, (ans) => {
|
|
14
|
+
rl.close();
|
|
15
|
+
resolve(ans);
|
|
16
|
+
})
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function analyzeAndPrompt(pkgName) {
|
|
21
|
+
console.log(`\nAnalyzing ${pkgName}...\n`);
|
|
22
|
+
|
|
23
|
+
const data = await getPackageData(pkgName);
|
|
24
|
+
const { score, warnings } = calculateRisk(data);
|
|
25
|
+
|
|
26
|
+
console.log(`Risk Score: ${score}/10`);
|
|
27
|
+
|
|
28
|
+
warnings.forEach((w) => console.log(`⚠ ${w}`));
|
|
29
|
+
|
|
30
|
+
if (score >= 6) {
|
|
31
|
+
const ans = await askQuestion(
|
|
32
|
+
"\nHigh risk package. Continue install? (y/n): "
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (ans.toLowerCase() !== "y") {
|
|
36
|
+
console.log("Installation aborted.");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log("\nInstalling...\n");
|
|
42
|
+
|
|
43
|
+
execSync(`npm install ${pkgName}`, { stdio: "inherit" });
|
|
44
|
+
}
|
package/src/npm.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
|
|
3
|
+
export async function getPackageData(pkg) {
|
|
4
|
+
const registryUrl = `https://registry.npmjs.org/${pkg}`;
|
|
5
|
+
const downloadUrl = `https://api.npmjs.org/downloads/point/last-week/${pkg}`;
|
|
6
|
+
|
|
7
|
+
const [registryRes, downloadRes] = await Promise.all([
|
|
8
|
+
axios.get(registryUrl),
|
|
9
|
+
axios.get(downloadUrl),
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
const data = registryRes.data;
|
|
13
|
+
const latest = data["dist-tags"].latest;
|
|
14
|
+
const versionData = data.versions[latest];
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
name: data.name,
|
|
18
|
+
maintainers: data.maintainers?.length || 0,
|
|
19
|
+
lastPublished: data.time?.[latest],
|
|
20
|
+
hasInstallScript:
|
|
21
|
+
versionData.scripts?.install ||
|
|
22
|
+
versionData.scripts?.postinstall ||
|
|
23
|
+
false,
|
|
24
|
+
version: latest,
|
|
25
|
+
downloads: downloadRes.data.downloads || 0,
|
|
26
|
+
};
|
|
27
|
+
}
|
package/src/scan.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { analyzePackage } from "./index.js";
|
|
3
|
+
|
|
4
|
+
export async function scanProject() {
|
|
5
|
+
const pkg = JSON.parse(fs.readFileSync("package.json"));
|
|
6
|
+
|
|
7
|
+
const deps = {
|
|
8
|
+
...pkg.dependencies,
|
|
9
|
+
...pkg.devDependencies,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
for (const dep of Object.keys(deps)) {
|
|
13
|
+
await analyzePackage(dep);
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/score.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export function calculateRisk(pkg) {
|
|
2
|
+
let score = 0;
|
|
3
|
+
const warnings = [];
|
|
4
|
+
|
|
5
|
+
// 🟢 Popularity (VERY IMPORTANT)
|
|
6
|
+
if (pkg.downloads < 1000) {
|
|
7
|
+
score += 3;
|
|
8
|
+
warnings.push("Very low downloads");
|
|
9
|
+
} else if (pkg.downloads < 10000) {
|
|
10
|
+
score += 2;
|
|
11
|
+
warnings.push("Low downloads");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// 🟡 Update recency
|
|
15
|
+
const lastUpdate = new Date(pkg.lastPublished);
|
|
16
|
+
const now = new Date();
|
|
17
|
+
const diffMonths = (now - lastUpdate) / (1000 * 60 * 60 * 24 * 30);
|
|
18
|
+
|
|
19
|
+
if (diffMonths > 12) {
|
|
20
|
+
score += 3;
|
|
21
|
+
warnings.push("Not updated in over a year");
|
|
22
|
+
} else if (diffMonths > 6) {
|
|
23
|
+
score += 1;
|
|
24
|
+
warnings.push("Not updated recently");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 🔴 Install scripts (HIGH RISK)
|
|
28
|
+
if (pkg.hasInstallScript) {
|
|
29
|
+
score += 3;
|
|
30
|
+
warnings.push("Uses install/postinstall scripts");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 🟢 High trust signal
|
|
34
|
+
if (pkg.downloads > 1000000) {
|
|
35
|
+
score -= 2; // reduce risk
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (pkg.downloads > 5000000) {
|
|
39
|
+
score -= 2;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Normalize
|
|
43
|
+
if (score < 0) score = 0;
|
|
44
|
+
if (score > 10) score = 10;
|
|
45
|
+
|
|
46
|
+
return { score, warnings };
|
|
47
|
+
}
|