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.
Files changed (3) hide show
  1. package/README.md +26 -7
  2. package/cli.mjs +258 -9
  3. 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 setup Register + configure API key
173
- agentaudit scan <url> [url...] Scan Git repositories
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 --help Show help
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
- # Scan a single repo
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
- # Scan multiple repos at once
185
- agentaudit scan https://github.com/owner/repo1 https://github.com/owner/repo2
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 for existing audit
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 scan <source-url>${c.reset}`);
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 scan <repo-url> to audit it${c.reset}`);
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}Usage:${c.reset}`);
839
- console.log(` agentaudit setup Register + configure API key`);
840
- console.log(` agentaudit discover Find & check local MCP servers`);
841
- console.log(` agentaudit scan <repo-url> [repo-url...] Scan repositories`);
842
- console.log(` agentaudit check <package-name> Look up in registry`);
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 scan repo1.git repo2.git repo3.git`);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentaudit",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Security scanner for AI packages — MCP server + CLI",
5
5
  "type": "module",
6
6
  "bin": {