agentaudit 3.7.1 → 3.9.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 +333 -157
- package/cli.mjs +326 -61
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,26 +2,93 @@
|
|
|
2
2
|
|
|
3
3
|
# 🛡️ AgentAudit
|
|
4
4
|
|
|
5
|
-
**Security scanner for AI packages**
|
|
5
|
+
**Security scanner for AI packages — MCP server + CLI**
|
|
6
6
|
|
|
7
|
-
Scan MCP servers,
|
|
8
|
-
|
|
7
|
+
Scan MCP servers, AI skills, and packages for vulnerabilities, prompt injection,
|
|
8
|
+
and supply chain attacks. Powered by regex static analysis and deep LLM audits.
|
|
9
9
|
|
|
10
|
-
[](https://www.npmjs.com/package/agentaudit)
|
|
11
|
+
[](https://agentaudit.dev)
|
|
12
|
+
[](LICENSE)
|
|
13
13
|
|
|
14
14
|
</div>
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## 📑 Table of Contents
|
|
19
|
+
|
|
20
|
+
- [What is AgentAudit?](#what-is-agentaudit)
|
|
21
|
+
- [Quick Start](#-quick-start)
|
|
22
|
+
- [Commands Reference](#-commands-reference)
|
|
23
|
+
- [Quick Scan vs Deep Audit](#-quick-scan-vs-deep-audit)
|
|
24
|
+
- [MCP Server](#-mcp-server)
|
|
25
|
+
- [What It Detects](#-what-it-detects)
|
|
26
|
+
- [How the 3-Pass Audit Works](#-how-the-3-pass-audit-works)
|
|
27
|
+
- [CI/CD Integration](#-cicd-integration)
|
|
28
|
+
- [Configuration](#-configuration)
|
|
29
|
+
- [Requirements](#-requirements)
|
|
30
|
+
- [FAQ](#-faq)
|
|
31
|
+
- [Related Links](#-related-links)
|
|
32
|
+
- [License](#-license)
|
|
19
33
|
|
|
20
|
-
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## What is AgentAudit?
|
|
37
|
+
|
|
38
|
+
AgentAudit is a security scanner purpose-built for the AI package ecosystem. It works in two modes:
|
|
39
|
+
|
|
40
|
+
1. **CLI tool** — Run `agentaudit` in your terminal to discover and scan MCP servers installed in your AI editors
|
|
41
|
+
2. **MCP server** — Add to Claude Desktop, Cursor, or Windsurf so your AI agent can audit packages on your behalf
|
|
42
|
+
|
|
43
|
+
It checks packages against the [AgentAudit Trust Registry](https://agentaudit.dev) — a shared, community-driven database of security findings — and can perform local scans ranging from fast regex analysis to deep LLM-powered 3-pass audits.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 🚀 Quick Start
|
|
48
|
+
|
|
49
|
+
### Option A: CLI (recommended)
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Install globally
|
|
53
|
+
npm install -g agentaudit
|
|
54
|
+
|
|
55
|
+
# Discover MCP servers in your AI editors
|
|
56
|
+
agentaudit
|
|
57
|
+
|
|
58
|
+
# Quick scan a specific repo
|
|
59
|
+
agentaudit scan https://github.com/owner/repo
|
|
60
|
+
|
|
61
|
+
# Deep LLM-powered audit
|
|
62
|
+
agentaudit audit https://github.com/owner/repo
|
|
63
|
+
|
|
64
|
+
# Look up a package in the trust registry
|
|
65
|
+
agentaudit lookup fastmcp
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Example output:**
|
|
69
|
+
```
|
|
70
|
+
AgentAudit v3.9.0
|
|
71
|
+
Security scanner for AI packages
|
|
72
|
+
|
|
73
|
+
Discovering MCP servers in your AI editors...
|
|
21
74
|
|
|
22
|
-
|
|
75
|
+
• Scanning Cursor ~/.cursor/mcp.json found 3 servers
|
|
23
76
|
|
|
24
|
-
|
|
77
|
+
├── tool supabase-mcp ✔ ok
|
|
78
|
+
│ SAFE Risk 0 https://agentaudit.dev/skills/supabase-mcp
|
|
79
|
+
├── tool browser-tools-mcp ✔ ok
|
|
80
|
+
│ ⚠ not audited Run: agentaudit audit https://github.com/nichochar/browser-tools-mcp
|
|
81
|
+
└── tool filesystem ✔ ok
|
|
82
|
+
│ SAFE Risk 0 https://agentaudit.dev/skills/filesystem
|
|
83
|
+
|
|
84
|
+
Looking for general package scanning? Try `pip audit` or `npm audit`.
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Option B: MCP Server in your editor
|
|
88
|
+
|
|
89
|
+
Add to your MCP config:
|
|
90
|
+
|
|
91
|
+
**Claude Desktop** (`~/.claude/mcp.json`), **Cursor** (`.cursor/mcp.json`), **Windsurf** (`~/.codeium/windsurf/mcp_config.json`):
|
|
25
92
|
|
|
26
93
|
```json
|
|
27
94
|
{
|
|
@@ -34,223 +101,332 @@ Add AgentAudit to Claude Desktop, Cursor, or Windsurf. **No API key needed** —
|
|
|
34
101
|
}
|
|
35
102
|
```
|
|
36
103
|
|
|
37
|
-
|
|
104
|
+
**VS Code** (`.vscode/mcp.json`):
|
|
38
105
|
|
|
39
|
-
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"servers": {
|
|
109
|
+
"agentaudit": {
|
|
110
|
+
"command": "npx",
|
|
111
|
+
"args": ["-y", "agentaudit"]
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
40
116
|
|
|
41
|
-
|
|
42
|
-
# Install
|
|
43
|
-
npm install -g agentaudit # or use npx agentaudit <command>
|
|
117
|
+
Your AI agent can then use AgentAudit's tools to scan packages directly within your editor.
|
|
44
118
|
|
|
45
|
-
|
|
46
|
-
npx agentaudit discover
|
|
119
|
+
---
|
|
47
120
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
121
|
+
## 📋 Commands Reference
|
|
122
|
+
|
|
123
|
+
| Command | Description | Example |
|
|
124
|
+
|---------|-------------|---------|
|
|
125
|
+
| `agentaudit` | Discover MCP servers (default, same as `discover`) | `agentaudit` |
|
|
126
|
+
| `agentaudit discover` | Find MCP servers in Cursor, Claude, VS Code, Windsurf | `agentaudit discover` |
|
|
127
|
+
| `agentaudit discover --quick` | Discover + auto-scan all servers | `agentaudit discover --quick` |
|
|
128
|
+
| `agentaudit discover --deep` | Discover + interactively select servers to deep-audit | `agentaudit discover --deep` |
|
|
129
|
+
| `agentaudit scan <url>` | Quick regex-based static scan (~2s) | `agentaudit scan https://github.com/owner/repo` |
|
|
130
|
+
| `agentaudit scan <url> --deep` | Deep audit (same as `audit`) | `agentaudit scan https://github.com/owner/repo --deep` |
|
|
131
|
+
| `agentaudit audit <url>` | Deep LLM-powered 3-pass audit (~30s) | `agentaudit audit https://github.com/owner/repo` |
|
|
132
|
+
| `agentaudit lookup <name>` | Look up package in trust registry | `agentaudit lookup fastmcp` |
|
|
133
|
+
| `agentaudit setup` | Register agent + configure API key | `agentaudit setup` |
|
|
134
|
+
|
|
135
|
+
### Global Flags
|
|
136
|
+
|
|
137
|
+
| Flag | Description |
|
|
138
|
+
|------|-------------|
|
|
139
|
+
| `--json` | Output machine-readable JSON to stdout |
|
|
140
|
+
| `--quiet` / `-q` | Suppress banner and decorative output (show findings only) |
|
|
141
|
+
| `--no-color` | Disable ANSI colors (also respects `NO_COLOR` env var) |
|
|
142
|
+
| `--help` / `-h` | Show help text |
|
|
143
|
+
| `-v` / `--version` | Show version |
|
|
144
|
+
|
|
145
|
+
### Exit Codes
|
|
146
|
+
|
|
147
|
+
| Code | Meaning |
|
|
148
|
+
|------|---------|
|
|
149
|
+
| `0` | Clean — no findings detected, or successful lookup |
|
|
150
|
+
| `1` | Findings detected |
|
|
151
|
+
| `2` | Error (clone failed, network error, invalid args) |
|
|
51
152
|
|
|
52
|
-
|
|
53
|
-
npx agentaudit setup
|
|
54
|
-
```
|
|
153
|
+
---
|
|
55
154
|
|
|
56
|
-
|
|
155
|
+
## ⚖️ Quick Scan vs Deep Audit
|
|
57
156
|
|
|
58
|
-
|
|
157
|
+
| | Quick Scan (`scan`) | Deep Audit (`audit`) |
|
|
158
|
+
|---|---------------------|---------------------|
|
|
159
|
+
| **Speed** | ~2 seconds | ~30 seconds |
|
|
160
|
+
| **Method** | Regex pattern matching | LLM-powered 3-pass analysis |
|
|
161
|
+
| **API key needed** | No | Yes (`ANTHROPIC_API_KEY` or `OPENAI_API_KEY`) |
|
|
162
|
+
| **False positives** | Higher (regex limitations) | Very low (context-aware) |
|
|
163
|
+
| **Detects** | Common patterns (injection, secrets, eval) | Complex attack chains, AI-specific threats, obfuscation |
|
|
164
|
+
| **Best for** | Quick triage, CI pipelines | Critical packages, pre-production review |
|
|
59
165
|
|
|
60
|
-
|
|
61
|
-
$ npx agentaudit discover
|
|
166
|
+
**Tip:** Use `agentaudit scan <url> --deep` to run a deep audit via the scan command.
|
|
62
167
|
|
|
63
|
-
|
|
64
|
-
Security scanner for AI packages
|
|
168
|
+
---
|
|
65
169
|
|
|
66
|
-
|
|
170
|
+
## 🔌 MCP Server
|
|
67
171
|
|
|
68
|
-
|
|
69
|
-
│ SAFE Risk 0 ✔ official https://agentaudit.dev/skills/fastmcp
|
|
70
|
-
└── my-tool npm:some-mcp-tool
|
|
71
|
-
⚠ not audited Run: agentaudit audit https://github.com/user/some-mcp-tool
|
|
172
|
+
When running as an MCP server, AgentAudit exposes the following tools to your AI agent:
|
|
72
173
|
|
|
73
|
-
|
|
174
|
+
| Tool | Description |
|
|
175
|
+
|------|-------------|
|
|
176
|
+
| `audit_package` | Deep LLM-powered audit of a repository |
|
|
177
|
+
| `check_registry` | Look up a package in the trust registry |
|
|
178
|
+
| `submit_report` | Upload audit findings to the registry |
|
|
179
|
+
| `discover_servers` | Find MCP servers in local editor configs |
|
|
74
180
|
|
|
75
|
-
|
|
76
|
-
⚠ 1 not audited
|
|
181
|
+
### Workflow
|
|
77
182
|
|
|
78
|
-
|
|
79
|
-
|
|
183
|
+
```
|
|
184
|
+
User asks agent to install a package
|
|
185
|
+
│
|
|
186
|
+
▼
|
|
187
|
+
Agent calls check_registry(package_name)
|
|
188
|
+
│
|
|
189
|
+
┌────┴────┐
|
|
190
|
+
│ │
|
|
191
|
+
Found Not Found
|
|
192
|
+
│ │
|
|
193
|
+
▼ ▼
|
|
194
|
+
Return Agent calls audit_package(repo_url)
|
|
195
|
+
score │
|
|
196
|
+
▼
|
|
197
|
+
LLM analyzes code (3-pass)
|
|
198
|
+
│
|
|
199
|
+
▼
|
|
200
|
+
Agent calls submit_report(findings)
|
|
201
|
+
│
|
|
202
|
+
▼
|
|
203
|
+
Return findings + risk score
|
|
80
204
|
```
|
|
81
205
|
|
|
82
206
|
---
|
|
83
207
|
|
|
84
|
-
##
|
|
208
|
+
## 🎯 What It Detects
|
|
85
209
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
| `check <name>` | Look up a package in the registry | ⚡ instant |
|
|
90
|
-
| `scan <url>` | Quick static analysis (regex-based, local) | 🔵 ~2s |
|
|
91
|
-
| `audit <url>` | **Deep LLM-powered security audit** | 🔴 ~30s |
|
|
92
|
-
| `setup` | Register + configure API key | interactive |
|
|
210
|
+
<table>
|
|
211
|
+
<tr>
|
|
212
|
+
<td>
|
|
93
213
|
|
|
94
|
-
|
|
214
|
+
**Core Security**
|
|
95
215
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
| **Uploads to registry** | No | Yes (with `agentaudit setup`) |
|
|
216
|
+

|
|
217
|
+

|
|
218
|
+

|
|
219
|
+

|
|
220
|
+

|
|
221
|
+

|
|
103
222
|
|
|
104
|
-
|
|
223
|
+
</td>
|
|
224
|
+
<td>
|
|
105
225
|
|
|
106
|
-
|
|
107
|
-
# Discover all MCP servers on your machine
|
|
108
|
-
npx agentaudit discover
|
|
226
|
+
**AI-Specific**
|
|
109
227
|
|
|
110
|
-
|
|
111
|
-
|
|
228
|
+

|
|
229
|
+

|
|
230
|
+

|
|
231
|
+

|
|
232
|
+

|
|
233
|
+

|
|
112
234
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
235
|
+
</td>
|
|
236
|
+
</tr>
|
|
237
|
+
<tr>
|
|
238
|
+
<td>
|
|
116
239
|
|
|
117
|
-
|
|
118
|
-
npx agentaudit audit https://github.com/owner/repo --export
|
|
240
|
+
**MCP-Specific**
|
|
119
241
|
|
|
120
|
-
|
|
121
|
-
|
|
242
|
+

|
|
243
|
+

|
|
244
|
+

|
|
245
|
+

|
|
246
|
+

|
|
122
247
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
248
|
+
</td>
|
|
249
|
+
<td>
|
|
250
|
+
|
|
251
|
+
**Persistence & Obfuscation**
|
|
252
|
+
|
|
253
|
+

|
|
254
|
+

|
|
255
|
+

|
|
256
|
+

|
|
257
|
+

|
|
258
|
+

|
|
259
|
+
|
|
260
|
+
</td>
|
|
261
|
+
</tr>
|
|
262
|
+
</table>
|
|
126
263
|
|
|
127
264
|
---
|
|
128
265
|
|
|
129
|
-
##
|
|
266
|
+
## 🧠 How the 3-Pass Audit Works
|
|
130
267
|
|
|
131
|
-
|
|
268
|
+
The deep audit (`agentaudit audit`) uses a structured 3-phase LLM analysis — not a single-shot prompt, but a rigorous multi-pass process:
|
|
132
269
|
|
|
133
|
-
|
|
|
134
|
-
|
|
135
|
-
|
|
|
136
|
-
|
|
|
137
|
-
|
|
|
138
|
-
| `check_package` | Quick registry lookup for a package |
|
|
270
|
+
| Phase | Name | What Happens |
|
|
271
|
+
|-------|------|-------------|
|
|
272
|
+
| **1** | 🔍 **UNDERSTAND** | Read all files and build a **Package Profile**: purpose, category, expected behaviors, trust boundaries. No scanning yet — the goal is to understand what the package *should* do before looking for what it *shouldn't*. |
|
|
273
|
+
| **2** | 🎯 **DETECT** | Evidence collection against **50+ detection patterns** across 8 categories (AI-specific, MCP, persistence, obfuscation, cross-file correlation). Only facts are recorded — no severity judgments yet. |
|
|
274
|
+
| **3** | ⚖️ **CLASSIFY** | Every finding goes through a **Mandatory Self-Check** (5 questions), **Exploitability Assessment**, and **Confidence Gating**. HIGH/CRITICAL findings must survive a **Devil's Advocate** challenge and include a full **Reasoning Chain**. |
|
|
139
275
|
|
|
140
|
-
|
|
276
|
+
**Why 3 passes?** Single-pass analysis is the #1 cause of false positives. By separating understanding → detection → classification:
|
|
141
277
|
|
|
142
|
-
|
|
278
|
+
- Phase 1 prevents flagging core functionality as suspicious (e.g., SQL execution in a database tool)
|
|
279
|
+
- Phase 2 ensures evidence is collected without severity bias
|
|
280
|
+
- Phase 3 catches false positives before they reach the report
|
|
143
281
|
|
|
144
|
-
**
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
282
|
+
This architecture achieved **0% false positives** on our 11-package test set, down from 42% in v2.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## 🔄 CI/CD Integration
|
|
287
|
+
|
|
288
|
+
AgentAudit is designed for CI pipelines with proper exit codes and JSON output:
|
|
289
|
+
|
|
290
|
+
```yaml
|
|
291
|
+
# GitHub Actions example
|
|
292
|
+
- name: Scan MCP servers
|
|
293
|
+
run: |
|
|
294
|
+
npx agentaudit scan https://github.com/org/mcp-server --json --quiet > results.json
|
|
295
|
+
# Exit code 1 = findings detected → fail the build
|
|
154
296
|
```
|
|
155
297
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
298
|
+
```bash
|
|
299
|
+
# Shell scripting
|
|
300
|
+
agentaudit scan https://github.com/owner/repo --json --quiet 2>/dev/null
|
|
301
|
+
if [ $? -eq 1 ]; then
|
|
302
|
+
echo "Security findings detected!"
|
|
303
|
+
exit 1
|
|
304
|
+
fi
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### JSON Output Examples
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
# Scan with JSON output
|
|
311
|
+
agentaudit scan https://github.com/owner/repo --json
|
|
166
312
|
```
|
|
167
313
|
|
|
168
|
-
**Windsurf** (`~/.codeium/windsurf/mcp_config.json`):
|
|
169
314
|
```json
|
|
170
315
|
{
|
|
171
|
-
"
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
316
|
+
"slug": "repo",
|
|
317
|
+
"url": "https://github.com/owner/repo",
|
|
318
|
+
"findings": [
|
|
319
|
+
{
|
|
320
|
+
"severity": "high",
|
|
321
|
+
"title": "Command injection risk",
|
|
322
|
+
"file": "src/handler.js",
|
|
323
|
+
"line": 42,
|
|
324
|
+
"snippet": "exec(`git ${userInput}`)"
|
|
175
325
|
}
|
|
176
|
-
|
|
326
|
+
],
|
|
327
|
+
"fileCount": 15,
|
|
328
|
+
"duration": "1.8s"
|
|
177
329
|
}
|
|
178
330
|
```
|
|
179
331
|
|
|
180
|
-
|
|
181
|
-
|
|
332
|
+
```bash
|
|
333
|
+
# Registry lookup with JSON
|
|
334
|
+
agentaudit lookup fastmcp --json
|
|
182
335
|
```
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
336
|
+
|
|
337
|
+
> **Coming soon:** `--fail-on <severity>` flag to set minimum severity threshold for non-zero exit (e.g., `--fail-on high` ignores low/medium findings).
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## ⚙️ Configuration
|
|
342
|
+
|
|
343
|
+
### Credentials
|
|
344
|
+
|
|
345
|
+
AgentAudit stores credentials in `~/.config/agentaudit/credentials.json` (or `$XDG_CONFIG_HOME/agentaudit/credentials.json`).
|
|
346
|
+
|
|
347
|
+
Run `agentaudit setup` to configure interactively, or set via environment:
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
export AGENTAUDIT_API_KEY=asf_your_key_here
|
|
198
351
|
```
|
|
199
352
|
|
|
200
|
-
###
|
|
353
|
+
### Environment Variables
|
|
354
|
+
|
|
355
|
+
| Variable | Description |
|
|
356
|
+
|----------|-------------|
|
|
357
|
+
| `AGENTAUDIT_API_KEY` | API key for registry access |
|
|
358
|
+
| `ANTHROPIC_API_KEY` | Anthropic API key for deep audits (Claude) |
|
|
359
|
+
| `OPENAI_API_KEY` | OpenAI API key for deep audits (GPT-4o) |
|
|
360
|
+
| `NO_COLOR` | Disable ANSI colors ([no-color.org](https://no-color.org)) |
|
|
201
361
|
|
|
202
|
-
|
|
362
|
+
---
|
|
203
363
|
|
|
204
|
-
|
|
205
|
-
2. `~/.config/agentaudit/credentials.json` (created by `setup`)
|
|
364
|
+
## 📦 Requirements
|
|
206
365
|
|
|
207
|
-
|
|
366
|
+
- **Node.js** ≥ 18.0.0
|
|
367
|
+
- **Git** (for cloning repositories during scan/audit)
|
|
208
368
|
|
|
209
369
|
---
|
|
210
370
|
|
|
211
|
-
##
|
|
371
|
+
## ❓ FAQ
|
|
372
|
+
|
|
373
|
+
### How do I set up AgentAudit?
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
npm install -g agentaudit
|
|
377
|
+
agentaudit setup
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Or use without installing: `npx agentaudit`
|
|
381
|
+
|
|
382
|
+
### Do I need an API key?
|
|
212
383
|
|
|
213
|
-
|
|
384
|
+
- **Quick scan** (`scan`): No API key needed — runs locally with regex
|
|
385
|
+
- **Deep audit** (`audit`): Needs `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`
|
|
386
|
+
- **Registry lookup** (`lookup`): No key needed for reading; key needed for uploading reports
|
|
387
|
+
- **MCP server**: No extra key needed — uses the host editor's LLM
|
|
214
388
|
|
|
215
|
-
|
|
216
|
-
- 🔴 Shell command injection
|
|
217
|
-
- 🔴 SQL injection
|
|
218
|
-
- 🔴 Unsafe deserialization (pickle, YAML)
|
|
219
|
-
- 🟡 Hardcoded secrets
|
|
220
|
-
- 🟡 SSL/TLS verification disabled
|
|
221
|
-
- 🟡 Path traversal
|
|
222
|
-
- 🔵 Wildcard CORS
|
|
223
|
-
- 🔵 Undisclosed telemetry
|
|
389
|
+
### What data is sent externally?
|
|
224
390
|
|
|
225
|
-
|
|
391
|
+
- **Registry lookups**: Package name/slug is sent to `agentaudit.dev` to check for existing audits
|
|
392
|
+
- **Report uploads**: Audit findings are uploaded to the public registry (requires API key)
|
|
393
|
+
- **Deep audits**: Source code is sent to Anthropic or OpenAI for LLM analysis
|
|
394
|
+
- **Quick scans**: Everything stays local — no data leaves your machine
|
|
226
395
|
|
|
227
|
-
|
|
396
|
+
### Can I use it offline?
|
|
228
397
|
|
|
229
|
-
|
|
230
|
-
- 🔴 Agent manipulation (impersonation, capability escalation, jailbreaks)
|
|
231
|
-
- 🔴 MCP-specific: tool description injection, resource traversal, unpinned npx
|
|
232
|
-
- 🟡 Persistence mechanisms (crontab, shell RC, git hooks, systemd)
|
|
233
|
-
- 🟡 Obfuscation (base64 exec, zero-width chars, ANSI escapes)
|
|
234
|
-
- 🟡 Context pollution & indirect prompt injection
|
|
398
|
+
Quick scans (`agentaudit scan`) work fully offline after cloning. Registry lookups and deep audits require network access.
|
|
235
399
|
|
|
236
|
-
|
|
400
|
+
### Can I use it as an MCP server without the CLI?
|
|
401
|
+
|
|
402
|
+
Yes! `npx agentaudit` starts the MCP server when invoked by an editor. The CLI and MCP server are the same package — behavior is determined by how it's called.
|
|
403
|
+
|
|
404
|
+
### How does `discover` know which editors I use?
|
|
405
|
+
|
|
406
|
+
It checks standard config file locations for Claude Desktop, Cursor, VS Code, and Windsurf. It also checks the current working directory for project-level `.cursor/mcp.json` and `.vscode/mcp.json`.
|
|
237
407
|
|
|
238
408
|
---
|
|
239
409
|
|
|
240
|
-
##
|
|
410
|
+
## 🔗 Related Links
|
|
241
411
|
|
|
242
|
-
- **
|
|
243
|
-
- **
|
|
412
|
+
- **Trust Registry**: [agentaudit.dev](https://agentaudit.dev)
|
|
413
|
+
- **Leaderboard**: [agentaudit.dev/leaderboard](https://agentaudit.dev/leaderboard)
|
|
414
|
+
- **Skill Repository**: [github.com/starbuck100/agentaudit-skill](https://github.com/starbuck100/agentaudit-skill)
|
|
415
|
+
- **MCP Server Repository**: [github.com/starbuck100/agentaudit-mcp](https://github.com/starbuck100/agentaudit-mcp)
|
|
416
|
+
- **Report Issues**: [GitHub Issues](https://github.com/starbuck100/agentaudit-mcp/issues)
|
|
244
417
|
|
|
245
418
|
---
|
|
246
419
|
|
|
247
|
-
##
|
|
420
|
+
## 📄 License
|
|
248
421
|
|
|
249
|
-
-
|
|
250
|
-
- **[agentaudit-skill](https://github.com/starbuck100/agentaudit-skill)** — Full agent skill with gate scripts, detection patterns & peer review system
|
|
422
|
+
[AGPL-3.0](LICENSE) — Free for open source use. Commercial license available for proprietary integrations.
|
|
251
423
|
|
|
252
424
|
---
|
|
253
425
|
|
|
254
|
-
|
|
426
|
+
<div align="center">
|
|
255
427
|
|
|
256
|
-
|
|
428
|
+
**Protect your AI stack. Scan before you trust.**
|
|
429
|
+
|
|
430
|
+
[Trust Registry](https://agentaudit.dev) · [Leaderboard](https://agentaudit.dev/leaderboard) · [Report Issues](https://github.com/starbuck100/agentaudit-mcp/issues)
|
|
431
|
+
|
|
432
|
+
</div>
|
package/cli.mjs
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* AgentAudit CLI —
|
|
3
|
+
* AgentAudit CLI — Security scanner for AI packages
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* agentaudit
|
|
7
|
-
* agentaudit
|
|
8
|
-
* agentaudit
|
|
6
|
+
* agentaudit Discover local MCP servers
|
|
7
|
+
* agentaudit discover [--quick|--deep] Find MCP servers in AI editors
|
|
8
|
+
* agentaudit scan <repo-url> [--deep] Quick scan (or deep audit with --deep)
|
|
9
|
+
* agentaudit audit <repo-url> Deep LLM-powered security audit
|
|
10
|
+
* agentaudit lookup <name> Look up package in registry
|
|
11
|
+
* agentaudit setup Register + configure API key
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
* agentaudit setup
|
|
12
|
-
* agentaudit scan https://github.com/owner/repo
|
|
13
|
-
* agentaudit scan repo1 repo2 repo3
|
|
13
|
+
* Global flags: --json, --quiet, --no-color
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import fs from 'fs';
|
|
@@ -24,9 +24,19 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
24
24
|
const SKILL_DIR = path.resolve(__dirname);
|
|
25
25
|
const REGISTRY_URL = 'https://agentaudit.dev';
|
|
26
26
|
|
|
27
|
-
// ──
|
|
27
|
+
// ── Global flags (set in main before command routing) ────
|
|
28
|
+
let jsonMode = false;
|
|
29
|
+
let quietMode = false;
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
// ── ANSI Colors (respects NO_COLOR and --no-color) ───────
|
|
32
|
+
|
|
33
|
+
const noColor = !!(process.env.NO_COLOR || process.argv.includes('--no-color'));
|
|
34
|
+
|
|
35
|
+
const c = noColor ? {
|
|
36
|
+
reset: '', bold: '', dim: '', red: '', green: '', yellow: '',
|
|
37
|
+
blue: '', magenta: '', cyan: '', white: '', gray: '',
|
|
38
|
+
bgRed: '', bgGreen: '', bgYellow: '',
|
|
39
|
+
} : {
|
|
30
40
|
reset: '\x1b[0m',
|
|
31
41
|
bold: '\x1b[1m',
|
|
32
42
|
dim: '\x1b[2m',
|
|
@@ -93,6 +103,111 @@ function askQuestion(question) {
|
|
|
93
103
|
return new Promise(resolve => rl.question(question, answer => { rl.close(); resolve(answer.trim()); }));
|
|
94
104
|
}
|
|
95
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Interactive multi-select in terminal. No dependencies.
|
|
108
|
+
* items: [{ label, sublabel?, value, checked? }]
|
|
109
|
+
* Returns: array of selected values
|
|
110
|
+
*/
|
|
111
|
+
function multiSelect(items, { title = 'Select items', hint = 'Space=toggle ↑↓=move a=all n=none Enter=confirm' } = {}) {
|
|
112
|
+
return new Promise((resolve) => {
|
|
113
|
+
if (!process.stdin.isTTY) {
|
|
114
|
+
// Non-interactive: return all items
|
|
115
|
+
resolve(items.map(i => i.value));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const selected = new Set(items.filter(i => i.checked).map((_, idx) => idx));
|
|
120
|
+
let cursor = 0;
|
|
121
|
+
|
|
122
|
+
const render = () => {
|
|
123
|
+
// Move cursor up to overwrite previous render
|
|
124
|
+
process.stdout.write(`\x1b[${items.length + 3}A\x1b[J`);
|
|
125
|
+
draw();
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const draw = () => {
|
|
129
|
+
console.log(` ${c.bold}${title}${c.reset} ${c.dim}(${selected.size}/${items.length} selected)${c.reset}`);
|
|
130
|
+
console.log(` ${c.dim}${hint}${c.reset}`);
|
|
131
|
+
console.log();
|
|
132
|
+
for (let i = 0; i < items.length; i++) {
|
|
133
|
+
const item = items[i];
|
|
134
|
+
const isCursor = i === cursor;
|
|
135
|
+
const isSelected = selected.has(i);
|
|
136
|
+
const pointer = isCursor ? `${c.cyan}❯${c.reset}` : ' ';
|
|
137
|
+
const checkbox = isSelected ? `${c.green}◉${c.reset}` : `${c.dim}○${c.reset}`;
|
|
138
|
+
const label = isCursor ? `${c.bold}${item.label}${c.reset}` : item.label;
|
|
139
|
+
const sub = item.sublabel ? ` ${c.dim}${item.sublabel}${c.reset}` : '';
|
|
140
|
+
console.log(` ${pointer} ${checkbox} ${label}${sub}`);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Initial draw
|
|
145
|
+
draw();
|
|
146
|
+
|
|
147
|
+
process.stdin.setRawMode(true);
|
|
148
|
+
process.stdin.resume();
|
|
149
|
+
process.stdin.setEncoding('utf8');
|
|
150
|
+
|
|
151
|
+
const onData = (key) => {
|
|
152
|
+
// Ctrl+C
|
|
153
|
+
if (key === '\x03') {
|
|
154
|
+
process.stdin.setRawMode(false);
|
|
155
|
+
process.stdin.pause();
|
|
156
|
+
process.stdin.removeListener('data', onData);
|
|
157
|
+
console.log();
|
|
158
|
+
process.exit(0);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Enter
|
|
162
|
+
if (key === '\r' || key === '\n') {
|
|
163
|
+
process.stdin.setRawMode(false);
|
|
164
|
+
process.stdin.pause();
|
|
165
|
+
process.stdin.removeListener('data', onData);
|
|
166
|
+
resolve(items.filter((_, i) => selected.has(i)).map(i => i.value));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Space — toggle
|
|
171
|
+
if (key === ' ') {
|
|
172
|
+
if (selected.has(cursor)) selected.delete(cursor);
|
|
173
|
+
else selected.add(cursor);
|
|
174
|
+
render();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// a — select all
|
|
179
|
+
if (key === 'a') {
|
|
180
|
+
for (let i = 0; i < items.length; i++) selected.add(i);
|
|
181
|
+
render();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// n — select none
|
|
186
|
+
if (key === 'n') {
|
|
187
|
+
selected.clear();
|
|
188
|
+
render();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Arrow up / k
|
|
193
|
+
if (key === '\x1b[A' || key === 'k') {
|
|
194
|
+
cursor = (cursor - 1 + items.length) % items.length;
|
|
195
|
+
render();
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Arrow down / j
|
|
200
|
+
if (key === '\x1b[B' || key === 'j') {
|
|
201
|
+
cursor = (cursor + 1) % items.length;
|
|
202
|
+
render();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
process.stdin.on('data', onData);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
96
211
|
async function registerAgent(agentName) {
|
|
97
212
|
const res = await fetch(`${REGISTRY_URL}/api/register`, {
|
|
98
213
|
method: 'POST',
|
|
@@ -176,6 +291,7 @@ function getVersion() {
|
|
|
176
291
|
}
|
|
177
292
|
|
|
178
293
|
function banner() {
|
|
294
|
+
if (quietMode || jsonMode) return;
|
|
179
295
|
console.log();
|
|
180
296
|
console.log(` ${c.bold}${c.cyan}AgentAudit${c.reset} ${c.dim}v${getVersion()}${c.reset}`);
|
|
181
297
|
console.log(` ${c.dim}Security scanner for AI packages${c.reset}`);
|
|
@@ -471,8 +587,30 @@ async function checkRegistry(slug) {
|
|
|
471
587
|
// ── Print results ───────────────────────────────────────
|
|
472
588
|
|
|
473
589
|
function printScanResult(url, info, files, findings, registryData, duration) {
|
|
590
|
+
if (jsonMode) return; // JSON mode handles output separately
|
|
591
|
+
|
|
474
592
|
const slug = slugFromUrl(url);
|
|
475
593
|
|
|
594
|
+
// Quiet mode: compact one-line-per-package output
|
|
595
|
+
if (quietMode) {
|
|
596
|
+
if (findings.length > 0) {
|
|
597
|
+
const bySev = {};
|
|
598
|
+
for (const f of findings) { bySev[f.severity] = (bySev[f.severity] || 0) + 1; }
|
|
599
|
+
const sevStr = Object.entries(bySev).map(([s, n]) => {
|
|
600
|
+
const sc = severityColor(s);
|
|
601
|
+
return `${sc}${n} ${s}${c.reset}`;
|
|
602
|
+
}).join(', ');
|
|
603
|
+
console.log(`${icons.caution} ${c.bold}${slug}${c.reset} ${findings.length} findings (${sevStr}) ${c.dim}${duration}${c.reset}`);
|
|
604
|
+
for (const f of findings) {
|
|
605
|
+
const sc = severityColor(f.severity);
|
|
606
|
+
console.log(` ${severityIcon(f.severity)} ${sc}${f.severity.toUpperCase().padEnd(8)}${c.reset} ${f.title} ${c.dim}${f.file}:${f.line}${c.reset}`);
|
|
607
|
+
}
|
|
608
|
+
} else {
|
|
609
|
+
console.log(`${icons.safe} ${c.bold}${slug}${c.reset} ${c.green}clean${c.reset} ${c.dim}${files.length} files, ${duration}${c.reset}`);
|
|
610
|
+
}
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
|
|
476
614
|
// Header
|
|
477
615
|
console.log(`${icons.scan} ${c.bold}${slug}${c.reset} ${c.dim}${url}${c.reset}`);
|
|
478
616
|
console.log(`${icons.pipe} ${c.dim}${info.language} ${info.type}${c.reset} ${c.dim}${files.length} files scanned in ${duration}${c.reset}`);
|
|
@@ -571,7 +709,7 @@ async function scanRepo(url) {
|
|
|
571
709
|
const start = Date.now();
|
|
572
710
|
const slug = slugFromUrl(url);
|
|
573
711
|
|
|
574
|
-
process.stdout.write(`${icons.scan} Scanning ${c.bold}${slug}${c.reset} ${c.dim}...${c.reset}`);
|
|
712
|
+
if (!jsonMode) process.stdout.write(`${icons.scan} Scanning ${c.bold}${slug}${c.reset} ${c.dim}...${c.reset}`);
|
|
575
713
|
|
|
576
714
|
// Clone
|
|
577
715
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agentaudit-'));
|
|
@@ -582,10 +720,12 @@ async function scanRepo(url) {
|
|
|
582
720
|
stdio: 'pipe',
|
|
583
721
|
});
|
|
584
722
|
} catch (err) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
723
|
+
if (!jsonMode) {
|
|
724
|
+
process.stdout.write(` ${c.red}✖ clone failed${c.reset}\n`);
|
|
725
|
+
const msg = err.stderr?.toString().trim() || err.message?.split('\n')[0] || '';
|
|
726
|
+
if (msg) console.log(` ${c.dim}${msg}${c.reset}`);
|
|
727
|
+
console.log(` ${c.dim}Make sure git is installed and the URL is accessible.${c.reset}`);
|
|
728
|
+
}
|
|
589
729
|
return null;
|
|
590
730
|
}
|
|
591
731
|
|
|
@@ -606,11 +746,13 @@ async function scanRepo(url) {
|
|
|
606
746
|
|
|
607
747
|
const duration = elapsed(start);
|
|
608
748
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
749
|
+
if (!jsonMode) {
|
|
750
|
+
// Clear the "Scanning..." line
|
|
751
|
+
process.stdout.write('\r\x1b[K');
|
|
752
|
+
|
|
753
|
+
// Print result
|
|
754
|
+
printScanResult(url, info, files, findings, registryData, duration);
|
|
755
|
+
}
|
|
614
756
|
|
|
615
757
|
return { slug, url, info, files: files.length, findings, registryData, duration };
|
|
616
758
|
}
|
|
@@ -834,9 +976,12 @@ async function resolveSourceUrl(server) {
|
|
|
834
976
|
|
|
835
977
|
async function discoverCommand(options = {}) {
|
|
836
978
|
const autoScan = options.scan || false;
|
|
979
|
+
const interactiveAudit = options.audit || false;
|
|
837
980
|
|
|
838
|
-
|
|
839
|
-
|
|
981
|
+
if (!jsonMode) {
|
|
982
|
+
console.log(` ${c.bold}Discovering MCP servers in your AI editors...${c.reset}`);
|
|
983
|
+
console.log();
|
|
984
|
+
}
|
|
840
985
|
|
|
841
986
|
const configs = findMcpConfigs();
|
|
842
987
|
|
|
@@ -990,10 +1135,10 @@ async function discoverCommand(options = {}) {
|
|
|
990
1135
|
console.log();
|
|
991
1136
|
if (serversWithFindings > 0) {
|
|
992
1137
|
console.log(` ${c.yellow}${serversWithFindings}/${scanResults.length} server${scanResults.length !== 1 ? 's' : ''} with findings (${totalFindings} total)${c.reset}`);
|
|
993
|
-
console.log(` ${c.dim}Run ${c.cyan}agentaudit
|
|
1138
|
+
console.log(` ${c.dim}Run ${c.cyan}agentaudit scan <url> --deep${c.dim} for deep LLM analysis on flagged servers${c.reset}`);
|
|
994
1139
|
} else {
|
|
995
1140
|
console.log(` ${c.green}All servers passed quick scan${c.reset}`);
|
|
996
|
-
console.log(` ${c.dim}Run ${c.cyan}agentaudit
|
|
1141
|
+
console.log(` ${c.dim}Run ${c.cyan}agentaudit scan <url> --deep${c.dim} for thorough LLM-powered analysis${c.reset}`);
|
|
997
1142
|
}
|
|
998
1143
|
console.log();
|
|
999
1144
|
}
|
|
@@ -1001,6 +1146,45 @@ async function discoverCommand(options = {}) {
|
|
|
1001
1146
|
console.log(` ${c.dim}No scannable source URLs found.${c.reset}`);
|
|
1002
1147
|
console.log();
|
|
1003
1148
|
}
|
|
1149
|
+
} else if (interactiveAudit && allServersWithUrls.length > 0) {
|
|
1150
|
+
// Interactive multi-select for audit
|
|
1151
|
+
const isCloneable = (url) => /^https?:\/\/(github\.com|gitlab\.com|bitbucket\.org)\//i.test(url);
|
|
1152
|
+
const auditCandidates = [];
|
|
1153
|
+
const seen = new Set();
|
|
1154
|
+
for (const s of allServersWithUrls) {
|
|
1155
|
+
if (!s.sourceUrl || !isCloneable(s.sourceUrl)) continue;
|
|
1156
|
+
if (seen.has(s.sourceUrl)) continue;
|
|
1157
|
+
seen.add(s.sourceUrl);
|
|
1158
|
+
auditCandidates.push(s);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
if (auditCandidates.length > 0) {
|
|
1162
|
+
console.log();
|
|
1163
|
+
const items = auditCandidates.map(s => ({
|
|
1164
|
+
label: s.name,
|
|
1165
|
+
sublabel: s.hasAudit ? `${c.green}✔ audited${c.reset} ${s.sourceUrl}` : s.sourceUrl,
|
|
1166
|
+
value: s,
|
|
1167
|
+
checked: !s.hasAudit, // Pre-select unaudited
|
|
1168
|
+
}));
|
|
1169
|
+
|
|
1170
|
+
const selected = await multiSelect(items, {
|
|
1171
|
+
title: 'Select servers to audit',
|
|
1172
|
+
hint: 'Space=toggle ↑↓=move a=all n=none Enter=confirm',
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
if (selected.length > 0) {
|
|
1176
|
+
console.log();
|
|
1177
|
+
console.log(` ${c.bold}Auditing ${selected.length} server${selected.length !== 1 ? 's' : ''}...${c.reset}`);
|
|
1178
|
+
console.log();
|
|
1179
|
+
for (const s of selected) {
|
|
1180
|
+
await auditRepo(s.sourceUrl);
|
|
1181
|
+
console.log();
|
|
1182
|
+
}
|
|
1183
|
+
} else {
|
|
1184
|
+
console.log();
|
|
1185
|
+
console.log(` ${c.dim}No servers selected.${c.reset}`);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1004
1188
|
} else if (unauditedServers > 0) {
|
|
1005
1189
|
if (unauditedWithUrls.length > 0) {
|
|
1006
1190
|
console.log(` ${c.dim}To audit unaudited servers:${c.reset}`);
|
|
@@ -1012,7 +1196,13 @@ async function discoverCommand(options = {}) {
|
|
|
1012
1196
|
console.log(` ${c.cyan}agentaudit audit <source-url>${c.reset}`);
|
|
1013
1197
|
}
|
|
1014
1198
|
console.log();
|
|
1015
|
-
console.log(` ${c.dim}Or run ${c.cyan}agentaudit discover --
|
|
1199
|
+
console.log(` ${c.dim}Or run ${c.cyan}agentaudit discover --quick${c.dim} to quick-scan all servers${c.reset}`);
|
|
1200
|
+
console.log(` ${c.dim}Or run ${c.cyan}agentaudit discover --deep${c.dim} to select & deep-audit interactively${c.reset}`);
|
|
1201
|
+
console.log();
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
if (!autoScan && !interactiveAudit && !jsonMode) {
|
|
1205
|
+
console.log(` ${c.dim}Looking for general package scanning? Try ${c.cyan}pip audit${c.dim} or ${c.cyan}npm audit${c.dim}.${c.reset}`);
|
|
1016
1206
|
console.log();
|
|
1017
1207
|
}
|
|
1018
1208
|
}
|
|
@@ -1270,56 +1460,83 @@ async function auditRepo(url) {
|
|
|
1270
1460
|
// ── Check command ───────────────────────────────────────
|
|
1271
1461
|
|
|
1272
1462
|
async function checkPackage(name) {
|
|
1273
|
-
|
|
1274
|
-
|
|
1463
|
+
if (!jsonMode) {
|
|
1464
|
+
console.log(`${icons.info} Looking up ${c.bold}${name}${c.reset} in registry...`);
|
|
1465
|
+
console.log();
|
|
1466
|
+
}
|
|
1275
1467
|
|
|
1276
1468
|
const data = await checkRegistry(name);
|
|
1277
1469
|
if (!data) {
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1470
|
+
if (!jsonMode) {
|
|
1471
|
+
console.log(` ${c.yellow}Not found${c.reset} — package "${name}" hasn't been audited yet.`);
|
|
1472
|
+
console.log(` ${c.dim}Run: agentaudit audit <repo-url> for a deep LLM audit${c.reset}`);
|
|
1473
|
+
}
|
|
1474
|
+
return null;
|
|
1281
1475
|
}
|
|
1282
1476
|
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1477
|
+
if (!jsonMode) {
|
|
1478
|
+
const riskScore = data.risk_score ?? data.latest_risk_score ?? 0;
|
|
1479
|
+
console.log(` ${c.bold}${name}${c.reset} ${riskBadge(riskScore)}`);
|
|
1480
|
+
console.log(` ${c.dim}Risk Score: ${riskScore}/100${c.reset}`);
|
|
1481
|
+
if (data.source_url) console.log(` ${c.dim}Source: ${data.source_url}${c.reset}`);
|
|
1482
|
+
console.log(` ${c.dim}Registry: ${REGISTRY_URL}/skills/${name}${c.reset}`);
|
|
1483
|
+
if (data.has_official_audit) console.log(` ${c.green}✔ Officially audited${c.reset}`);
|
|
1484
|
+
console.log();
|
|
1485
|
+
}
|
|
1486
|
+
return data;
|
|
1290
1487
|
}
|
|
1291
1488
|
|
|
1292
1489
|
// ── Main ────────────────────────────────────────────────
|
|
1293
1490
|
|
|
1294
1491
|
async function main() {
|
|
1295
|
-
const
|
|
1492
|
+
const rawArgs = process.argv.slice(2);
|
|
1493
|
+
|
|
1494
|
+
// Parse global flags early
|
|
1495
|
+
jsonMode = rawArgs.includes('--json');
|
|
1496
|
+
quietMode = rawArgs.includes('--quiet') || rawArgs.includes('-q');
|
|
1497
|
+
// --no-color already handled at top level for `c` object
|
|
1498
|
+
|
|
1499
|
+
// Strip global flags from args
|
|
1500
|
+
const globalFlags = new Set(['--json', '--quiet', '-q', '--no-color']);
|
|
1501
|
+
const args = rawArgs.filter(a => !globalFlags.has(a));
|
|
1296
1502
|
|
|
1297
1503
|
if (args[0] === '-v' || args[0] === '--version') {
|
|
1298
1504
|
console.log(`agentaudit ${getVersion()}`);
|
|
1299
1505
|
process.exit(0);
|
|
1300
1506
|
}
|
|
1301
1507
|
|
|
1302
|
-
if (args
|
|
1508
|
+
if (args[0] === '--help' || args[0] === '-h') {
|
|
1303
1509
|
banner();
|
|
1304
1510
|
console.log(` ${c.bold}Commands:${c.reset}`);
|
|
1305
1511
|
console.log();
|
|
1306
|
-
console.log(` ${c.cyan}agentaudit
|
|
1307
|
-
console.log(` ${c.cyan}agentaudit discover
|
|
1512
|
+
console.log(` ${c.cyan}agentaudit${c.reset} Discover MCP servers (same as discover)`);
|
|
1513
|
+
console.log(` ${c.cyan}agentaudit discover${c.reset} Find MCP servers in your AI editors (Cursor, Claude, VS Code, Windsurf)`);
|
|
1514
|
+
console.log(` ${c.cyan}agentaudit discover --quick${c.reset} Discover + auto-scan all servers`);
|
|
1515
|
+
console.log(` ${c.cyan}agentaudit discover --deep${c.reset} Discover + select servers to deep-audit`);
|
|
1308
1516
|
console.log(` ${c.cyan}agentaudit scan${c.reset} <url> [url...] Quick static scan (regex, local)`);
|
|
1517
|
+
console.log(` ${c.cyan}agentaudit scan${c.reset} <url> ${c.dim}--deep${c.reset} Deep audit (same as audit)`);
|
|
1309
1518
|
console.log(` ${c.cyan}agentaudit audit${c.reset} <url> [url...] Deep LLM-powered security audit`);
|
|
1310
|
-
console.log(` ${c.cyan}agentaudit
|
|
1519
|
+
console.log(` ${c.cyan}agentaudit lookup${c.reset} <name> Look up package in registry`);
|
|
1311
1520
|
console.log(` ${c.cyan}agentaudit setup${c.reset} Register + configure API key`);
|
|
1312
1521
|
console.log();
|
|
1313
|
-
console.log(` ${c.bold}
|
|
1522
|
+
console.log(` ${c.bold}Global flags:${c.reset}`);
|
|
1523
|
+
console.log(` ${c.dim}--json Output JSON to stdout (machine-readable)${c.reset}`);
|
|
1524
|
+
console.log(` ${c.dim}--quiet Suppress banner and tree visualization${c.reset}`);
|
|
1525
|
+
console.log(` ${c.dim}--no-color Disable ANSI colors (also: NO_COLOR env)${c.reset}`);
|
|
1526
|
+
console.log();
|
|
1527
|
+
console.log(` ${c.bold}Quick Scan${c.reset} vs ${c.bold}Deep Audit${c.reset}:`);
|
|
1314
1528
|
console.log(` ${c.dim}scan = fast regex-based static analysis (~2s)${c.reset}`);
|
|
1315
1529
|
console.log(` ${c.dim}audit = deep LLM analysis with 3-pass methodology (~30s)${c.reset}`);
|
|
1316
1530
|
console.log();
|
|
1531
|
+
console.log(` ${c.bold}Exit codes:${c.reset}`);
|
|
1532
|
+
console.log(` ${c.dim}0 = clean / success 1 = findings detected 2 = error${c.reset}`);
|
|
1533
|
+
console.log();
|
|
1317
1534
|
console.log(` ${c.bold}Examples:${c.reset}`);
|
|
1318
|
-
console.log(` agentaudit
|
|
1319
|
-
console.log(` agentaudit discover --
|
|
1535
|
+
console.log(` agentaudit`);
|
|
1536
|
+
console.log(` agentaudit discover --quick`);
|
|
1320
1537
|
console.log(` agentaudit scan https://github.com/owner/repo`);
|
|
1321
1538
|
console.log(` agentaudit audit https://github.com/owner/repo`);
|
|
1322
|
-
console.log(` agentaudit
|
|
1539
|
+
console.log(` agentaudit lookup fastmcp --json`);
|
|
1323
1540
|
console.log();
|
|
1324
1541
|
console.log(` ${c.bold}For deep audits,${c.reset} set an LLM API key:`);
|
|
1325
1542
|
if (process.platform === 'win32') {
|
|
@@ -1337,7 +1554,8 @@ async function main() {
|
|
|
1337
1554
|
process.exit(0);
|
|
1338
1555
|
}
|
|
1339
1556
|
|
|
1340
|
-
|
|
1557
|
+
// Default no-arg → discover
|
|
1558
|
+
const command = args.length === 0 ? 'discover' : args[0];
|
|
1341
1559
|
const targets = args.slice(1);
|
|
1342
1560
|
|
|
1343
1561
|
banner();
|
|
@@ -1348,37 +1566,81 @@ async function main() {
|
|
|
1348
1566
|
}
|
|
1349
1567
|
|
|
1350
1568
|
if (command === 'discover') {
|
|
1351
|
-
const scanFlag = targets.includes('--scan') || targets.includes('-s');
|
|
1352
|
-
|
|
1569
|
+
const scanFlag = targets.includes('--quick') || targets.includes('--scan') || targets.includes('-s');
|
|
1570
|
+
const auditFlag = targets.includes('--deep') || targets.includes('--audit') || targets.includes('-a');
|
|
1571
|
+
await discoverCommand({ scan: scanFlag, audit: auditFlag });
|
|
1353
1572
|
return;
|
|
1354
1573
|
}
|
|
1355
1574
|
|
|
1356
|
-
if (command === 'check') {
|
|
1357
|
-
|
|
1575
|
+
if (command === 'lookup' || command === 'check') {
|
|
1576
|
+
const names = targets.filter(t => !t.startsWith('--'));
|
|
1577
|
+
if (names.length === 0) {
|
|
1358
1578
|
console.log(` ${c.red}Error: package name required${c.reset}`);
|
|
1359
|
-
process.exit(
|
|
1579
|
+
process.exit(2);
|
|
1360
1580
|
}
|
|
1361
|
-
|
|
1581
|
+
const results = [];
|
|
1582
|
+
for (const t of names) {
|
|
1583
|
+
const data = await checkPackage(t);
|
|
1584
|
+
results.push(data);
|
|
1585
|
+
}
|
|
1586
|
+
if (jsonMode) {
|
|
1587
|
+
console.log(JSON.stringify(results.length === 1 ? (results[0] || { error: 'not_found' }) : results, null, 2));
|
|
1588
|
+
}
|
|
1589
|
+
process.exit(0);
|
|
1362
1590
|
return;
|
|
1363
1591
|
}
|
|
1364
1592
|
|
|
1365
1593
|
if (command === 'scan') {
|
|
1366
|
-
|
|
1594
|
+
const deepFlag = targets.includes('--deep');
|
|
1595
|
+
const urls = targets.filter(t => !t.startsWith('--'));
|
|
1596
|
+
if (urls.length === 0) {
|
|
1367
1597
|
console.log(` ${c.red}Error: at least one repository URL required${c.reset}`);
|
|
1368
1598
|
console.log(` ${c.dim}Tip: use ${c.cyan}agentaudit discover${c.dim} to find & check locally installed MCP servers${c.reset}`);
|
|
1369
1599
|
console.log(` ${c.dim}Tip: use ${c.cyan}agentaudit audit <url>${c.dim} for a deep LLM-powered audit${c.reset}`);
|
|
1370
|
-
process.exit(
|
|
1600
|
+
process.exit(2);
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
// --deep redirects to audit flow
|
|
1604
|
+
if (deepFlag) {
|
|
1605
|
+
let hasFindings = false;
|
|
1606
|
+
for (const url of urls) {
|
|
1607
|
+
const report = await auditRepo(url);
|
|
1608
|
+
if (report?.findings?.length > 0) hasFindings = true;
|
|
1609
|
+
}
|
|
1610
|
+
process.exit(hasFindings ? 1 : 0);
|
|
1611
|
+
return;
|
|
1371
1612
|
}
|
|
1372
1613
|
|
|
1373
1614
|
const results = [];
|
|
1374
|
-
|
|
1615
|
+
let hadErrors = false;
|
|
1616
|
+
for (const url of urls) {
|
|
1375
1617
|
const result = await scanRepo(url);
|
|
1376
1618
|
if (result) results.push(result);
|
|
1619
|
+
else hadErrors = true;
|
|
1377
1620
|
}
|
|
1378
1621
|
|
|
1379
|
-
if (
|
|
1622
|
+
if (jsonMode) {
|
|
1623
|
+
const jsonOut = results.map(r => ({
|
|
1624
|
+
slug: r.slug,
|
|
1625
|
+
url: r.url,
|
|
1626
|
+
findings: r.findings.map(f => ({
|
|
1627
|
+
severity: f.severity,
|
|
1628
|
+
title: f.title,
|
|
1629
|
+
file: f.file,
|
|
1630
|
+
line: f.line,
|
|
1631
|
+
snippet: f.snippet,
|
|
1632
|
+
})),
|
|
1633
|
+
fileCount: r.files,
|
|
1634
|
+
duration: r.duration,
|
|
1635
|
+
}));
|
|
1636
|
+
console.log(JSON.stringify(jsonOut.length === 1 ? jsonOut[0] : jsonOut, null, 2));
|
|
1637
|
+
} else if (results.length > 1) {
|
|
1380
1638
|
printSummary(results);
|
|
1381
1639
|
}
|
|
1640
|
+
|
|
1641
|
+
if (hadErrors && results.length === 0) process.exit(2);
|
|
1642
|
+
const totalFindings = results.reduce((sum, r) => sum + r.findings.length, 0);
|
|
1643
|
+
process.exit(totalFindings > 0 ? 1 : 0);
|
|
1382
1644
|
return;
|
|
1383
1645
|
}
|
|
1384
1646
|
|
|
@@ -1386,21 +1648,24 @@ async function main() {
|
|
|
1386
1648
|
const urls = targets.filter(t => !t.startsWith('--'));
|
|
1387
1649
|
if (urls.length === 0) {
|
|
1388
1650
|
console.log(` ${c.red}Error: at least one repository URL required${c.reset}`);
|
|
1389
|
-
process.exit(
|
|
1651
|
+
process.exit(2);
|
|
1390
1652
|
}
|
|
1391
1653
|
|
|
1654
|
+
let hasFindings = false;
|
|
1392
1655
|
for (const url of urls) {
|
|
1393
|
-
await auditRepo(url);
|
|
1656
|
+
const report = await auditRepo(url);
|
|
1657
|
+
if (report?.findings?.length > 0) hasFindings = true;
|
|
1394
1658
|
}
|
|
1659
|
+
process.exit(hasFindings ? 1 : 0);
|
|
1395
1660
|
return;
|
|
1396
1661
|
}
|
|
1397
1662
|
|
|
1398
1663
|
console.log(` ${c.red}Unknown command: ${command}${c.reset}`);
|
|
1399
1664
|
console.log(` ${c.dim}Run agentaudit --help for usage${c.reset}`);
|
|
1400
|
-
process.exit(
|
|
1665
|
+
process.exit(2);
|
|
1401
1666
|
}
|
|
1402
1667
|
|
|
1403
1668
|
main().catch(err => {
|
|
1404
1669
|
console.error(`${c.red}Error: ${err.message}${c.reset}`);
|
|
1405
|
-
process.exit(
|
|
1670
|
+
process.exit(2);
|
|
1406
1671
|
});
|