@deepv-code/safe-npm 0.1.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/LICENSE +21 -0
- package/README.md +120 -0
- package/README.zh-CN.md +121 -0
- package/dist/cli/args-parser.d.ts +11 -0
- package/dist/cli/args-parser.js +36 -0
- package/dist/cli/check.d.ts +5 -0
- package/dist/cli/check.js +126 -0
- package/dist/cli/proxy.d.ts +1 -0
- package/dist/cli/proxy.js +4 -0
- package/dist/data/popular-packages.d.ts +9 -0
- package/dist/data/popular-packages.js +83 -0
- package/dist/i18n/en.d.ts +43 -0
- package/dist/i18n/en.js +50 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/index.js +11 -0
- package/dist/i18n/zh.d.ts +2 -0
- package/dist/i18n/zh.js +50 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +77 -0
- package/dist/scanner/code-analyzer.d.ts +2 -0
- package/dist/scanner/code-analyzer.js +130 -0
- package/dist/scanner/index.d.ts +3 -0
- package/dist/scanner/index.js +163 -0
- package/dist/scanner/patterns/exfiltration.d.ts +7 -0
- package/dist/scanner/patterns/exfiltration.js +49 -0
- package/dist/scanner/patterns/miner.d.ts +5 -0
- package/dist/scanner/patterns/miner.js +32 -0
- package/dist/scanner/patterns/obfuscation.d.ts +15 -0
- package/dist/scanner/patterns/obfuscation.js +110 -0
- package/dist/scanner/types.d.ts +26 -0
- package/dist/scanner/types.js +1 -0
- package/dist/scanner/typosquatting.d.ts +3 -0
- package/dist/scanner/typosquatting.js +126 -0
- package/dist/scanner/virustotal.d.ts +7 -0
- package/dist/scanner/virustotal.js +249 -0
- package/dist/scanner/vulnerability.d.ts +2 -0
- package/dist/scanner/vulnerability.js +42 -0
- package/dist/tui/App.d.ts +2 -0
- package/dist/tui/App.js +67 -0
- package/dist/tui/index.d.ts +1 -0
- package/dist/tui/index.js +6 -0
- package/dist/tui/screens/CheckScreen.d.ts +7 -0
- package/dist/tui/screens/CheckScreen.js +92 -0
- package/dist/tui/screens/PopularScreen.d.ts +7 -0
- package/dist/tui/screens/PopularScreen.js +39 -0
- package/dist/tui/screens/SettingsScreen.d.ts +6 -0
- package/dist/tui/screens/SettingsScreen.js +64 -0
- package/dist/utils/config.d.ts +16 -0
- package/dist/utils/config.js +69 -0
- package/dist/utils/npm-package.d.ts +38 -0
- package/dist/utils/npm-package.js +191 -0
- package/dist/utils/npm-runner.d.ts +7 -0
- package/dist/utils/npm-runner.js +56 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepV Code
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# safe-npm 🛡️
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@deepv-code/safe-npm)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
|
|
7
|
+
**[中文文档](./README.zh-CN.md) | English Docs**
|
|
8
|
+
|
|
9
|
+
> **Guard your supply chain.** Detect malicious packages, typosquatting attacks, and known vulnerabilities *before* they touch your disk.
|
|
10
|
+
|
|
11
|
+
**safe-npm** is a security-focused wrapper for the NPM CLI. It intercepts installation commands, scans target packages using multiple engines (VirusTotal, Static Analysis, etc.), and provides interactive feedback. If a package is suspicious or a typo is detected, it blocks execution and suggests the correct official package.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## ✨ Key Features
|
|
16
|
+
|
|
17
|
+
* **🔍 Multi-Engine Scanning**
|
|
18
|
+
* **VirusTotal Integration**: Checks package hashes against 70+ security vendors. Automatically uploads unknown files for analysis.
|
|
19
|
+
* **Static Code Analysis**: Detects crypto-miners, data exfiltration scripts, and suspicious obfuscation.
|
|
20
|
+
* **Vulnerability Check**: Cross-references against known CVE databases.
|
|
21
|
+
* **🚫 Typosquatting Protection**
|
|
22
|
+
* Detects package names that mimic popular libraries (e.g., `reacct` vs `react`).
|
|
23
|
+
* **Scope Hijacking Defense**: Prevents installing fake unscoped packages that mimic official scoped ones (e.g., detects `claude-code` attempting to impersonate `@anthropic-ai/claude-code`).
|
|
24
|
+
* **💡 Smart Suggestions**
|
|
25
|
+
* Automatically identifies the package you *intended* to install.
|
|
26
|
+
* "Did you mean...?" interactive prompt to one-click install the correct package.
|
|
27
|
+
* **🖥️ Interactive TUI**
|
|
28
|
+
* A beautiful Terminal User Interface for exploring checks and managing settings.
|
|
29
|
+
* Real-time scanning progress and detailed reports.
|
|
30
|
+
* **⚡ Drop-in Replacement**
|
|
31
|
+
* Works just like `npm`. Use `safe-npm install` instead of `npm install`.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 🚀 Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install -g @deepv-code/safe-npm
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 📖 Usage
|
|
44
|
+
|
|
45
|
+
### 1. Safe Installation (Recommended)
|
|
46
|
+
Use `safe-npm` as a replacement for `npm` when installing packages.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# This triggers a full security scan before installation
|
|
50
|
+
safe-npm install react
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Scenario: Typosquatting Interception**
|
|
54
|
+
```bash
|
|
55
|
+
$ safe-npm install qwen
|
|
56
|
+
⚠ Scanning...
|
|
57
|
+
✗ qwen: Package not found / Suspicious
|
|
58
|
+
💡 Suggestion: Did you mean to install the official package "@qwen-code/qwen-code"?
|
|
59
|
+
Do you want to install "@qwen-code/qwen-code" instead? (Y/n)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 2. Check a Package
|
|
63
|
+
Scan a package without installing it.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
safe-npm check <package-name>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Interactive Mode (TUI)
|
|
70
|
+
Launch the dashboard to check popular agents, configure settings, or manually scan packages.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
safe-npm tui
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## ⚙️ Configuration
|
|
79
|
+
|
|
80
|
+
On the first run, `safe-npm` will ask for your preferred language (English/Chinese).
|
|
81
|
+
|
|
82
|
+
Configuration is stored in `~/.safe-npm/config.json`. You can modify it manually or via the TUI (`safe-npm tui` -> Settings).
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"language": "en",
|
|
87
|
+
"virustotal": {
|
|
88
|
+
"enabled": true,
|
|
89
|
+
"apiKey": "YOUR_OWN_API_KEY" // Optional: Use your own key for higher rate limits
|
|
90
|
+
},
|
|
91
|
+
"offline": false
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 🛡️ Security Checks Explained
|
|
98
|
+
|
|
99
|
+
| Check Type | Description |
|
|
100
|
+
|------------|-------------|
|
|
101
|
+
| **Typosquatting** | Uses Levenshtein distance and pattern matching against a curated list of top 150+ popular packages (React, Vue, NestJS, AI Agents, etc.) to detect impostors. |
|
|
102
|
+
| **VirusTotal** | queries the file hash. If the file is new (404), it **automatically uploads** the tarball to VirusTotal for a fresh scan. |
|
|
103
|
+
| **Code Patterns** | Scans for hardcoded IPs, suspicious domains, `eval()` abuse, and known crypto-mining signatures. |
|
|
104
|
+
| **Registry** | Verifies package existence and metadata consistency. |
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 🤝 Contributing
|
|
109
|
+
|
|
110
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
111
|
+
|
|
112
|
+
1. Fork the Project
|
|
113
|
+
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
|
|
114
|
+
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
|
|
115
|
+
4. Push to the Branch (`git push origin feature/AmazingFeature`)
|
|
116
|
+
5. Open a Pull Request
|
|
117
|
+
|
|
118
|
+
## 📄 License
|
|
119
|
+
|
|
120
|
+
Distributed under the MIT License. See `LICENSE` for more information.
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# safe-npm 🛡️
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@deepv-code/safe-npm)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
|
|
7
|
+
**[English Docs](./README.md) | 中文文档**
|
|
8
|
+
|
|
9
|
+
> **守护您的软件供应链安全。** 在恶意代码触达您的硬盘之前,精准拦截恶意包、误拼包及已知漏洞。
|
|
10
|
+
|
|
11
|
+
**safe-npm** 是一个专注于安全性的 NPM 命令行包装器。它拦截安装命令,使用多种引擎(VirusTotal、静态分析、拼写检查等)对目标包进行全面扫描,并提供交互式反馈。如果检测到恶意包或拼写错误,它会阻止执行并为您推荐正确的官方包。
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## ✨ 核心功能
|
|
16
|
+
|
|
17
|
+
* **🔍 多引擎扫描**
|
|
18
|
+
* **集成 VirusTotal**: 将包的 Hash 与全球 70+ 家安全厂商的数据库进行比对。对于未收录的新文件,支持**自动上传**进行云端查杀。
|
|
19
|
+
* **静态代码分析**: 内置规则引擎,自动检测加密货币挖矿脚本、数据窃取代码及可疑混淆逻辑。
|
|
20
|
+
* **漏洞检测**: 实时核对 CVE 漏洞数据库。
|
|
21
|
+
* **🚫 防误拼与防劫持 (Typosquatting)**
|
|
22
|
+
* **误拼检测**: 基于 Levenshtein 距离和模式匹配,识别模仿热门库(如 `reacct` vs `react`)的恶意包。
|
|
23
|
+
* **Scope 劫持防御**: 针对近期高发的 Scope 攻击进行专项防护(例如:拦截试图冒充 `@anthropic-ai/claude-code` 的 `claude-code` 伪造包)。
|
|
24
|
+
* **💡 智能纠错与建议**
|
|
25
|
+
* 自动识别您原本*想要*安装的包。
|
|
26
|
+
* 提供 "Did you mean...?" 交互式提示,一键安装正确的官方正版包。
|
|
27
|
+
* **🖥️ 交互式终端 (TUI)**
|
|
28
|
+
* 提供精美的终端用户界面,可视化查看扫描进度、检测详情及管理设置。
|
|
29
|
+
* 内置热门 AI Agent 工具推荐列表。
|
|
30
|
+
* **⚡ 无缝替换**
|
|
31
|
+
* 用法与 `npm` 完全一致。只需用 `safe-npm install` 代替 `npm install` 即可。
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 🚀 安装
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install -g @deepv-code/safe-npm
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 📖 使用方法
|
|
44
|
+
|
|
45
|
+
### 1. 安全安装(推荐)
|
|
46
|
+
在日常开发中,使用 `safe-npm` 替代 `npm` 来安装依赖。
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# 此命令会在安装前触发完整的安全扫描
|
|
50
|
+
safe-npm install react
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**场景演示:拦截恶意抢注包**
|
|
54
|
+
```bash
|
|
55
|
+
$ safe-npm install qwen
|
|
56
|
+
⚠ Scanning...
|
|
57
|
+
✗ qwen: Package not found / Suspicious (未找到包或疑似恶意)
|
|
58
|
+
💡 Suggestion: Did you mean to install the official package "@qwen-code/qwen-code"?
|
|
59
|
+
(建议:您是否想安装官方包 "@qwen-code/qwen-code"?)
|
|
60
|
+
Do you want to install "@qwen-code/qwen-code" instead? (Y/n)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 2. 仅扫描
|
|
64
|
+
在不安装的情况下,检查某个包的安全性。
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
safe-npm check <package-name>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 3. 交互模式 (TUI)
|
|
71
|
+
启动图形化仪表盘,查看热门工具、配置设置或手动扫描。
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
safe-npm tui
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## ⚙️ 配置说明
|
|
80
|
+
|
|
81
|
+
首次运行时,`safe-npm` 会询问您的语言偏好(中文/英文)。
|
|
82
|
+
|
|
83
|
+
配置文件位于 `~/.safe-npm/config.json`。您可以通过 TUI (`safe-npm tui` -> Settings) 或手动修改配置。
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"language": "zh",
|
|
88
|
+
"virustotal": {
|
|
89
|
+
"enabled": true,
|
|
90
|
+
"apiKey": "YOUR_OWN_API_KEY" // 可选:配置您自己的 Key 以获得更高的 API 速率限制
|
|
91
|
+
},
|
|
92
|
+
"offline": false // 设置为 true 可开启离线模式(仅本地检查)
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 🛡️ 安全检测机制详情
|
|
99
|
+
|
|
100
|
+
| 检测类型 | 说明 |
|
|
101
|
+
|------------|-------------|
|
|
102
|
+
| **近似名检测 (Typosquatting)** | 内置 150+ 个热门包(含 React, Vue, NestJS, 及各类 AI Agent)的正版名单。通过算法检测名称相似度,严防“李鬼”包。 |
|
|
103
|
+
| **VirusTotal 查杀** | 查询文件 Hash。如果该文件是全网首次出现(404),工具会**自动提取并上传**该包到 VirusTotal 服务器进行新鲜查杀。 |
|
|
104
|
+
| **代码模式分析** | 扫描硬编码 IP、可疑域名、`eval()` 滥用、挖矿脚本特征等潜在威胁。 |
|
|
105
|
+
| **注册表核验** | 验证包是否存在,以及元数据是否合规。 |
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 🤝 贡献指南
|
|
110
|
+
|
|
111
|
+
欢迎提交 Pull Request 来改进这个项目!
|
|
112
|
+
|
|
113
|
+
1. Fork 本项目
|
|
114
|
+
2. 创建您的特性分支 (`git checkout -b feature/AmazingFeature`)
|
|
115
|
+
3. 提交您的修改 (`git commit -m 'Add some AmazingFeature'`)
|
|
116
|
+
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
|
117
|
+
5. 提交 Pull Request
|
|
118
|
+
|
|
119
|
+
## 📄 许可证
|
|
120
|
+
|
|
121
|
+
本项目采用 MIT 许可证。详情请参阅 `LICENSE` 文件。
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ParsedArgs {
|
|
2
|
+
command: string;
|
|
3
|
+
packages: string[];
|
|
4
|
+
flags: string[];
|
|
5
|
+
isInstall: boolean;
|
|
6
|
+
isForce: boolean;
|
|
7
|
+
isGlobal: boolean;
|
|
8
|
+
isTui: boolean;
|
|
9
|
+
isCheck: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function parseArgs(args: string[]): ParsedArgs;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const INSTALL_COMMANDS = ['install', 'i', 'add', 'isntall'];
|
|
2
|
+
export function parseArgs(args) {
|
|
3
|
+
const command = args[0] || '';
|
|
4
|
+
const restArgs = args.slice(1);
|
|
5
|
+
const flags = [];
|
|
6
|
+
const packages = [];
|
|
7
|
+
let isForce = false;
|
|
8
|
+
let isGlobal = false;
|
|
9
|
+
for (const arg of restArgs) {
|
|
10
|
+
if (arg.startsWith('-')) {
|
|
11
|
+
flags.push(arg);
|
|
12
|
+
if (arg === '--force' || arg === '-f') {
|
|
13
|
+
isForce = true;
|
|
14
|
+
}
|
|
15
|
+
if (arg === '-g' || arg === '--global') {
|
|
16
|
+
isGlobal = true;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
packages.push(arg);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const isInstall = INSTALL_COMMANDS.includes(command.toLowerCase());
|
|
24
|
+
const isTui = command === 'tui';
|
|
25
|
+
const isCheck = command === 'check';
|
|
26
|
+
return {
|
|
27
|
+
command,
|
|
28
|
+
packages,
|
|
29
|
+
flags,
|
|
30
|
+
isInstall,
|
|
31
|
+
isForce,
|
|
32
|
+
isGlobal,
|
|
33
|
+
isTui,
|
|
34
|
+
isCheck,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import * as readline from 'readline';
|
|
4
|
+
import { t } from '../i18n/index.js';
|
|
5
|
+
import { scanPackage } from '../scanner/index.js';
|
|
6
|
+
import { runNpm } from '../utils/npm-runner.js';
|
|
7
|
+
async function promptInstallCorrect(correctPackage) {
|
|
8
|
+
return new Promise((resolve) => {
|
|
9
|
+
const rl = readline.createInterface({
|
|
10
|
+
input: process.stdin,
|
|
11
|
+
output: process.stdout,
|
|
12
|
+
});
|
|
13
|
+
console.log(chalk.cyan(`\n${t('suggestionTitle')} "${chalk.bold(correctPackage)}"?`));
|
|
14
|
+
rl.question(`${t('suggestionPrompt')} "${correctPackage}"? (Y/n) `, (answer) => {
|
|
15
|
+
rl.close();
|
|
16
|
+
if (answer.toLowerCase() === 'n') {
|
|
17
|
+
resolve(false);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
resolve(true);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function formatResult(result) {
|
|
26
|
+
const { packageName, riskLevel, issues, canBypass, checks } = result;
|
|
27
|
+
const levelColors = {
|
|
28
|
+
fatal: chalk.bgRed.white,
|
|
29
|
+
high: chalk.red,
|
|
30
|
+
warning: chalk.yellow,
|
|
31
|
+
safe: chalk.green,
|
|
32
|
+
};
|
|
33
|
+
const riskLabels = {
|
|
34
|
+
fatal: t('riskFatal'),
|
|
35
|
+
high: t('riskHigh'),
|
|
36
|
+
warning: t('riskWarning'),
|
|
37
|
+
safe: t('riskSafe'),
|
|
38
|
+
};
|
|
39
|
+
const levelColor = levelColors[riskLevel];
|
|
40
|
+
if (riskLevel === 'safe') {
|
|
41
|
+
console.log(chalk.green(`✓ ${packageName}: ${riskLabels[riskLevel]}`));
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.log('');
|
|
45
|
+
// Special handling for "Not Found" case to be friendlier
|
|
46
|
+
if (checks.some(c => c.name === 'Registry Check' && c.status === 'fail')) {
|
|
47
|
+
console.log(chalk.red(`✗ ${packageName}: ${t('packageNotFound')}`));
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
console.log(levelColor(`⚠ ${packageName}: ${riskLabels[riskLevel]}`));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Print Details
|
|
54
|
+
if (checks && checks.length > 0) {
|
|
55
|
+
console.log(chalk.dim('\n Scan Details:'));
|
|
56
|
+
for (const check of checks) {
|
|
57
|
+
const icon = check.status === 'pass' ? chalk.green('✓') :
|
|
58
|
+
check.status === 'fail' ? chalk.red('✗') :
|
|
59
|
+
chalk.gray('○');
|
|
60
|
+
const desc = check.description ? chalk.dim(`- ${check.description}`) : '';
|
|
61
|
+
console.log(` ${icon} ${check.name} ${desc}`);
|
|
62
|
+
}
|
|
63
|
+
console.log('');
|
|
64
|
+
}
|
|
65
|
+
if (issues.length > 0) {
|
|
66
|
+
for (const issue of issues) {
|
|
67
|
+
console.log(` [!] ${issue.message}`);
|
|
68
|
+
if (issue.details) {
|
|
69
|
+
console.log(chalk.dim(` ${issue.details}`));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
console.log('');
|
|
73
|
+
}
|
|
74
|
+
if (riskLevel === 'safe')
|
|
75
|
+
return;
|
|
76
|
+
if (!canBypass) {
|
|
77
|
+
console.log(chalk.red(`✗ ${t('blocked')} - ${t('cannotBypass')}`));
|
|
78
|
+
}
|
|
79
|
+
else if (riskLevel === 'high') {
|
|
80
|
+
console.log(chalk.red(`✗ ${t('blocked')}`));
|
|
81
|
+
console.log(chalk.dim(` ${t('useForce')}`));
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
console.log(chalk.yellow(`⚠ ${t('continuing')}`));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export async function checkPackages(packages, options = {}) {
|
|
88
|
+
if (packages.length === 0) {
|
|
89
|
+
console.log('No packages specified to check');
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
const spinner = ora(`${t('scanning')}...`).start();
|
|
93
|
+
let allSafe = true;
|
|
94
|
+
const results = [];
|
|
95
|
+
for (const pkg of packages) {
|
|
96
|
+
spinner.text = `${t('scanning')}: ${pkg}`;
|
|
97
|
+
const result = await scanPackage(pkg, {}, (msg, completed, total) => {
|
|
98
|
+
spinner.text = `${t('scanning')} ${pkg}: ${msg} (${completed}/${total})`;
|
|
99
|
+
});
|
|
100
|
+
results.push(result);
|
|
101
|
+
if (result.riskLevel === 'fatal') {
|
|
102
|
+
allSafe = false;
|
|
103
|
+
}
|
|
104
|
+
else if (result.riskLevel === 'high' && !options.isForce) {
|
|
105
|
+
allSafe = false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
spinner.stop();
|
|
109
|
+
console.log(chalk.bold(`\n${t('scanComplete')}\n`));
|
|
110
|
+
for (const result of results) {
|
|
111
|
+
formatResult(result);
|
|
112
|
+
// Auto-fix suggestion for install mode
|
|
113
|
+
if (options.install && result.riskLevel === 'fatal' && result.suggestedPackage) {
|
|
114
|
+
const shouldInstall = await promptInstallCorrect(result.suggestedPackage);
|
|
115
|
+
if (shouldInstall) {
|
|
116
|
+
console.log(chalk.green(`\n${t('installingCorrect')}: ${result.suggestedPackage}...`));
|
|
117
|
+
await runNpm(['install', result.suggestedPackage]);
|
|
118
|
+
// Return false to stop the original (malicious) installation logic
|
|
119
|
+
// But we essentially succeeded in the user's intent.
|
|
120
|
+
// However, to keep flow clean, we exit the process here or return false to block original.
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return allSafe;
|
|
126
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function proxyToNpm(args: string[]): Promise<number>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Popular packages that are commonly typosquatted
|
|
2
|
+
// Source: Top NPM packages by downloads
|
|
3
|
+
export const popularPackages = [
|
|
4
|
+
// --- Frameworks & Libraries ---
|
|
5
|
+
'react', 'react-dom', 'vue', 'angular', 'svelte', 'preact', 'solid-js', 'qwik',
|
|
6
|
+
'next', 'nuxt', 'gatsby', 'remix', 'astro',
|
|
7
|
+
'express', 'fastify', 'koa', 'nestjs', 'meteor', 'sails',
|
|
8
|
+
'jquery', 'backbone', 'ember-cli', 'bootstrap', 'tailwindcss', 'bulma',
|
|
9
|
+
'three', 'pixi.js', 'd3', 'chart.js', 'recharts',
|
|
10
|
+
'socket.io', 'socket.io-client', 'ws',
|
|
11
|
+
// --- State Management ---
|
|
12
|
+
'redux', 'react-redux', '@reduxjs/toolkit', 'mobx', 'mobx-react', 'vuex', 'pinia', 'zustand', 'jotai', 'recoil', 'xstate',
|
|
13
|
+
// --- Utilities & Helpers ---
|
|
14
|
+
'lodash', 'underscore', 'ramda', 'immutable', 'rxjs',
|
|
15
|
+
'moment', 'dayjs', 'date-fns', 'luxon',
|
|
16
|
+
'uuid', 'nanoid', 'shortid',
|
|
17
|
+
'chalk', 'colors', 'kleur', 'ansi-styles',
|
|
18
|
+
'classnames', 'clsx',
|
|
19
|
+
'qs', 'query-string',
|
|
20
|
+
'semver', 'minimist', 'yargs', 'commander', 'inquirer', 'prompts', 'ora',
|
|
21
|
+
'debug', 'ms', 'dotenv', 'cross-env', 'rimraf', 'mkdirp', 'fs-extra',
|
|
22
|
+
'glob', 'minimatch', 'chokidar',
|
|
23
|
+
// --- Network & Requests ---
|
|
24
|
+
'axios', 'node-fetch', 'got', 'request', 'superagent', 'undici', 'ky', 'swr', 'react-query', '@tanstack/react-query',
|
|
25
|
+
// --- Build Tools & Compilers ---
|
|
26
|
+
'typescript', 'ts-node', 'tslib',
|
|
27
|
+
'webpack', 'webpack-cli', 'webpack-dev-server',
|
|
28
|
+
'vite', 'rollup', 'esbuild', 'parcel', 'turbopack',
|
|
29
|
+
'babel-core', '@babel/core', '@babel/preset-env', '@babel/preset-react',
|
|
30
|
+
'swc', 'postcss', 'autoprefixer', 'sass', 'less', 'stylus',
|
|
31
|
+
// --- Testing ---
|
|
32
|
+
'jest', 'mocha', 'chai', 'jasmine', 'karma',
|
|
33
|
+
'vitest', 'cypress', 'playwright', 'puppeteer', 'selenium-webdriver',
|
|
34
|
+
'testing-library', '@testing-library/react',
|
|
35
|
+
'supertest',
|
|
36
|
+
// --- Linting & Formatting ---
|
|
37
|
+
'eslint', 'prettier', 'stylelint', 'husky', 'lint-staged', 'commitizen',
|
|
38
|
+
// --- Node.js Core & Server ---
|
|
39
|
+
'cookie-parser', 'body-parser', 'cors', 'morgan', 'helmet', 'compression',
|
|
40
|
+
'multer', 'jsonwebtoken', 'passport', 'bcrypt', 'bcryptjs',
|
|
41
|
+
'mongoose', 'sequelize', 'typeorm', 'prisma', '@prisma/client',
|
|
42
|
+
'pg', 'mysql2', 'mysql', 'sqlite3', 'redis', 'mongodb',
|
|
43
|
+
// --- Cloud & DevOps ---
|
|
44
|
+
'aws-sdk', '@aws-sdk/client-s3', 'firebase', 'firebase-admin', '@google-cloud/storage',
|
|
45
|
+
'serverless', 'pm2', 'nodemon', 'forever',
|
|
46
|
+
// --- CI/CD & Others ---
|
|
47
|
+
'npm-run-all', 'concurrently',
|
|
48
|
+
'shelljs', 'execa',
|
|
49
|
+
'xml2js', 'js-yaml',
|
|
50
|
+
'winston', 'bunyan', 'pino',
|
|
51
|
+
'sharp', 'jimp',
|
|
52
|
+
// --- Scoped Packages Protection ---
|
|
53
|
+
'@anthropic-ai/claude-code',
|
|
54
|
+
'@qwen-code/qwen-code',
|
|
55
|
+
'@openai/codex',
|
|
56
|
+
// --- Recent Hot Packages ---
|
|
57
|
+
'clawdbot',
|
|
58
|
+
];
|
|
59
|
+
export const popularGlobalTools = [
|
|
60
|
+
{ name: 'typescript', desc: 'TypeScript compiler' },
|
|
61
|
+
{ name: 'eslint', desc: 'JavaScript linter' },
|
|
62
|
+
{ name: 'prettier', desc: 'Code formatter' },
|
|
63
|
+
{ name: 'npm-check-updates', desc: 'Update dependencies' },
|
|
64
|
+
{ name: 'nodemon', desc: 'Auto-restart Node.js' },
|
|
65
|
+
{ name: 'tsx', desc: 'TypeScript execute' },
|
|
66
|
+
{ name: 'pnpm', desc: 'Fast package manager' },
|
|
67
|
+
{ name: 'yarn', desc: 'Alternative package manager' },
|
|
68
|
+
{ name: 'vercel', desc: 'Vercel CLI' },
|
|
69
|
+
{ name: 'netlify-cli', desc: 'Netlify CLI' },
|
|
70
|
+
{ name: 'create-react-app', desc: 'React project generator' },
|
|
71
|
+
{ name: 'create-next-app', desc: 'Next.js project generator' },
|
|
72
|
+
{ name: 'vue-cli', desc: 'Vue CLI' },
|
|
73
|
+
{ name: '@vue/cli', desc: 'Vue CLI' },
|
|
74
|
+
{ name: 'nest', desc: 'NestJS CLI' },
|
|
75
|
+
{ name: '@nestjs/cli', desc: 'NestJS CLI' },
|
|
76
|
+
];
|
|
77
|
+
export const popularAgents = [
|
|
78
|
+
{ name: 'deepv-code', desc: 'DeepV Code AI Assistant' },
|
|
79
|
+
{ name: '@qwen-code/qwen-code', desc: 'Qwen Code Official' },
|
|
80
|
+
{ name: '@anthropic-ai/claude-code', desc: 'Claude Code Official' },
|
|
81
|
+
{ name: '@openai/codex', desc: 'OpenAI Codex Official' },
|
|
82
|
+
{ name: 'clawdbot', desc: 'WhatsApp Gateway Agent' },
|
|
83
|
+
];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export declare const en: {
|
|
2
|
+
appName: string;
|
|
3
|
+
version: string;
|
|
4
|
+
riskFatal: string;
|
|
5
|
+
riskHigh: string;
|
|
6
|
+
riskWarning: string;
|
|
7
|
+
riskSafe: string;
|
|
8
|
+
virusDetected: string;
|
|
9
|
+
typosquatDetected: string;
|
|
10
|
+
suspiciousCode: string;
|
|
11
|
+
cveFound: string;
|
|
12
|
+
minerDetected: string;
|
|
13
|
+
obfuscatedCode: string;
|
|
14
|
+
dangerousScript: string;
|
|
15
|
+
blacklisted: string;
|
|
16
|
+
blocked: string;
|
|
17
|
+
useForce: string;
|
|
18
|
+
cannotBypass: string;
|
|
19
|
+
continuing: string;
|
|
20
|
+
scanning: string;
|
|
21
|
+
scanComplete: string;
|
|
22
|
+
downloadingPackage: string;
|
|
23
|
+
analyzingCode: string;
|
|
24
|
+
checkingVirustotal: string;
|
|
25
|
+
checkingBlacklist: string;
|
|
26
|
+
networkError: string;
|
|
27
|
+
offlineMode: string;
|
|
28
|
+
vtNoApiKey: string;
|
|
29
|
+
tuiWelcome: string;
|
|
30
|
+
tuiPopular: string;
|
|
31
|
+
tuiCheck: string;
|
|
32
|
+
tuiSettings: string;
|
|
33
|
+
tuiQuit: string;
|
|
34
|
+
tuiLanguage: string;
|
|
35
|
+
suggestionTitle: string;
|
|
36
|
+
suggestionPrompt: string;
|
|
37
|
+
installingCorrect: string;
|
|
38
|
+
packageNotFound: string;
|
|
39
|
+
didYouMean: string;
|
|
40
|
+
checkName: string;
|
|
41
|
+
registryCheckFail: string;
|
|
42
|
+
};
|
|
43
|
+
export type I18nKey = keyof typeof en;
|
package/dist/i18n/en.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export const en = {
|
|
2
|
+
// General
|
|
3
|
+
appName: 'safe-npm',
|
|
4
|
+
version: 'Version',
|
|
5
|
+
// Risk levels
|
|
6
|
+
riskFatal: 'FATAL',
|
|
7
|
+
riskHigh: 'HIGH RISK',
|
|
8
|
+
riskWarning: 'WARNING',
|
|
9
|
+
riskSafe: 'SAFE',
|
|
10
|
+
// Detection messages
|
|
11
|
+
virusDetected: 'Known malware detected',
|
|
12
|
+
typosquatDetected: 'Typosquatting detected - this may be a fake package',
|
|
13
|
+
suspiciousCode: 'Suspicious code pattern detected',
|
|
14
|
+
cveFound: 'Known vulnerability found',
|
|
15
|
+
minerDetected: 'Crypto-miner pattern detected',
|
|
16
|
+
obfuscatedCode: 'Obfuscated code detected - may hide malicious behavior',
|
|
17
|
+
dangerousScript: 'Dangerous install script detected',
|
|
18
|
+
blacklisted: 'Package is in known malicious packages list',
|
|
19
|
+
// Actions
|
|
20
|
+
blocked: 'Installation blocked',
|
|
21
|
+
useForce: 'Use --force to install anyway if you are sure it is safe',
|
|
22
|
+
cannotBypass: 'This threat cannot be bypassed',
|
|
23
|
+
continuing: 'Continuing with installation...',
|
|
24
|
+
// Scanner
|
|
25
|
+
scanning: 'Scanning package',
|
|
26
|
+
scanComplete: 'Scan complete',
|
|
27
|
+
downloadingPackage: 'Downloading package for analysis',
|
|
28
|
+
analyzingCode: 'Analyzing code patterns',
|
|
29
|
+
checkingVirustotal: 'Checking VirusTotal database',
|
|
30
|
+
checkingBlacklist: 'Checking against known malicious packages',
|
|
31
|
+
// Errors
|
|
32
|
+
networkError: 'Network error - falling back to local cache',
|
|
33
|
+
offlineMode: 'Running in offline mode',
|
|
34
|
+
vtNoApiKey: 'VirusTotal API key not configured - skipping online scan',
|
|
35
|
+
// TUI
|
|
36
|
+
tuiWelcome: 'Welcome to safe-npm',
|
|
37
|
+
tuiPopular: 'Popular Packages',
|
|
38
|
+
tuiCheck: 'Check Package',
|
|
39
|
+
tuiSettings: 'Settings',
|
|
40
|
+
tuiQuit: 'Quit',
|
|
41
|
+
tuiLanguage: 'Language',
|
|
42
|
+
// Suggestions
|
|
43
|
+
suggestionTitle: '💡 Suggestion: Did you mean to install the official package',
|
|
44
|
+
suggestionPrompt: 'Do you want to install',
|
|
45
|
+
installingCorrect: '🚀 Installing correct package',
|
|
46
|
+
packageNotFound: 'Package not found in registry',
|
|
47
|
+
didYouMean: 'Did you mean',
|
|
48
|
+
checkName: 'Please check the package name',
|
|
49
|
+
registryCheckFail: 'Package does not exist',
|
|
50
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { en } from './en.js';
|
|
2
|
+
import { zh } from './zh.js';
|
|
3
|
+
import { getConfig } from '../utils/config.js';
|
|
4
|
+
const translations = { en, zh };
|
|
5
|
+
export function t(key) {
|
|
6
|
+
const lang = getConfig().language;
|
|
7
|
+
return translations[lang]?.[key] ?? translations.en[key] ?? key;
|
|
8
|
+
}
|
|
9
|
+
export function setLanguage(lang) {
|
|
10
|
+
// Will be handled via config
|
|
11
|
+
}
|