agentaudit 3.3.0 → 3.4.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 +42 -12
- package/cli.mjs +93 -14
- package/index.mjs +42 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,16 +15,52 @@ MCP server for agents + standalone CLI for humans.
|
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## Getting Started
|
|
19
|
+
|
|
20
|
+
There are two ways to use AgentAudit:
|
|
21
|
+
|
|
22
|
+
### Option A: MCP Server in your AI editor (recommended)
|
|
23
|
+
|
|
24
|
+
Add AgentAudit to Claude Desktop, Cursor, or Windsurf. **No API key needed** — your editor's agent runs audits using its own LLM.
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"agentaudit": {
|
|
30
|
+
"command": "npx",
|
|
31
|
+
"args": ["-y", "agentaudit"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Then just ask your agent: *"Check which MCP servers I have installed and audit any unaudited ones."*
|
|
38
|
+
|
|
39
|
+
### Option B: CLI
|
|
19
40
|
|
|
20
41
|
```bash
|
|
42
|
+
# Install
|
|
43
|
+
npm install -g agentaudit # or use npx agentaudit <command>
|
|
44
|
+
|
|
45
|
+
# 1. Discover your MCP servers
|
|
21
46
|
npx agentaudit discover
|
|
47
|
+
|
|
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
|
|
51
|
+
|
|
52
|
+
# 3. (Optional) Register to upload reports to the public registry
|
|
53
|
+
npx agentaudit setup
|
|
22
54
|
```
|
|
23
55
|
|
|
24
|
-
|
|
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.
|
|
57
|
+
|
|
58
|
+
### Quick example
|
|
25
59
|
|
|
26
60
|
```
|
|
27
|
-
|
|
61
|
+
$ npx agentaudit discover
|
|
62
|
+
|
|
63
|
+
AgentAudit v3.3.0
|
|
28
64
|
Security scanner for AI packages
|
|
29
65
|
|
|
30
66
|
• Scanning Claude Desktop ~/.claude/mcp.json found 2 servers
|
|
@@ -32,21 +68,15 @@ AgentAudit v3.2.0
|
|
|
32
68
|
├── fastmcp-demo npm:fastmcp
|
|
33
69
|
│ SAFE Risk 0 ✔ official https://agentaudit.dev/skills/fastmcp
|
|
34
70
|
└── my-tool npm:some-mcp-tool
|
|
35
|
-
⚠ not audited Run: agentaudit audit
|
|
71
|
+
⚠ not audited Run: agentaudit audit https://github.com/user/some-mcp-tool
|
|
36
72
|
|
|
37
|
-
────────────────────────────────────────────────────────
|
|
38
73
|
Summary 2 servers across 1 config
|
|
39
74
|
|
|
40
75
|
✔ 1 audited
|
|
41
76
|
⚠ 1 not audited
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## Install
|
|
45
77
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
# or use directly:
|
|
49
|
-
npx agentaudit <command>
|
|
78
|
+
To audit unaudited servers:
|
|
79
|
+
agentaudit audit https://github.com/user/some-mcp-tool (my-tool)
|
|
50
80
|
```
|
|
51
81
|
|
|
52
82
|
---
|
package/cli.mjs
CHANGED
|
@@ -157,17 +157,26 @@ async function setupCommand() {
|
|
|
157
157
|
|
|
158
158
|
console.log();
|
|
159
159
|
console.log(` ${c.bold}Ready!${c.reset} You can now:`);
|
|
160
|
-
console.log(` ${c.dim}•${c.reset}
|
|
161
|
-
console.log(` ${c.dim}•${c.reset}
|
|
160
|
+
console.log(` ${c.dim}•${c.reset} Discover servers: ${c.cyan}agentaudit discover${c.reset}`);
|
|
161
|
+
console.log(` ${c.dim}•${c.reset} Audit packages: ${c.cyan}agentaudit audit <repo-url>${c.reset} ${c.dim}(deep LLM analysis)${c.reset}`);
|
|
162
|
+
console.log(` ${c.dim}•${c.reset} Quick scan: ${c.cyan}agentaudit scan <repo-url>${c.reset} ${c.dim}(regex-based)${c.reset}`);
|
|
163
|
+
console.log(` ${c.dim}•${c.reset} Check registry: ${c.cyan}agentaudit check <name>${c.reset}`);
|
|
162
164
|
console.log(` ${c.dim}•${c.reset} Submit reports via MCP in Claude/Cursor/Windsurf`);
|
|
163
165
|
console.log();
|
|
164
166
|
}
|
|
165
167
|
|
|
166
168
|
// ── Helpers ──────────────────────────────────────────────
|
|
167
169
|
|
|
170
|
+
function getVersion() {
|
|
171
|
+
try {
|
|
172
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
|
|
173
|
+
return pkg.version || '0.0.0';
|
|
174
|
+
} catch { return '0.0.0'; }
|
|
175
|
+
}
|
|
176
|
+
|
|
168
177
|
function banner() {
|
|
169
178
|
console.log();
|
|
170
|
-
console.log(` ${c.bold}${c.cyan}AgentAudit${c.reset} ${c.dim}
|
|
179
|
+
console.log(` ${c.bold}${c.cyan}AgentAudit${c.reset} ${c.dim}v${getVersion()}${c.reset}`);
|
|
171
180
|
console.log(` ${c.dim}Security scanner for AI packages${c.reset}`);
|
|
172
181
|
console.log();
|
|
173
182
|
}
|
|
@@ -705,6 +714,48 @@ function serverSlug(server) {
|
|
|
705
714
|
return server.name.toLowerCase().replace(/[^a-z0-9-]/gi, '-');
|
|
706
715
|
}
|
|
707
716
|
|
|
717
|
+
async function resolveSourceUrl(server) {
|
|
718
|
+
// Already have it
|
|
719
|
+
if (server.sourceUrl) return server.sourceUrl;
|
|
720
|
+
|
|
721
|
+
// Try npm registry
|
|
722
|
+
if (server.npmPackage) {
|
|
723
|
+
try {
|
|
724
|
+
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(server.npmPackage)}`, {
|
|
725
|
+
signal: AbortSignal.timeout(5000),
|
|
726
|
+
});
|
|
727
|
+
if (res.ok) {
|
|
728
|
+
const data = await res.json();
|
|
729
|
+
let repoUrl = data.repository?.url;
|
|
730
|
+
if (repoUrl) {
|
|
731
|
+
repoUrl = repoUrl.replace(/^git\+/, '').replace(/\.git$/, '').replace(/^ssh:\/\/git@github\.com/, 'https://github.com');
|
|
732
|
+
if (repoUrl.startsWith('http')) return repoUrl;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
} catch {}
|
|
736
|
+
// Fallback: npm page
|
|
737
|
+
return `https://www.npmjs.com/package/${server.npmPackage}`;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Try PyPI
|
|
741
|
+
if (server.pyPackage) {
|
|
742
|
+
try {
|
|
743
|
+
const res = await fetch(`https://pypi.org/pypi/${encodeURIComponent(server.pyPackage)}/json`, {
|
|
744
|
+
signal: AbortSignal.timeout(5000),
|
|
745
|
+
});
|
|
746
|
+
if (res.ok) {
|
|
747
|
+
const data = await res.json();
|
|
748
|
+
const urls = data.info?.project_urls || {};
|
|
749
|
+
const source = urls.Source || urls.Repository || urls.Homepage || urls['Source Code'] || data.info?.home_page;
|
|
750
|
+
if (source && source.startsWith('http')) return source;
|
|
751
|
+
}
|
|
752
|
+
} catch {}
|
|
753
|
+
return `https://pypi.org/project/${server.pyPackage}/`;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
|
|
708
759
|
async function discoverCommand() {
|
|
709
760
|
console.log(` ${c.bold}Discovering local MCP servers...${c.reset}`);
|
|
710
761
|
console.log();
|
|
@@ -728,6 +779,7 @@ async function discoverCommand() {
|
|
|
728
779
|
let checkedServers = 0;
|
|
729
780
|
let auditedServers = 0;
|
|
730
781
|
let unauditedServers = 0;
|
|
782
|
+
const unauditedWithUrls = [];
|
|
731
783
|
|
|
732
784
|
for (const config of configs) {
|
|
733
785
|
const servers = extractServersFromConfig(config.content);
|
|
@@ -779,11 +831,18 @@ async function discoverCommand() {
|
|
|
779
831
|
console.log(`${pipe} ${riskBadge(riskScore)} Risk ${riskScore} ${hasOfficial ? `${c.green}✔ official${c.reset} ` : ''}${c.dim}${REGISTRY_URL}/skills/${slug}${c.reset}`);
|
|
780
832
|
} else {
|
|
781
833
|
unauditedServers++;
|
|
834
|
+
// Resolve source URL
|
|
835
|
+
const resolvedUrl = await resolveSourceUrl(server);
|
|
782
836
|
console.log(`${branch} ${c.bold}${server.name}${c.reset} ${sourceLabel}`);
|
|
783
|
-
|
|
837
|
+
if (resolvedUrl) {
|
|
838
|
+
console.log(`${pipe} ${c.yellow}⚠ not audited${c.reset} ${c.dim}Run: ${c.cyan}agentaudit audit ${resolvedUrl}${c.reset}`);
|
|
839
|
+
unauditedWithUrls.push({ name: server.name, sourceUrl: resolvedUrl });
|
|
840
|
+
} else {
|
|
841
|
+
console.log(`${pipe} ${c.yellow}⚠ not audited${c.reset} ${c.dim}Source URL unknown — check the package's GitHub/npm page${c.reset}`);
|
|
842
|
+
}
|
|
784
843
|
}
|
|
785
844
|
|
|
786
|
-
if (server.sourceUrl) {
|
|
845
|
+
if (server.sourceUrl && !server.sourceUrl.includes('npmjs.com')) {
|
|
787
846
|
console.log(`${pipe} ${c.dim}source: ${server.sourceUrl}${c.reset}`);
|
|
788
847
|
}
|
|
789
848
|
}
|
|
@@ -800,8 +859,15 @@ async function discoverCommand() {
|
|
|
800
859
|
console.log();
|
|
801
860
|
|
|
802
861
|
if (unauditedServers > 0) {
|
|
803
|
-
|
|
804
|
-
|
|
862
|
+
if (unauditedWithUrls.length > 0) {
|
|
863
|
+
console.log(` ${c.dim}To audit unaudited servers:${c.reset}`);
|
|
864
|
+
for (const { name, sourceUrl } of unauditedWithUrls) {
|
|
865
|
+
console.log(` ${c.cyan}agentaudit audit ${sourceUrl}${c.reset} ${c.dim}(${name})${c.reset}`);
|
|
866
|
+
}
|
|
867
|
+
} else {
|
|
868
|
+
console.log(` ${c.dim}To audit unaudited servers, run:${c.reset}`);
|
|
869
|
+
console.log(` ${c.cyan}agentaudit audit <source-url>${c.reset}`);
|
|
870
|
+
}
|
|
805
871
|
console.log();
|
|
806
872
|
}
|
|
807
873
|
}
|
|
@@ -857,15 +923,28 @@ async function auditRepo(url) {
|
|
|
857
923
|
const openaiKey = process.env.OPENAI_API_KEY;
|
|
858
924
|
|
|
859
925
|
if (!anthropicKey && !openaiKey) {
|
|
860
|
-
// No LLM API key —
|
|
926
|
+
// No LLM API key — clear explanation
|
|
927
|
+
console.log();
|
|
928
|
+
console.log(` ${c.yellow}No LLM API key found.${c.reset} The ${c.bold}audit${c.reset} command needs an LLM to analyze code.`);
|
|
929
|
+
console.log();
|
|
930
|
+
console.log(` ${c.bold}Option 1: Set an API key${c.reset}`);
|
|
931
|
+
console.log(` Supported keys: ${c.cyan}ANTHROPIC_API_KEY${c.reset} or ${c.cyan}OPENAI_API_KEY${c.reset}`);
|
|
932
|
+
console.log();
|
|
933
|
+
console.log(` ${c.dim}# Linux / macOS:${c.reset}`);
|
|
934
|
+
console.log(` ${c.dim}export ANTHROPIC_API_KEY=sk-ant-...${c.reset}`);
|
|
935
|
+
console.log(` ${c.dim}export OPENAI_API_KEY=sk-...${c.reset}`);
|
|
936
|
+
console.log();
|
|
937
|
+
console.log(` ${c.dim}# Windows (PowerShell):${c.reset}`);
|
|
938
|
+
console.log(` ${c.dim}$env:ANTHROPIC_API_KEY = "sk-ant-..."${c.reset}`);
|
|
939
|
+
console.log(` ${c.dim}$env:OPENAI_API_KEY = "sk-..."${c.reset}`);
|
|
861
940
|
console.log();
|
|
862
|
-
console.log(` ${c.
|
|
863
|
-
console.log(`
|
|
864
|
-
console.log(`
|
|
941
|
+
console.log(` ${c.bold}Option 2: Export for manual review${c.reset}`);
|
|
942
|
+
console.log(` ${c.cyan}agentaudit audit ${url} --export${c.reset}`);
|
|
943
|
+
console.log(` ${c.dim}Creates a markdown file you can paste into any LLM (Claude, ChatGPT, etc.)${c.reset}`);
|
|
865
944
|
console.log();
|
|
866
|
-
console.log(` ${c.bold}
|
|
867
|
-
console.log(`
|
|
868
|
-
console.log(`
|
|
945
|
+
console.log(` ${c.bold}Option 3: Use MCP in Claude/Cursor/Windsurf (no API key needed)${c.reset}`);
|
|
946
|
+
console.log(` ${c.dim}Add AgentAudit as MCP server — your editor's agent runs the audit using its own LLM.${c.reset}`);
|
|
947
|
+
console.log(` ${c.dim}Config: { "mcpServers": { "agentaudit": { "command": "npx", "args": ["-y", "agentaudit"] } } }${c.reset}`);
|
|
869
948
|
console.log();
|
|
870
949
|
|
|
871
950
|
// Check if --export flag
|
package/index.mjs
CHANGED
|
@@ -190,6 +190,40 @@ function discoverMcpServers() {
|
|
|
190
190
|
return results;
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
async function resolveSourceUrl(server) {
|
|
194
|
+
if (server.npm_package) {
|
|
195
|
+
try {
|
|
196
|
+
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(server.npm_package)}`, {
|
|
197
|
+
signal: AbortSignal.timeout(5000),
|
|
198
|
+
});
|
|
199
|
+
if (res.ok) {
|
|
200
|
+
const data = await res.json();
|
|
201
|
+
let repoUrl = data.repository?.url;
|
|
202
|
+
if (repoUrl) {
|
|
203
|
+
repoUrl = repoUrl.replace(/^git\+/, '').replace(/\.git$/, '').replace(/^ssh:\/\/git@github\.com/, 'https://github.com');
|
|
204
|
+
if (repoUrl.startsWith('http')) return repoUrl;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
} catch {}
|
|
208
|
+
return `https://www.npmjs.com/package/${server.npm_package}`;
|
|
209
|
+
}
|
|
210
|
+
if (server.pip_package) {
|
|
211
|
+
try {
|
|
212
|
+
const res = await fetch(`https://pypi.org/pypi/${encodeURIComponent(server.pip_package)}/json`, {
|
|
213
|
+
signal: AbortSignal.timeout(5000),
|
|
214
|
+
});
|
|
215
|
+
if (res.ok) {
|
|
216
|
+
const data = await res.json();
|
|
217
|
+
const urls = data.info?.project_urls || {};
|
|
218
|
+
const source = urls.Source || urls.Repository || urls.Homepage || urls['Source Code'] || data.info?.home_page;
|
|
219
|
+
if (source && source.startsWith('http')) return source;
|
|
220
|
+
}
|
|
221
|
+
} catch {}
|
|
222
|
+
return `https://pypi.org/project/${server.pip_package}/`;
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
193
227
|
async function checkRegistry(slug) {
|
|
194
228
|
try {
|
|
195
229
|
const res = await fetch(`${REGISTRY_URL}/api/skills/${encodeURIComponent(slug)}`, {
|
|
@@ -310,8 +344,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
310
344
|
text += `- **Registry: ✅ Audited** — Risk ${risk}/100${official}\n`;
|
|
311
345
|
text += `- Report: ${REGISTRY_URL}/skills/${slug}\n`;
|
|
312
346
|
} else {
|
|
347
|
+
const sourceUrl = await resolveSourceUrl(srv);
|
|
313
348
|
text += `- **Registry: ⚠️ Not audited** — no audit report found\n`;
|
|
314
|
-
|
|
349
|
+
if (sourceUrl) {
|
|
350
|
+
text += `- Source: ${sourceUrl}\n`;
|
|
351
|
+
text += `- To audit: call \`audit_package\` with source_url \`${sourceUrl}\`\n`;
|
|
352
|
+
} else {
|
|
353
|
+
text += `- Source URL unknown — check the package's GitHub/npm page\n`;
|
|
354
|
+
text += `- To audit: find the source URL, then call \`audit_package\`\n`;
|
|
355
|
+
}
|
|
315
356
|
}
|
|
316
357
|
}
|
|
317
358
|
text += `\n`;
|