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.
- package/cli.mjs +102 -14
- package/index.mjs +4 -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('
|
|
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}"
|
|
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 {
|
|
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
|
-
|
|
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('
|
|
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}"
|
|
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 {
|
|
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 {
|
|
1167
|
+
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
1101
1168
|
return null;
|
|
1102
1169
|
}
|
|
1103
1170
|
|
|
1104
1171
|
// Cleanup repo
|
|
1105
|
-
try {
|
|
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
|
-
|
|
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('
|
|
128
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agentaudit-'));
|
|
128
129
|
try {
|
|
129
|
-
execSync(`git clone --depth 1 "${sourceUrl}" "${tmpDir}/repo"
|
|
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 {
|
|
140
|
+
try { fs.rmSync(path.dirname(repoPath), { recursive: true, force: true }); } catch {}
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
function slugFromUrl(url) {
|