agentaudit 3.1.0 → 3.2.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 +26 -7
- package/cli.mjs +258 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -169,22 +169,41 @@ The MCP server finds credentials automatically from:
|
|
|
169
169
|
## CLI Reference
|
|
170
170
|
|
|
171
171
|
```
|
|
172
|
-
agentaudit
|
|
173
|
-
agentaudit scan <url> [url...]
|
|
172
|
+
agentaudit discover Find local MCP servers + check registry
|
|
173
|
+
agentaudit scan <url> [url...] Quick static scan (regex, ~2s)
|
|
174
|
+
agentaudit audit <url> [url...] Deep LLM-powered audit (~30s)
|
|
174
175
|
agentaudit check <name> Look up package in registry
|
|
175
|
-
agentaudit
|
|
176
|
+
agentaudit setup Register + configure API key
|
|
176
177
|
```
|
|
177
178
|
|
|
179
|
+
### `scan` vs `audit`
|
|
180
|
+
|
|
181
|
+
| | `scan` | `audit` |
|
|
182
|
+
|--|--------|---------|
|
|
183
|
+
| **How** | Regex-based static analysis | LLM 3-pass analysis (UNDERSTAND → DETECT → CLASSIFY) |
|
|
184
|
+
| **Speed** | ~2 seconds | ~30 seconds |
|
|
185
|
+
| **Depth** | Pattern matching | Semantic code understanding |
|
|
186
|
+
| **Needs API key** | No | Yes (`ANTHROPIC_API_KEY` or `OPENAI_API_KEY`) |
|
|
187
|
+
| **Upload to registry** | No | Yes (with `agentaudit setup`) |
|
|
188
|
+
|
|
189
|
+
Use `scan` for quick checks, `audit` for thorough analysis.
|
|
190
|
+
|
|
178
191
|
### Examples
|
|
179
192
|
|
|
180
193
|
```bash
|
|
181
|
-
#
|
|
194
|
+
# Discover all MCP servers on your machine
|
|
195
|
+
agentaudit discover
|
|
196
|
+
|
|
197
|
+
# Quick scan
|
|
182
198
|
agentaudit scan https://github.com/jlowin/fastmcp
|
|
183
199
|
|
|
184
|
-
#
|
|
185
|
-
agentaudit
|
|
200
|
+
# Deep audit (requires ANTHROPIC_API_KEY or OPENAI_API_KEY)
|
|
201
|
+
agentaudit audit https://github.com/jlowin/fastmcp
|
|
202
|
+
|
|
203
|
+
# Export audit for manual LLM review (no API key needed)
|
|
204
|
+
agentaudit audit https://github.com/owner/repo --export
|
|
186
205
|
|
|
187
|
-
# Check registry
|
|
206
|
+
# Check registry
|
|
188
207
|
agentaudit check mongodb-mcp-server
|
|
189
208
|
```
|
|
190
209
|
|
package/cli.mjs
CHANGED
|
@@ -780,7 +780,7 @@ async function discoverCommand() {
|
|
|
780
780
|
} else {
|
|
781
781
|
unauditedServers++;
|
|
782
782
|
console.log(`${branch} ${c.bold}${server.name}${c.reset} ${sourceLabel}`);
|
|
783
|
-
console.log(`${pipe} ${c.yellow}⚠ not audited${c.reset} ${c.dim}Run: agentaudit
|
|
783
|
+
console.log(`${pipe} ${c.yellow}⚠ not audited${c.reset} ${c.dim}Run: agentaudit audit <source-url>${c.reset}`);
|
|
784
784
|
}
|
|
785
785
|
|
|
786
786
|
if (server.sourceUrl) {
|
|
@@ -806,6 +806,236 @@ async function discoverCommand() {
|
|
|
806
806
|
}
|
|
807
807
|
}
|
|
808
808
|
|
|
809
|
+
// ── Audit command (deep LLM-powered) ────────────────────
|
|
810
|
+
|
|
811
|
+
function loadAuditPrompt() {
|
|
812
|
+
const promptPath = path.join(SKILL_DIR, 'prompts', 'audit-prompt.md');
|
|
813
|
+
if (fs.existsSync(promptPath)) return fs.readFileSync(promptPath, 'utf8');
|
|
814
|
+
return null;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
async function auditRepo(url) {
|
|
818
|
+
const start = Date.now();
|
|
819
|
+
const slug = slugFromUrl(url);
|
|
820
|
+
|
|
821
|
+
console.log(`${icons.scan} ${c.bold}Auditing ${slug}${c.reset} ${c.dim}${url}${c.reset}`);
|
|
822
|
+
console.log(`${icons.pipe} ${c.dim}Deep LLM-powered analysis (3-pass: UNDERSTAND → DETECT → CLASSIFY)${c.reset}`);
|
|
823
|
+
console.log();
|
|
824
|
+
|
|
825
|
+
// Step 1: Clone
|
|
826
|
+
process.stdout.write(` ${c.dim}[1/4]${c.reset} Cloning repository...`);
|
|
827
|
+
const tmpDir = fs.mkdtempSync('/tmp/agentaudit-');
|
|
828
|
+
const repoPath = path.join(tmpDir, 'repo');
|
|
829
|
+
try {
|
|
830
|
+
execSync(`git clone --depth 1 "${url}" "${repoPath}" 2>/dev/null`, {
|
|
831
|
+
timeout: 30_000, stdio: 'pipe',
|
|
832
|
+
});
|
|
833
|
+
console.log(` ${c.green}done${c.reset}`);
|
|
834
|
+
} catch {
|
|
835
|
+
console.log(` ${c.red}failed${c.reset}`);
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Step 2: Collect files
|
|
840
|
+
process.stdout.write(` ${c.dim}[2/4]${c.reset} Collecting source files...`);
|
|
841
|
+
const files = collectFiles(repoPath);
|
|
842
|
+
console.log(` ${c.green}${files.length} files${c.reset}`);
|
|
843
|
+
|
|
844
|
+
// Step 3: Build audit payload
|
|
845
|
+
process.stdout.write(` ${c.dim}[3/4]${c.reset} Preparing audit payload...`);
|
|
846
|
+
const auditPrompt = loadAuditPrompt();
|
|
847
|
+
|
|
848
|
+
let codeBlock = '';
|
|
849
|
+
for (const file of files) {
|
|
850
|
+
codeBlock += `\n### FILE: ${file.path}\n\`\`\`\n${file.content}\n\`\`\`\n`;
|
|
851
|
+
}
|
|
852
|
+
console.log(` ${c.green}done${c.reset}`);
|
|
853
|
+
|
|
854
|
+
// Step 4: LLM Analysis
|
|
855
|
+
// Check for API keys to determine which LLM to use
|
|
856
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
857
|
+
const openaiKey = process.env.OPENAI_API_KEY;
|
|
858
|
+
|
|
859
|
+
if (!anthropicKey && !openaiKey) {
|
|
860
|
+
// No LLM API key — output the prepared audit for piping or MCP use
|
|
861
|
+
console.log();
|
|
862
|
+
console.log(` ${c.yellow}No LLM API key found.${c.reset} To run the audit automatically, set one of:`);
|
|
863
|
+
console.log(` ${c.dim}export ANTHROPIC_API_KEY=sk-ant-...${c.reset}`);
|
|
864
|
+
console.log(` ${c.dim}export OPENAI_API_KEY=sk-...${c.reset}`);
|
|
865
|
+
console.log();
|
|
866
|
+
console.log(` ${c.bold}Alternatives:${c.reset}`);
|
|
867
|
+
console.log(` ${c.dim}1.${c.reset} Use the MCP server in Claude/Cursor — your agent runs the audit automatically`);
|
|
868
|
+
console.log(` ${c.dim}2.${c.reset} Export for manual review: ${c.cyan}agentaudit audit ${url} --export${c.reset}`);
|
|
869
|
+
console.log();
|
|
870
|
+
|
|
871
|
+
// Check if --export flag
|
|
872
|
+
if (process.argv.includes('--export')) {
|
|
873
|
+
const exportPath = path.join(process.cwd(), `audit-${slug}.md`);
|
|
874
|
+
const exportContent = [
|
|
875
|
+
`# Security Audit: ${slug}`,
|
|
876
|
+
`**Source:** ${url}`,
|
|
877
|
+
`**Files:** ${files.length}`,
|
|
878
|
+
``,
|
|
879
|
+
`## Audit Instructions`,
|
|
880
|
+
``,
|
|
881
|
+
auditPrompt || '(audit prompt not found)',
|
|
882
|
+
``,
|
|
883
|
+
`## Report Format`,
|
|
884
|
+
``,
|
|
885
|
+
`After analysis, produce a JSON report:`,
|
|
886
|
+
'```json',
|
|
887
|
+
`{ "skill_slug": "${slug}", "source_url": "${url}", "risk_score": 0, "result": "safe", "findings": [] }`,
|
|
888
|
+
'```',
|
|
889
|
+
``,
|
|
890
|
+
`## Source Code`,
|
|
891
|
+
``,
|
|
892
|
+
codeBlock,
|
|
893
|
+
].join('\n');
|
|
894
|
+
fs.writeFileSync(exportPath, exportContent);
|
|
895
|
+
console.log(` ${icons.safe} Exported to ${c.bold}${exportPath}${c.reset}`);
|
|
896
|
+
console.log(` ${c.dim}Paste this into any LLM (Claude, ChatGPT, etc.) for analysis${c.reset}`);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// Cleanup
|
|
900
|
+
try { execSync(`rm -rf "${tmpDir}"`, { stdio: 'pipe' }); } catch {}
|
|
901
|
+
return null;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// We have an API key — run LLM audit
|
|
905
|
+
process.stdout.write(` ${c.dim}[4/4]${c.reset} Running LLM analysis...`);
|
|
906
|
+
|
|
907
|
+
const systemPrompt = auditPrompt || 'You are a security auditor. Analyze the code and report findings as JSON.';
|
|
908
|
+
const userMessage = [
|
|
909
|
+
`Audit this package: **${slug}** (${url})`,
|
|
910
|
+
``,
|
|
911
|
+
`After analysis, respond with ONLY a JSON object (no markdown, no explanation):`,
|
|
912
|
+
'```',
|
|
913
|
+
`{ "skill_slug": "${slug}", "source_url": "${url}", "package_type": "<mcp-server|agent-skill|library|cli-tool>",`,
|
|
914
|
+
` "risk_score": <0-100>, "result": "<safe|caution|unsafe>", "max_severity": "<none|low|medium|high|critical>",`,
|
|
915
|
+
` "findings_count": <n>, "findings": [{ "id": "...", "title": "...", "severity": "...", "category": "...",`,
|
|
916
|
+
` "description": "...", "file": "...", "line": <n>, "remediation": "...", "confidence": "...", "is_by_design": false }] }`,
|
|
917
|
+
'```',
|
|
918
|
+
``,
|
|
919
|
+
`## Source Code`,
|
|
920
|
+
codeBlock,
|
|
921
|
+
].join('\n');
|
|
922
|
+
|
|
923
|
+
let report = null;
|
|
924
|
+
|
|
925
|
+
try {
|
|
926
|
+
if (anthropicKey) {
|
|
927
|
+
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
|
928
|
+
method: 'POST',
|
|
929
|
+
headers: {
|
|
930
|
+
'x-api-key': anthropicKey,
|
|
931
|
+
'anthropic-version': '2023-06-01',
|
|
932
|
+
'content-type': 'application/json',
|
|
933
|
+
},
|
|
934
|
+
body: JSON.stringify({
|
|
935
|
+
model: 'claude-sonnet-4-20250514',
|
|
936
|
+
max_tokens: 8192,
|
|
937
|
+
system: systemPrompt,
|
|
938
|
+
messages: [{ role: 'user', content: userMessage }],
|
|
939
|
+
}),
|
|
940
|
+
signal: AbortSignal.timeout(120_000),
|
|
941
|
+
});
|
|
942
|
+
const data = await res.json();
|
|
943
|
+
const text = data.content?.[0]?.text || '';
|
|
944
|
+
// Extract JSON from response
|
|
945
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
946
|
+
if (jsonMatch) report = JSON.parse(jsonMatch[0]);
|
|
947
|
+
} else if (openaiKey) {
|
|
948
|
+
const res = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
949
|
+
method: 'POST',
|
|
950
|
+
headers: {
|
|
951
|
+
'Authorization': `Bearer ${openaiKey}`,
|
|
952
|
+
'Content-Type': 'application/json',
|
|
953
|
+
},
|
|
954
|
+
body: JSON.stringify({
|
|
955
|
+
model: 'gpt-4o',
|
|
956
|
+
max_tokens: 8192,
|
|
957
|
+
messages: [
|
|
958
|
+
{ role: 'system', content: systemPrompt },
|
|
959
|
+
{ role: 'user', content: userMessage },
|
|
960
|
+
],
|
|
961
|
+
}),
|
|
962
|
+
signal: AbortSignal.timeout(120_000),
|
|
963
|
+
});
|
|
964
|
+
const data = await res.json();
|
|
965
|
+
const text = data.choices?.[0]?.message?.content || '';
|
|
966
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
967
|
+
if (jsonMatch) report = JSON.parse(jsonMatch[0]);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
console.log(` ${c.green}done${c.reset} ${c.dim}(${elapsed(start)})${c.reset}`);
|
|
971
|
+
} catch (err) {
|
|
972
|
+
console.log(` ${c.red}failed${c.reset}`);
|
|
973
|
+
console.log(` ${c.red}${err.message}${c.reset}`);
|
|
974
|
+
try { execSync(`rm -rf "${tmpDir}"`, { stdio: 'pipe' }); } catch {}
|
|
975
|
+
return null;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// Cleanup repo
|
|
979
|
+
try { execSync(`rm -rf "${tmpDir}"`, { stdio: 'pipe' }); } catch {}
|
|
980
|
+
|
|
981
|
+
if (!report) {
|
|
982
|
+
console.log(` ${c.red}Could not parse LLM response as JSON${c.reset}`);
|
|
983
|
+
return null;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// Display results
|
|
987
|
+
console.log();
|
|
988
|
+
const riskScore = report.risk_score || 0;
|
|
989
|
+
console.log(` ${riskBadge(riskScore)} Risk ${riskScore}/100 ${c.bold}${report.result || 'unknown'}${c.reset}`);
|
|
990
|
+
console.log();
|
|
991
|
+
|
|
992
|
+
if (report.findings && report.findings.length > 0) {
|
|
993
|
+
console.log(` ${c.bold}Findings (${report.findings.length})${c.reset}`);
|
|
994
|
+
console.log();
|
|
995
|
+
for (const f of report.findings) {
|
|
996
|
+
const sc = severityColor(f.severity);
|
|
997
|
+
console.log(` ${severityIcon(f.severity)} ${sc}${(f.severity || '').toUpperCase().padEnd(8)}${c.reset} ${f.title}`);
|
|
998
|
+
if (f.file) console.log(` ${c.dim}${f.file}${f.line ? ':' + f.line : ''}${c.reset}`);
|
|
999
|
+
if (f.description) console.log(` ${c.dim}${f.description.slice(0, 120)}${c.reset}`);
|
|
1000
|
+
console.log();
|
|
1001
|
+
}
|
|
1002
|
+
} else {
|
|
1003
|
+
console.log(` ${c.green}No findings — package looks clean.${c.reset}`);
|
|
1004
|
+
console.log();
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// Upload to registry
|
|
1008
|
+
const creds = loadCredentials();
|
|
1009
|
+
if (creds) {
|
|
1010
|
+
process.stdout.write(` Uploading report to registry...`);
|
|
1011
|
+
try {
|
|
1012
|
+
const res = await fetch(`${REGISTRY_URL}/api/reports`, {
|
|
1013
|
+
method: 'POST',
|
|
1014
|
+
headers: {
|
|
1015
|
+
'Authorization': `Bearer ${creds.api_key}`,
|
|
1016
|
+
'Content-Type': 'application/json',
|
|
1017
|
+
},
|
|
1018
|
+
body: JSON.stringify(report),
|
|
1019
|
+
signal: AbortSignal.timeout(15_000),
|
|
1020
|
+
});
|
|
1021
|
+
if (res.ok) {
|
|
1022
|
+
const data = await res.json();
|
|
1023
|
+
console.log(` ${c.green}done${c.reset}`);
|
|
1024
|
+
console.log(` ${c.dim}Report: ${REGISTRY_URL}/skills/${slug}${c.reset}`);
|
|
1025
|
+
} else {
|
|
1026
|
+
console.log(` ${c.yellow}failed (HTTP ${res.status})${c.reset}`);
|
|
1027
|
+
}
|
|
1028
|
+
} catch (err) {
|
|
1029
|
+
console.log(` ${c.yellow}failed${c.reset}`);
|
|
1030
|
+
}
|
|
1031
|
+
} else {
|
|
1032
|
+
console.log(` ${c.dim}Run ${c.cyan}agentaudit setup${c.dim} to upload reports to the registry${c.reset}`);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
console.log();
|
|
1036
|
+
return report;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
809
1039
|
// ── Check command ───────────────────────────────────────
|
|
810
1040
|
|
|
811
1041
|
async function checkPackage(name) {
|
|
@@ -815,7 +1045,7 @@ async function checkPackage(name) {
|
|
|
815
1045
|
const data = await checkRegistry(name);
|
|
816
1046
|
if (!data) {
|
|
817
1047
|
console.log(` ${c.yellow}Not found${c.reset} — package "${name}" hasn't been audited yet.`);
|
|
818
|
-
console.log(` ${c.dim}Run: agentaudit
|
|
1048
|
+
console.log(` ${c.dim}Run: agentaudit audit <repo-url> for a deep LLM audit${c.reset}`);
|
|
819
1049
|
return;
|
|
820
1050
|
}
|
|
821
1051
|
|
|
@@ -835,17 +1065,22 @@ async function main() {
|
|
|
835
1065
|
|
|
836
1066
|
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
837
1067
|
banner();
|
|
838
|
-
console.log(` ${c.bold}
|
|
839
|
-
console.log(
|
|
840
|
-
console.log(` agentaudit discover Find
|
|
841
|
-
console.log(` agentaudit scan <
|
|
842
|
-
console.log(` agentaudit
|
|
1068
|
+
console.log(` ${c.bold}Commands:${c.reset}`);
|
|
1069
|
+
console.log();
|
|
1070
|
+
console.log(` ${c.cyan}agentaudit discover${c.reset} Find local MCP servers + check registry`);
|
|
1071
|
+
console.log(` ${c.cyan}agentaudit scan${c.reset} <url> [url...] Quick static scan (regex, local)`);
|
|
1072
|
+
console.log(` ${c.cyan}agentaudit audit${c.reset} <url> [url...] Deep LLM-powered security audit`);
|
|
1073
|
+
console.log(` ${c.cyan}agentaudit check${c.reset} <name> Look up package in registry`);
|
|
1074
|
+
console.log(` ${c.cyan}agentaudit setup${c.reset} Register + configure API key`);
|
|
1075
|
+
console.log();
|
|
1076
|
+
console.log(` ${c.bold}scan${c.reset} vs ${c.bold}audit${c.reset}:`);
|
|
1077
|
+
console.log(` ${c.dim}scan = fast regex-based static analysis (~2s)${c.reset}`);
|
|
1078
|
+
console.log(` ${c.dim}audit = deep LLM analysis with 3-pass methodology (~30s)${c.reset}`);
|
|
843
1079
|
console.log();
|
|
844
1080
|
console.log(` ${c.bold}Examples:${c.reset}`);
|
|
845
|
-
console.log(` agentaudit setup`);
|
|
846
1081
|
console.log(` agentaudit discover`);
|
|
847
1082
|
console.log(` agentaudit scan https://github.com/owner/repo`);
|
|
848
|
-
console.log(` agentaudit
|
|
1083
|
+
console.log(` agentaudit audit https://github.com/owner/repo`);
|
|
849
1084
|
console.log(` agentaudit check fastmcp`);
|
|
850
1085
|
console.log();
|
|
851
1086
|
process.exit(0);
|
|
@@ -879,6 +1114,7 @@ async function main() {
|
|
|
879
1114
|
if (targets.length === 0) {
|
|
880
1115
|
console.log(` ${c.red}Error: at least one repository URL required${c.reset}`);
|
|
881
1116
|
console.log(` ${c.dim}Tip: use ${c.cyan}agentaudit discover${c.dim} to find & check locally installed MCP servers${c.reset}`);
|
|
1117
|
+
console.log(` ${c.dim}Tip: use ${c.cyan}agentaudit audit <url>${c.dim} for a deep LLM-powered audit${c.reset}`);
|
|
882
1118
|
process.exit(1);
|
|
883
1119
|
}
|
|
884
1120
|
|
|
@@ -894,6 +1130,19 @@ async function main() {
|
|
|
894
1130
|
return;
|
|
895
1131
|
}
|
|
896
1132
|
|
|
1133
|
+
if (command === 'audit') {
|
|
1134
|
+
const urls = targets.filter(t => !t.startsWith('--'));
|
|
1135
|
+
if (urls.length === 0) {
|
|
1136
|
+
console.log(` ${c.red}Error: at least one repository URL required${c.reset}`);
|
|
1137
|
+
process.exit(1);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
for (const url of urls) {
|
|
1141
|
+
await auditRepo(url);
|
|
1142
|
+
}
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
897
1146
|
console.log(` ${c.red}Unknown command: ${command}${c.reset}`);
|
|
898
1147
|
console.log(` ${c.dim}Run agentaudit --help for usage${c.reset}`);
|
|
899
1148
|
process.exit(1);
|