agentaudit 3.5.0 → 3.7.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/cli.mjs +102 -14
  2. package/index.mjs +4 -3
  3. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -14,6 +14,7 @@
14
14
  */
15
15
 
16
16
  import fs from 'fs';
17
+ import os from 'os';
17
18
  import path from 'path';
18
19
  import { execSync } from 'child_process';
19
20
  import { createInterface } from 'readline';
@@ -573,15 +574,18 @@ async function scanRepo(url) {
573
574
  process.stdout.write(`${icons.scan} Scanning ${c.bold}${slug}${c.reset} ${c.dim}...${c.reset}`);
574
575
 
575
576
  // Clone
576
- const tmpDir = fs.mkdtempSync('/tmp/agentaudit-');
577
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agentaudit-'));
577
578
  const repoPath = path.join(tmpDir, 'repo');
578
579
  try {
579
- execSync(`git clone --depth 1 "${url}" "${repoPath}" 2>/dev/null`, {
580
+ execSync(`git clone --depth 1 "${url}" "${repoPath}"`, {
580
581
  timeout: 30_000,
581
582
  stdio: 'pipe',
582
583
  });
583
584
  } catch (err) {
584
585
  process.stdout.write(` ${c.red}✖ clone failed${c.reset}\n`);
586
+ const msg = err.stderr?.toString().trim() || err.message?.split('\n')[0] || '';
587
+ if (msg) console.log(` ${c.dim}${msg}${c.reset}`);
588
+ console.log(` ${c.dim}Make sure git is installed and the URL is accessible.${c.reset}`);
585
589
  return null;
586
590
  }
587
591
 
@@ -598,7 +602,7 @@ async function scanRepo(url) {
598
602
  const registryData = await checkRegistry(slug);
599
603
 
600
604
  // Cleanup
601
- try { execSync(`rm -rf "${tmpDir}"`, { stdio: 'pipe' }); } catch {}
605
+ try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
602
606
 
603
607
  const duration = elapsed(start);
604
608
 
@@ -802,7 +806,9 @@ async function resolveSourceUrl(server) {
802
806
  return null;
803
807
  }
804
808
 
805
- async function discoverCommand() {
809
+ async function discoverCommand(options = {}) {
810
+ const autoScan = options.scan || false;
811
+
806
812
  console.log(` ${c.bold}Discovering local MCP servers...${c.reset}`);
807
813
  console.log();
808
814
 
@@ -826,6 +832,7 @@ async function discoverCommand() {
826
832
  let auditedServers = 0;
827
833
  let unauditedServers = 0;
828
834
  const unauditedWithUrls = [];
835
+ const allServersWithUrls = []; // For --scan: all servers we can scan
829
836
 
830
837
  for (const config of configs) {
831
838
  const servers = extractServersFromConfig(config.content);
@@ -870,20 +877,23 @@ async function discoverCommand() {
870
877
  else if (server.url) sourceLabel = `${c.dim}${server.url.length > 60 ? server.url.slice(0, 57) + '...' : server.url}${c.reset}`;
871
878
  else if (server.command) sourceLabel = `${c.dim}${[server.command, ...server.args.slice(0, 2)].join(' ')}${c.reset}`;
872
879
 
880
+ // Always resolve source URL (needed for --scan)
881
+ const resolvedUrl = await resolveSourceUrl(server);
882
+
873
883
  if (regData) {
874
884
  auditedServers++;
875
885
  const riskScore = regData.risk_score ?? regData.latest_risk_score ?? 0;
876
886
  const hasOfficial = regData.has_official_audit;
877
887
  console.log(`${branch} ${c.bold}${server.name}${c.reset} ${sourceLabel}`);
878
888
  console.log(`${pipe} ${riskBadge(riskScore)} Risk ${riskScore} ${hasOfficial ? `${c.green}✔ official${c.reset} ` : ''}${c.dim}${REGISTRY_URL}/skills/${slug}${c.reset}`);
889
+ if (resolvedUrl) allServersWithUrls.push({ name: server.name, sourceUrl: resolvedUrl, hasAudit: true, regData });
879
890
  } else {
880
891
  unauditedServers++;
881
- // Resolve source URL
882
- const resolvedUrl = await resolveSourceUrl(server);
883
892
  console.log(`${branch} ${c.bold}${server.name}${c.reset} ${sourceLabel}`);
884
893
  if (resolvedUrl) {
885
894
  console.log(`${pipe} ${c.yellow}⚠ not audited${c.reset} ${c.dim}Run: ${c.cyan}agentaudit audit ${resolvedUrl}${c.reset}`);
886
895
  unauditedWithUrls.push({ name: server.name, sourceUrl: resolvedUrl });
896
+ allServersWithUrls.push({ name: server.name, sourceUrl: resolvedUrl, hasAudit: false });
887
897
  } else {
888
898
  console.log(`${pipe} ${c.yellow}⚠ not audited${c.reset} ${c.dim}Source URL unknown — check the package's GitHub/npm page${c.reset}`);
889
899
  }
@@ -905,7 +915,55 @@ async function discoverCommand() {
905
915
  if (unauditedServers > 0) console.log(` ${icons.caution} ${c.yellow}${unauditedServers} not audited${c.reset}`);
906
916
  console.log();
907
917
 
908
- if (unauditedServers > 0) {
918
+ // --scan: automatically scan all servers with resolved source URLs
919
+ if (autoScan) {
920
+ const scanTargets = allServersWithUrls.filter(s => s.sourceUrl && s.sourceUrl.startsWith('http'));
921
+ if (scanTargets.length > 0) {
922
+ console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
923
+ console.log(` ${c.bold}${icons.scan} Auto-scanning ${scanTargets.length} server${scanTargets.length !== 1 ? 's' : ''}...${c.reset}`);
924
+ console.log();
925
+
926
+ const scanResults = [];
927
+ for (const target of scanTargets) {
928
+ const result = await scanRepo(target.sourceUrl);
929
+ if (result) scanResults.push({ ...result, serverName: target.name });
930
+ }
931
+
932
+ if (scanResults.length > 1) {
933
+ // Print combined scan summary
934
+ console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
935
+ console.log(` ${c.bold}Scan Summary${c.reset} ${scanResults.length} server${scanResults.length !== 1 ? 's' : ''} scanned`);
936
+ console.log();
937
+
938
+ let totalFindings = 0;
939
+ let serversWithFindings = 0;
940
+
941
+ for (const r of scanResults) {
942
+ const findingCount = r.findings ? r.findings.length : 0;
943
+ totalFindings += findingCount;
944
+ if (findingCount > 0) serversWithFindings++;
945
+
946
+ const status = findingCount === 0
947
+ ? `${icons.safe} ${c.green}clean${c.reset}`
948
+ : `${icons.caution} ${c.yellow}${findingCount} finding${findingCount !== 1 ? 's' : ''}${c.reset}`;
949
+ console.log(` ${status} ${c.bold}${r.serverName || r.slug}${c.reset} ${c.dim}(${r.duration})${c.reset}`);
950
+ }
951
+
952
+ console.log();
953
+ if (serversWithFindings > 0) {
954
+ console.log(` ${c.yellow}${serversWithFindings}/${scanResults.length} server${scanResults.length !== 1 ? 's' : ''} with findings (${totalFindings} total)${c.reset}`);
955
+ console.log(` ${c.dim}Run ${c.cyan}agentaudit audit <url>${c.dim} for deep LLM analysis on flagged servers${c.reset}`);
956
+ } else {
957
+ console.log(` ${c.green}All servers passed quick scan${c.reset}`);
958
+ console.log(` ${c.dim}Run ${c.cyan}agentaudit audit <url>${c.dim} for thorough LLM-powered analysis${c.reset}`);
959
+ }
960
+ console.log();
961
+ }
962
+ } else {
963
+ console.log(` ${c.dim}No scannable source URLs found.${c.reset}`);
964
+ console.log();
965
+ }
966
+ } else if (unauditedServers > 0) {
909
967
  if (unauditedWithUrls.length > 0) {
910
968
  console.log(` ${c.dim}To audit unaudited servers:${c.reset}`);
911
969
  for (const { name, sourceUrl } of unauditedWithUrls) {
@@ -916,6 +974,8 @@ async function discoverCommand() {
916
974
  console.log(` ${c.cyan}agentaudit audit <source-url>${c.reset}`);
917
975
  }
918
976
  console.log();
977
+ console.log(` ${c.dim}Or run ${c.cyan}agentaudit discover --scan${c.dim} to auto-scan all servers${c.reset}`);
978
+ console.log();
919
979
  }
920
980
  }
921
981
 
@@ -937,15 +997,18 @@ async function auditRepo(url) {
937
997
 
938
998
  // Step 1: Clone
939
999
  process.stdout.write(` ${c.dim}[1/4]${c.reset} Cloning repository...`);
940
- const tmpDir = fs.mkdtempSync('/tmp/agentaudit-');
1000
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agentaudit-'));
941
1001
  const repoPath = path.join(tmpDir, 'repo');
942
1002
  try {
943
- execSync(`git clone --depth 1 "${url}" "${repoPath}" 2>/dev/null`, {
1003
+ execSync(`git clone --depth 1 "${url}" "${repoPath}"`, {
944
1004
  timeout: 30_000, stdio: 'pipe',
945
1005
  });
946
1006
  console.log(` ${c.green}done${c.reset}`);
947
- } catch {
1007
+ } catch (err) {
948
1008
  console.log(` ${c.red}failed${c.reset}`);
1009
+ const msg = err.stderr?.toString().trim() || err.message?.split('\n')[0] || '';
1010
+ if (msg) console.log(` ${c.dim}${msg}${c.reset}`);
1011
+ console.log(` ${c.dim}Make sure git is installed and the URL is accessible.${c.reset}`);
949
1012
  return null;
950
1013
  }
951
1014
 
@@ -985,6 +1048,10 @@ async function auditRepo(url) {
985
1048
  console.log(` ${c.dim}$env:ANTHROPIC_API_KEY = "sk-ant-..."${c.reset}`);
986
1049
  console.log(` ${c.dim}$env:OPENAI_API_KEY = "sk-..."${c.reset}`);
987
1050
  console.log();
1051
+ console.log(` ${c.dim}# Windows (CMD):${c.reset}`);
1052
+ console.log(` ${c.dim}set ANTHROPIC_API_KEY=sk-ant-...${c.reset}`);
1053
+ console.log(` ${c.dim}set OPENAI_API_KEY=sk-...${c.reset}`);
1054
+ console.log();
988
1055
  console.log(` ${c.bold}Option 2: Export for manual review${c.reset}`);
989
1056
  console.log(` ${c.cyan}agentaudit audit ${url} --export${c.reset}`);
990
1057
  console.log(` ${c.dim}Creates a markdown file you can paste into any LLM (Claude, ChatGPT, etc.)${c.reset}`);
@@ -1023,7 +1090,7 @@ async function auditRepo(url) {
1023
1090
  }
1024
1091
 
1025
1092
  // Cleanup
1026
- try { execSync(`rm -rf "${tmpDir}"`, { stdio: 'pipe' }); } catch {}
1093
+ try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
1027
1094
  return null;
1028
1095
  }
1029
1096
 
@@ -1097,12 +1164,12 @@ async function auditRepo(url) {
1097
1164
  } catch (err) {
1098
1165
  console.log(` ${c.red}failed${c.reset}`);
1099
1166
  console.log(` ${c.red}${err.message}${c.reset}`);
1100
- try { execSync(`rm -rf "${tmpDir}"`, { stdio: 'pipe' }); } catch {}
1167
+ try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
1101
1168
  return null;
1102
1169
  }
1103
1170
 
1104
1171
  // Cleanup repo
1105
- try { execSync(`rm -rf "${tmpDir}"`, { stdio: 'pipe' }); } catch {}
1172
+ try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
1106
1173
 
1107
1174
  if (!report) {
1108
1175
  console.log(` ${c.red}Could not parse LLM response as JSON${c.reset}`);
@@ -1189,11 +1256,17 @@ async function checkPackage(name) {
1189
1256
  async function main() {
1190
1257
  const args = process.argv.slice(2);
1191
1258
 
1259
+ if (args[0] === '-v' || args[0] === '--version') {
1260
+ console.log(`agentaudit ${getVersion()}`);
1261
+ process.exit(0);
1262
+ }
1263
+
1192
1264
  if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
1193
1265
  banner();
1194
1266
  console.log(` ${c.bold}Commands:${c.reset}`);
1195
1267
  console.log();
1196
1268
  console.log(` ${c.cyan}agentaudit discover${c.reset} Find local MCP servers + check registry`);
1269
+ console.log(` ${c.cyan}agentaudit discover --scan${c.reset} Discover + auto-scan all servers`);
1197
1270
  console.log(` ${c.cyan}agentaudit scan${c.reset} <url> [url...] Quick static scan (regex, local)`);
1198
1271
  console.log(` ${c.cyan}agentaudit audit${c.reset} <url> [url...] Deep LLM-powered security audit`);
1199
1272
  console.log(` ${c.cyan}agentaudit check${c.reset} <name> Look up package in registry`);
@@ -1205,10 +1278,24 @@ async function main() {
1205
1278
  console.log();
1206
1279
  console.log(` ${c.bold}Examples:${c.reset}`);
1207
1280
  console.log(` agentaudit discover`);
1281
+ console.log(` agentaudit discover --scan`);
1208
1282
  console.log(` agentaudit scan https://github.com/owner/repo`);
1209
1283
  console.log(` agentaudit audit https://github.com/owner/repo`);
1210
1284
  console.log(` agentaudit check fastmcp`);
1211
1285
  console.log();
1286
+ console.log(` ${c.bold}For deep audits,${c.reset} set an LLM API key:`);
1287
+ if (process.platform === 'win32') {
1288
+ console.log(` ${c.dim}PowerShell: $env:ANTHROPIC_API_KEY = "sk-ant-..."${c.reset}`);
1289
+ console.log(` ${c.dim}CMD: set ANTHROPIC_API_KEY=sk-ant-...${c.reset}`);
1290
+ console.log(` ${c.dim}(or use OPENAI_API_KEY instead)${c.reset}`);
1291
+ } else {
1292
+ console.log(` ${c.dim}export ANTHROPIC_API_KEY=sk-ant-...${c.reset} ${c.dim}(or OPENAI_API_KEY)${c.reset}`);
1293
+ }
1294
+ console.log();
1295
+ console.log(` ${c.bold}Or use as MCP server${c.reset} in Cursor/Claude ${c.dim}(no extra API key needed):${c.reset}`);
1296
+ console.log(` ${c.dim}Add to your MCP config:${c.reset}`);
1297
+ console.log(` ${c.dim}{ "agentaudit": { "command": "npx", "args": ["-y", "agentaudit"] } }${c.reset}`);
1298
+ console.log();
1212
1299
  process.exit(0);
1213
1300
  }
1214
1301
 
@@ -1223,7 +1310,8 @@ async function main() {
1223
1310
  }
1224
1311
 
1225
1312
  if (command === 'discover') {
1226
- await discoverCommand();
1313
+ const scanFlag = targets.includes('--scan') || targets.includes('-s');
1314
+ await discoverCommand({ scan: scanFlag });
1227
1315
  return;
1228
1316
  }
1229
1317
 
package/index.mjs CHANGED
@@ -25,6 +25,7 @@ import {
25
25
  ListToolsRequestSchema,
26
26
  } from '@modelcontextprotocol/sdk/types.js';
27
27
  import fs from 'fs';
28
+ import os from 'os';
28
29
  import path from 'path';
29
30
  import { execSync } from 'child_process';
30
31
  import { fileURLToPath } from 'url';
@@ -124,9 +125,9 @@ function collectFiles(dir, basePath = '', collected = [], totalSize = { bytes: 0
124
125
  // ── Repo Helpers ────────────────────────────────────────
125
126
 
126
127
  function cloneRepo(sourceUrl) {
127
- const tmpDir = fs.mkdtempSync('/tmp/agentaudit-');
128
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agentaudit-'));
128
129
  try {
129
- execSync(`git clone --depth 1 "${sourceUrl}" "${tmpDir}/repo" 2>/dev/null`, {
130
+ execSync(`git clone --depth 1 "${sourceUrl}" "${tmpDir}/repo"`, {
130
131
  timeout: 30_000, stdio: 'pipe',
131
132
  });
132
133
  return path.join(tmpDir, 'repo');
@@ -136,7 +137,7 @@ function cloneRepo(sourceUrl) {
136
137
  }
137
138
 
138
139
  function cleanupRepo(repoPath) {
139
- try { execSync(`rm -rf "${path.dirname(repoPath)}"`, { stdio: 'pipe' }); } catch {}
140
+ try { fs.rmSync(path.dirname(repoPath), { recursive: true, force: true }); } catch {}
140
141
  }
141
142
 
142
143
  function slugFromUrl(url) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentaudit",
3
- "version": "3.5.0",
3
+ "version": "3.7.0",
4
4
  "description": "Security scanner for AI packages — MCP server + CLI",
5
5
  "type": "module",
6
6
  "bin": {