agentaudit 3.8.0 → 3.9.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.
Files changed (3) hide show
  1. package/README.md +333 -157
  2. package/cli.mjs +186 -66
  3. 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, agent skills, and AI tools for vulnerabilities.
8
- MCP server for agents + standalone CLI for humans.
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
- [![npm](https://img.shields.io/npm/v/agentaudit?style=flat-square&color=00C853)](https://www.npmjs.com/package/agentaudit)
11
- [![Registry](https://img.shields.io/badge/Registry-agentaudit.dev-00C853?style=flat-square)](https://agentaudit.dev)
12
- [![License](https://img.shields.io/badge/License-AGPL_3.0-F9A825?style=flat-square)](LICENSE)
10
+ [![npm version](https://img.shields.io/npm/v/agentaudit?style=for-the-badge&color=CB3837&logo=npm)](https://www.npmjs.com/package/agentaudit)
11
+ [![Trust Registry](https://img.shields.io/badge/Trust_Registry-Live-00C853?style=for-the-badge)](https://agentaudit.dev)
12
+ [![License](https://img.shields.io/badge/License-AGPL_3.0-F9A825?style=for-the-badge)](LICENSE)
13
13
 
14
14
  </div>
15
15
 
16
16
  ---
17
17
 
18
- ## Getting Started
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
- There are two ways to use AgentAudit:
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
- ### Option A: MCP Server in your AI editor (recommended)
75
+ • Scanning Cursor ~/.cursor/mcp.json found 3 servers
23
76
 
24
- Add AgentAudit to Claude Desktop, Cursor, or Windsurf. **No API key needed** — your editor's agent runs audits using its own LLM.
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
- Then just ask your agent: *"Check which MCP servers I have installed and audit any unaudited ones."*
104
+ **VS Code** (`.vscode/mcp.json`):
38
105
 
39
- ### Option B: CLI
106
+ ```json
107
+ {
108
+ "servers": {
109
+ "agentaudit": {
110
+ "command": "npx",
111
+ "args": ["-y", "agentaudit"]
112
+ }
113
+ }
114
+ }
115
+ ```
40
116
 
41
- ```bash
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
- # 1. Discover your MCP servers
46
- npx agentaudit discover
119
+ ---
47
120
 
48
- # 2. Audit unaudited packages (needs an LLM API key)
49
- export ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY=sk-...
50
- npx agentaudit audit https://github.com/owner/repo
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
- # 3. (Optional) Register to upload reports to the public registry
53
- npx agentaudit setup
54
- ```
153
+ ---
55
154
 
56
- > **Note:** The `audit` command requires an LLM API key (`ANTHROPIC_API_KEY` or `OPENAI_API_KEY`) to analyze code. The `discover`, `scan`, and `check` commands work without one. If you don't have an API key, use `--export` to generate a markdown file you can paste into any LLM, or use AgentAudit as an MCP server (Option A) where no extra key is needed.
155
+ ## ⚖️ Quick Scan vs Deep Audit
57
156
 
58
- ### Quick example
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
- AgentAudit v3.3.0
64
- Security scanner for AI packages
168
+ ---
65
169
 
66
- • Scanning Claude Desktop ~/.claude/mcp.json found 2 servers
170
+ ## 🔌 MCP Server
67
171
 
68
- ├── fastmcp-demo npm:fastmcp
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
- Summary 2 servers across 1 config
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
- ✔ 1 audited
76
- ⚠ 1 not audited
181
+ ### Workflow
77
182
 
78
- To audit unaudited servers:
79
- agentaudit audit https://github.com/user/some-mcp-tool (my-tool)
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
- ## Commands
208
+ ## 🎯 What It Detects
85
209
 
86
- | Command | What it does | Speed |
87
- |---------|-------------|-------|
88
- | `discover` | Find local MCP servers + check registry | ⚡ instant |
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
- ### `scan` vs `audit`
214
+ **Core Security**
95
215
 
96
- | | `scan` | `audit` |
97
- |--|--------|---------|
98
- | **How** | Regex pattern matching | LLM 3-pass: UNDERSTAND → DETECT → CLASSIFY |
99
- | **Speed** | ~2 seconds | ~30 seconds |
100
- | **Depth** | Surface-level patterns | Semantic code understanding |
101
- | **Needs LLM API key** | No | Yes (`ANTHROPIC_API_KEY` or `OPENAI_API_KEY`) |
102
- | **Uploads to registry** | No | Yes (with `agentaudit setup`) |
216
+ ![Command Injection](https://img.shields.io/badge/-Command_Injection-E53935?style=flat-square)
217
+ ![Credential Theft](https://img.shields.io/badge/-Credential_Theft-E53935?style=flat-square)
218
+ ![Data Exfiltration](https://img.shields.io/badge/-Data_Exfiltration-E53935?style=flat-square)
219
+ ![SQL Injection](https://img.shields.io/badge/-SQL_Injection-E53935?style=flat-square)
220
+ ![Path Traversal](https://img.shields.io/badge/-Path_Traversal-E53935?style=flat-square)
221
+ ![Unsafe Deserialization](https://img.shields.io/badge/-Unsafe_Deserialization-E53935?style=flat-square)
103
222
 
104
- ### Examples
223
+ </td>
224
+ <td>
105
225
 
106
- ```bash
107
- # Discover all MCP servers on your machine
108
- npx agentaudit discover
226
+ **AI-Specific**
109
227
 
110
- # Quick static scan
111
- npx agentaudit scan https://github.com/owner/repo
228
+ ![Prompt Injection](https://img.shields.io/badge/-Prompt_Injection-7B1FA2?style=flat-square)
229
+ ![Jailbreak](https://img.shields.io/badge/-Jailbreak-7B1FA2?style=flat-square)
230
+ ![Agent Impersonation](https://img.shields.io/badge/-Agent_Impersonation-7B1FA2?style=flat-square)
231
+ ![Capability Escalation](https://img.shields.io/badge/-Capability_Escalation-7B1FA2?style=flat-square)
232
+ ![Context Pollution](https://img.shields.io/badge/-Context_Pollution-7B1FA2?style=flat-square)
233
+ ![Hidden Instructions](https://img.shields.io/badge/-Hidden_Instructions-7B1FA2?style=flat-square)
112
234
 
113
- # Deep LLM-powered audit
114
- export ANTHROPIC_API_KEY=sk-ant-...
115
- npx agentaudit audit https://github.com/owner/repo
235
+ </td>
236
+ </tr>
237
+ <tr>
238
+ <td>
116
239
 
117
- # Export code + audit prompt for manual LLM review
118
- npx agentaudit audit https://github.com/owner/repo --export
240
+ **MCP-Specific**
119
241
 
120
- # Registry lookup
121
- npx agentaudit check fastmcp
242
+ ![Tool Poisoning](https://img.shields.io/badge/-Tool_Poisoning-FF6F00?style=flat-square)
243
+ ![Desc Injection](https://img.shields.io/badge/-Desc_Injection-FF6F00?style=flat-square)
244
+ ![Resource Traversal](https://img.shields.io/badge/-Resource_Traversal-FF6F00?style=flat-square)
245
+ ![Unpinned npx](https://img.shields.io/badge/-Unpinned_npx-FF6F00?style=flat-square)
246
+ ![Broad Permissions](https://img.shields.io/badge/-Broad_Permissions-FF6F00?style=flat-square)
122
247
 
123
- # Register for an API key (free)
124
- npx agentaudit setup
125
- ```
248
+ </td>
249
+ <td>
250
+
251
+ **Persistence & Obfuscation**
252
+
253
+ ![Crontab Mod](https://img.shields.io/badge/-Crontab_Mod-455A64?style=flat-square)
254
+ ![Shell RC Inject](https://img.shields.io/badge/-Shell_RC_Inject-455A64?style=flat-square)
255
+ ![Git Hook Abuse](https://img.shields.io/badge/-Git_Hook_Abuse-455A64?style=flat-square)
256
+ ![Zero-Width Chars](https://img.shields.io/badge/-Zero--Width_Chars-455A64?style=flat-square)
257
+ ![Base64 Exec](https://img.shields.io/badge/-Base64_Exec-455A64?style=flat-square)
258
+ ![ANSI Escape](https://img.shields.io/badge/-ANSI_Escape-455A64?style=flat-square)
259
+
260
+ </td>
261
+ </tr>
262
+ </table>
126
263
 
127
264
  ---
128
265
 
129
- ## MCP Server
266
+ ## 🧠 How the 3-Pass Audit Works
130
267
 
131
- Add AgentAudit to your AI editor. Your agent gets 4 tools:
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
- | MCP Tool | Description |
134
- |----------|-------------|
135
- | `discover_servers` | Find all locally installed MCP servers, check registry status |
136
- | `audit_package` | Clone a repo return source code + audit prompt you analyze `submit_report` |
137
- | `submit_report` | Upload your audit report to [agentaudit.dev](https://agentaudit.dev) |
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
- ### Setup
276
+ **Why 3 passes?** Single-pass analysis is the #1 cause of false positives. By separating understanding → detection → classification:
141
277
 
142
- One-line config works with `npx`, no manual clone needed:
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
- **Claude Desktop** (`~/.claude/mcp.json`):
145
- ```json
146
- {
147
- "mcpServers": {
148
- "agentaudit": {
149
- "command": "npx",
150
- "args": ["-y", "agentaudit"]
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
- **Cursor** (`.cursor/mcp.json`):
157
- ```json
158
- {
159
- "mcpServers": {
160
- "agentaudit": {
161
- "command": "npx",
162
- "args": ["-y", "agentaudit"]
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
- "mcpServers": {
172
- "agentaudit": {
173
- "command": "npx",
174
- "args": ["-y", "agentaudit"]
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
- ### How an audit works via MCP
181
-
332
+ ```bash
333
+ # Registry lookup with JSON
334
+ agentaudit lookup fastmcp --json
182
335
  ```
183
- You: "Audit the blender-mcp server"
184
-
185
- Agent calls discover_servers → finds blender-mcp
186
-
187
- Agent calls check_package("blender-mcp") → not in registry
188
-
189
- Agent calls audit_package("https://github.com/user/blender-mcp")
190
-
191
- MCP server clones repo, returns source code + audit methodology
192
-
193
- Agent's LLM analyzes code (3-pass: UNDERSTAND → DETECT → CLASSIFY)
194
-
195
- Agent calls submit_report(findings_json)
196
-
197
- Report published at agentaudit.dev/skills/blender-mcp
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
- ### Authentication
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
- Run `npx agentaudit setup` once. Both CLI and MCP server find credentials automatically:
362
+ ---
203
363
 
204
- 1. `AGENTAUDIT_API_KEY` environment variable
205
- 2. `~/.config/agentaudit/credentials.json` (created by `setup`)
364
+ ## 📦 Requirements
206
365
 
207
- **`discover`, `scan`, and `check` work without a key.** Only `audit`/`submit_report` need one.
366
+ - **Node.js** 18.0.0
367
+ - **Git** (for cloning repositories during scan/audit)
208
368
 
209
369
  ---
210
370
 
211
- ## What it detects
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
- ### Static scan (`scan`)
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
- - 🔴 Prompt injection & tool poisoning
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
- ### Deep audit (`audit` / MCP `audit_package`)
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
- Everything above, plus:
396
+ ### Can I use it offline?
228
397
 
229
- - 🔴 Multi-file attack chains (credential harvest exfiltration)
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
- 50+ detection patterns across 8 categories. [Full pattern list →](https://github.com/starbuck100/agentaudit-skill)
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
- ## Requirements
410
+ ## 🔗 Related Links
241
411
 
242
- - **Node.js 18+**
243
- - **Git** (for cloning repos)
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
- ## Related
420
+ ## 📄 License
248
421
 
249
- - **[agentaudit.dev](https://agentaudit.dev)**Trust registry with 400+ audit reports
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
- ## License
426
+ <div align="center">
255
427
 
256
- [AGPL-3.0](LICENSE)
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 — Beautiful terminal output for security audits
3
+ * AgentAudit CLI — Security scanner for AI packages
4
4
  *
5
5
  * Usage:
6
- * agentaudit setup Interactive setup (register + API key)
7
- * agentaudit scan <repo-url> [repo-url...] Scan repositories
8
- * agentaudit check <package-name> Look up in registry
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
- * Examples:
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
- // ── ANSI Colors ──────────────────────────────────────────
27
+ // ── Global flags (set in main before command routing) ────
28
+ let jsonMode = false;
29
+ let quietMode = false;
28
30
 
29
- const c = {
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',
@@ -145,7 +155,7 @@ function multiSelect(items, { title = 'Select items', hint = 'Space=toggle ↑
145
155
  process.stdin.pause();
146
156
  process.stdin.removeListener('data', onData);
147
157
  console.log();
148
- process.exit(0);
158
+ process.exitCode = 0; return;
149
159
  }
150
160
 
151
161
  // Enter
@@ -281,6 +291,7 @@ function getVersion() {
281
291
  }
282
292
 
283
293
  function banner() {
294
+ if (quietMode || jsonMode) return;
284
295
  console.log();
285
296
  console.log(` ${c.bold}${c.cyan}AgentAudit${c.reset} ${c.dim}v${getVersion()}${c.reset}`);
286
297
  console.log(` ${c.dim}Security scanner for AI packages${c.reset}`);
@@ -576,8 +587,30 @@ async function checkRegistry(slug) {
576
587
  // ── Print results ───────────────────────────────────────
577
588
 
578
589
  function printScanResult(url, info, files, findings, registryData, duration) {
590
+ if (jsonMode) return; // JSON mode handles output separately
591
+
579
592
  const slug = slugFromUrl(url);
580
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
+
581
614
  // Header
582
615
  console.log(`${icons.scan} ${c.bold}${slug}${c.reset} ${c.dim}${url}${c.reset}`);
583
616
  console.log(`${icons.pipe} ${c.dim}${info.language} ${info.type}${c.reset} ${c.dim}${files.length} files scanned in ${duration}${c.reset}`);
@@ -676,7 +709,7 @@ async function scanRepo(url) {
676
709
  const start = Date.now();
677
710
  const slug = slugFromUrl(url);
678
711
 
679
- 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}`);
680
713
 
681
714
  // Clone
682
715
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agentaudit-'));
@@ -687,10 +720,12 @@ async function scanRepo(url) {
687
720
  stdio: 'pipe',
688
721
  });
689
722
  } catch (err) {
690
- process.stdout.write(` ${c.red}✖ clone failed${c.reset}\n`);
691
- const msg = err.stderr?.toString().trim() || err.message?.split('\n')[0] || '';
692
- if (msg) console.log(` ${c.dim}${msg}${c.reset}`);
693
- console.log(` ${c.dim}Make sure git is installed and the URL is accessible.${c.reset}`);
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
+ }
694
729
  return null;
695
730
  }
696
731
 
@@ -711,11 +746,13 @@ async function scanRepo(url) {
711
746
 
712
747
  const duration = elapsed(start);
713
748
 
714
- // Clear the "Scanning..." line
715
- process.stdout.write('\r\x1b[K');
716
-
717
- // Print result
718
- printScanResult(url, info, files, findings, registryData, duration);
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
+ }
719
756
 
720
757
  return { slug, url, info, files: files.length, findings, registryData, duration };
721
758
  }
@@ -941,8 +978,10 @@ async function discoverCommand(options = {}) {
941
978
  const autoScan = options.scan || false;
942
979
  const interactiveAudit = options.audit || false;
943
980
 
944
- console.log(` ${c.bold}Discovering local MCP servers...${c.reset}`);
945
- console.log();
981
+ if (!jsonMode) {
982
+ console.log(` ${c.bold}Discovering MCP servers in your AI editors...${c.reset}`);
983
+ console.log();
984
+ }
946
985
 
947
986
  const configs = findMcpConfigs();
948
987
 
@@ -1096,10 +1135,10 @@ async function discoverCommand(options = {}) {
1096
1135
  console.log();
1097
1136
  if (serversWithFindings > 0) {
1098
1137
  console.log(` ${c.yellow}${serversWithFindings}/${scanResults.length} server${scanResults.length !== 1 ? 's' : ''} with findings (${totalFindings} total)${c.reset}`);
1099
- console.log(` ${c.dim}Run ${c.cyan}agentaudit audit <url>${c.dim} for deep LLM analysis on flagged servers${c.reset}`);
1138
+ console.log(` ${c.dim}Run ${c.cyan}agentaudit scan <url> --deep${c.dim} for deep LLM analysis on flagged servers${c.reset}`);
1100
1139
  } else {
1101
1140
  console.log(` ${c.green}All servers passed quick scan${c.reset}`);
1102
- console.log(` ${c.dim}Run ${c.cyan}agentaudit audit <url>${c.dim} for thorough LLM-powered analysis${c.reset}`);
1141
+ console.log(` ${c.dim}Run ${c.cyan}agentaudit scan <url> --deep${c.dim} for thorough LLM-powered analysis${c.reset}`);
1103
1142
  }
1104
1143
  console.log();
1105
1144
  }
@@ -1157,8 +1196,13 @@ async function discoverCommand(options = {}) {
1157
1196
  console.log(` ${c.cyan}agentaudit audit <source-url>${c.reset}`);
1158
1197
  }
1159
1198
  console.log();
1160
- console.log(` ${c.dim}Or run ${c.cyan}agentaudit discover --scan${c.dim} to quick-scan all servers${c.reset}`);
1161
- console.log(` ${c.dim}Or run ${c.cyan}agentaudit discover --audit${c.dim} to select & deep-audit interactively${c.reset}`);
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}`);
1162
1206
  console.log();
1163
1207
  }
1164
1208
  }
@@ -1416,57 +1460,83 @@ async function auditRepo(url) {
1416
1460
  // ── Check command ───────────────────────────────────────
1417
1461
 
1418
1462
  async function checkPackage(name) {
1419
- console.log(`${icons.info} Looking up ${c.bold}${name}${c.reset} in registry...`);
1420
- console.log();
1463
+ if (!jsonMode) {
1464
+ console.log(`${icons.info} Looking up ${c.bold}${name}${c.reset} in registry...`);
1465
+ console.log();
1466
+ }
1421
1467
 
1422
1468
  const data = await checkRegistry(name);
1423
1469
  if (!data) {
1424
- console.log(` ${c.yellow}Not found${c.reset} — package "${name}" hasn't been audited yet.`);
1425
- console.log(` ${c.dim}Run: agentaudit audit <repo-url> for a deep LLM audit${c.reset}`);
1426
- return;
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;
1427
1475
  }
1428
1476
 
1429
- const riskScore = data.risk_score ?? data.latest_risk_score ?? 0;
1430
- console.log(` ${c.bold}${name}${c.reset} ${riskBadge(riskScore)}`);
1431
- console.log(` ${c.dim}Risk Score: ${riskScore}/100${c.reset}`);
1432
- if (data.source_url) console.log(` ${c.dim}Source: ${data.source_url}${c.reset}`);
1433
- console.log(` ${c.dim}Registry: ${REGISTRY_URL}/skills/${name}${c.reset}`);
1434
- if (data.has_official_audit) console.log(` ${c.green} Officially audited${c.reset}`);
1435
- console.log();
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;
1436
1487
  }
1437
1488
 
1438
1489
  // ── Main ────────────────────────────────────────────────
1439
1490
 
1440
1491
  async function main() {
1441
- const args = process.argv.slice(2);
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));
1442
1502
 
1443
1503
  if (args[0] === '-v' || args[0] === '--version') {
1444
1504
  console.log(`agentaudit ${getVersion()}`);
1445
- process.exit(0);
1505
+ process.exitCode = 0; return;
1446
1506
  }
1447
1507
 
1448
- if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
1508
+ if (args[0] === '--help' || args[0] === '-h') {
1449
1509
  banner();
1450
1510
  console.log(` ${c.bold}Commands:${c.reset}`);
1451
1511
  console.log();
1452
- console.log(` ${c.cyan}agentaudit discover${c.reset} Find local MCP servers + check registry`);
1453
- console.log(` ${c.cyan}agentaudit discover --scan${c.reset} Discover + auto-scan all servers`);
1454
- console.log(` ${c.cyan}agentaudit discover --audit${c.reset} Discover + select servers to deep-audit`);
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`);
1455
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)`);
1456
1518
  console.log(` ${c.cyan}agentaudit audit${c.reset} <url> [url...] Deep LLM-powered security audit`);
1457
- console.log(` ${c.cyan}agentaudit check${c.reset} <name> Look up package in registry`);
1519
+ console.log(` ${c.cyan}agentaudit lookup${c.reset} <name> Look up package in registry`);
1458
1520
  console.log(` ${c.cyan}agentaudit setup${c.reset} Register + configure API key`);
1459
1521
  console.log();
1460
- console.log(` ${c.bold}scan${c.reset} vs ${c.bold}audit${c.reset}:`);
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}:`);
1461
1528
  console.log(` ${c.dim}scan = fast regex-based static analysis (~2s)${c.reset}`);
1462
1529
  console.log(` ${c.dim}audit = deep LLM analysis with 3-pass methodology (~30s)${c.reset}`);
1463
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();
1464
1534
  console.log(` ${c.bold}Examples:${c.reset}`);
1465
- console.log(` agentaudit discover`);
1466
- console.log(` agentaudit discover --scan`);
1535
+ console.log(` agentaudit`);
1536
+ console.log(` agentaudit discover --quick`);
1467
1537
  console.log(` agentaudit scan https://github.com/owner/repo`);
1468
1538
  console.log(` agentaudit audit https://github.com/owner/repo`);
1469
- console.log(` agentaudit check fastmcp`);
1539
+ console.log(` agentaudit lookup fastmcp --json`);
1470
1540
  console.log();
1471
1541
  console.log(` ${c.bold}For deep audits,${c.reset} set an LLM API key:`);
1472
1542
  if (process.platform === 'win32') {
@@ -1481,10 +1551,11 @@ async function main() {
1481
1551
  console.log(` ${c.dim}Add to your MCP config:${c.reset}`);
1482
1552
  console.log(` ${c.dim}{ "agentaudit": { "command": "npx", "args": ["-y", "agentaudit"] } }${c.reset}`);
1483
1553
  console.log();
1484
- process.exit(0);
1554
+ process.exitCode = 0; return;
1485
1555
  }
1486
1556
 
1487
- const command = args[0];
1557
+ // Default no-arg → discover
1558
+ const command = args.length === 0 ? 'discover' : args[0];
1488
1559
  const targets = args.slice(1);
1489
1560
 
1490
1561
  banner();
@@ -1495,38 +1566,83 @@ async function main() {
1495
1566
  }
1496
1567
 
1497
1568
  if (command === 'discover') {
1498
- const scanFlag = targets.includes('--scan') || targets.includes('-s');
1499
- const auditFlag = targets.includes('--audit') || targets.includes('-a');
1569
+ const scanFlag = targets.includes('--quick') || targets.includes('--scan') || targets.includes('-s');
1570
+ const auditFlag = targets.includes('--deep') || targets.includes('--audit') || targets.includes('-a');
1500
1571
  await discoverCommand({ scan: scanFlag, audit: auditFlag });
1501
1572
  return;
1502
1573
  }
1503
1574
 
1504
- if (command === 'check') {
1505
- if (targets.length === 0) {
1575
+ if (command === 'lookup' || command === 'check') {
1576
+ const names = targets.filter(t => !t.startsWith('--'));
1577
+ if (names.length === 0) {
1506
1578
  console.log(` ${c.red}Error: package name required${c.reset}`);
1507
- process.exit(1);
1579
+ process.exitCode = 2;
1580
+ return;
1581
+ }
1582
+ const results = [];
1583
+ for (const t of names) {
1584
+ const data = await checkPackage(t);
1585
+ results.push(data);
1508
1586
  }
1509
- for (const t of targets) await checkPackage(t);
1587
+ if (jsonMode) {
1588
+ console.log(JSON.stringify(results.length === 1 ? (results[0] || { error: 'not_found' }) : results, null, 2));
1589
+ }
1590
+ process.exitCode = 0; return;
1510
1591
  return;
1511
1592
  }
1512
1593
 
1513
1594
  if (command === 'scan') {
1514
- if (targets.length === 0) {
1595
+ const deepFlag = targets.includes('--deep');
1596
+ const urls = targets.filter(t => !t.startsWith('--'));
1597
+ if (urls.length === 0) {
1515
1598
  console.log(` ${c.red}Error: at least one repository URL required${c.reset}`);
1516
1599
  console.log(` ${c.dim}Tip: use ${c.cyan}agentaudit discover${c.dim} to find & check locally installed MCP servers${c.reset}`);
1517
1600
  console.log(` ${c.dim}Tip: use ${c.cyan}agentaudit audit <url>${c.dim} for a deep LLM-powered audit${c.reset}`);
1518
- process.exit(1);
1601
+ process.exitCode = 2;
1602
+ return;
1603
+ }
1604
+
1605
+ // --deep redirects to audit flow
1606
+ if (deepFlag) {
1607
+ let hasFindings = false;
1608
+ for (const url of urls) {
1609
+ const report = await auditRepo(url);
1610
+ if (report?.findings?.length > 0) hasFindings = true;
1611
+ }
1612
+ process.exitCode = hasFindings ? 1 : 0;
1613
+ return;
1519
1614
  }
1520
1615
 
1521
1616
  const results = [];
1522
- for (const url of targets) {
1617
+ let hadErrors = false;
1618
+ for (const url of urls) {
1523
1619
  const result = await scanRepo(url);
1524
1620
  if (result) results.push(result);
1621
+ else hadErrors = true;
1525
1622
  }
1526
1623
 
1527
- if (results.length > 1) {
1624
+ if (jsonMode) {
1625
+ const jsonOut = results.map(r => ({
1626
+ slug: r.slug,
1627
+ url: r.url,
1628
+ findings: r.findings.map(f => ({
1629
+ severity: f.severity,
1630
+ title: f.title,
1631
+ file: f.file,
1632
+ line: f.line,
1633
+ snippet: f.snippet,
1634
+ })),
1635
+ fileCount: r.files,
1636
+ duration: r.duration,
1637
+ }));
1638
+ console.log(JSON.stringify(jsonOut.length === 1 ? jsonOut[0] : jsonOut, null, 2));
1639
+ } else if (results.length > 1) {
1528
1640
  printSummary(results);
1529
1641
  }
1642
+
1643
+ if (hadErrors && results.length === 0) { process.exitCode = 2; return; }
1644
+ const totalFindings = results.reduce((sum, r) => sum + r.findings.length, 0);
1645
+ process.exitCode = totalFindings > 0 ? 1 : 0;
1530
1646
  return;
1531
1647
  }
1532
1648
 
@@ -1534,21 +1650,25 @@ async function main() {
1534
1650
  const urls = targets.filter(t => !t.startsWith('--'));
1535
1651
  if (urls.length === 0) {
1536
1652
  console.log(` ${c.red}Error: at least one repository URL required${c.reset}`);
1537
- process.exit(1);
1653
+ process.exitCode = 2;
1654
+ return;
1538
1655
  }
1539
1656
 
1657
+ let hasFindings = false;
1540
1658
  for (const url of urls) {
1541
- await auditRepo(url);
1659
+ const report = await auditRepo(url);
1660
+ if (report?.findings?.length > 0) hasFindings = true;
1542
1661
  }
1662
+ process.exitCode = hasFindings ? 1 : 0;
1543
1663
  return;
1544
1664
  }
1545
1665
 
1546
1666
  console.log(` ${c.red}Unknown command: ${command}${c.reset}`);
1547
1667
  console.log(` ${c.dim}Run agentaudit --help for usage${c.reset}`);
1548
- process.exit(1);
1668
+ process.exitCode = 2;
1549
1669
  }
1550
1670
 
1551
1671
  main().catch(err => {
1552
1672
  console.error(`${c.red}Error: ${err.message}${c.reset}`);
1553
- process.exit(1);
1673
+ process.exitCode = 2;
1554
1674
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentaudit",
3
- "version": "3.8.0",
3
+ "version": "3.9.1",
4
4
  "description": "Security scanner for AI packages — MCP server + CLI",
5
5
  "type": "module",
6
6
  "bin": {