agentaudit 3.10.8 → 3.10.10

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 +65 -68
  2. package/index.mjs +13 -9
  3. package/package.json +3 -3
package/cli.mjs CHANGED
@@ -435,26 +435,31 @@ function singleSelect(items, { title = 'Select', hint = '↑↓=move Enter=sele
435
435
  });
436
436
  }
437
437
 
438
- async function registerAgent(agentName) {
439
- const res = await fetch(`${REGISTRY_URL}/api/register`, {
440
- method: 'POST',
441
- headers: { 'Content-Type': 'application/json' },
442
- body: JSON.stringify({ agent_name: agentName }),
443
- signal: AbortSignal.timeout(15_000),
444
- });
445
- if (!res.ok) throw new Error(`Registration failed (HTTP ${res.status}): ${await res.text()}`);
446
- return res.json();
438
+ async function validateApiKey(apiKey) {
439
+ try {
440
+ const res = await fetch(`${REGISTRY_URL}/api/auth/validate`, {
441
+ headers: { 'Authorization': `Bearer ${apiKey}` },
442
+ signal: AbortSignal.timeout(10_000),
443
+ });
444
+ if (res.ok) {
445
+ const data = await res.json();
446
+ return { valid: true, agent_name: data.agent_name || null };
447
+ }
448
+ return { valid: false, agent_name: null };
449
+ } catch {
450
+ return { valid: false, agent_name: null };
451
+ }
447
452
  }
448
453
 
449
454
  async function setupCommand() {
450
- console.log(` ${c.bold}AgentAudit Login${c.reset}`);
451
- console.log(` ${c.dim}Create an account to upload audit reports to agentaudit.dev${c.reset}`);
455
+ console.log(` ${c.bold}AgentAudit Setup${c.reset}`);
456
+ console.log(` ${c.dim}Link your API key to upload audit reports to agentaudit.dev${c.reset}`);
452
457
  console.log();
453
458
 
454
459
  const existing = loadCredentials();
455
460
  if (existing) {
456
- console.log(` ${icons.safe} Already logged in as ${c.bold}${existing.agent_name}${c.reset}`);
457
- console.log(` ${c.dim}Key: ${existing.api_key.slice(0, 8)}...${c.reset}`);
461
+ console.log(` ${icons.safe} Already configured as ${c.bold}${existing.agent_name}${c.reset}`);
462
+ console.log(` ${c.dim}Key: ${existing.api_key.slice(0, 12)}...${c.reset}`);
458
463
  console.log();
459
464
  const answer = await askQuestion(` Reconfigure? ${c.dim}(y/N)${c.reset} `);
460
465
  if (answer.toLowerCase() !== 'y') {
@@ -464,39 +469,28 @@ async function setupCommand() {
464
469
  console.log();
465
470
  }
466
471
 
467
- console.log(` ${c.bold}1)${c.reset} Register new agent ${c.dim}(free, creates API key automatically)${c.reset}`);
468
- console.log(` ${c.bold}2)${c.reset} Enter existing API key`);
469
- console.log();
470
- const choice = await askQuestion(` Choice ${c.dim}(1/2)${c.reset}: `);
472
+ console.log(` ${c.bold}Step 1:${c.reset} Create an API key at ${c.cyan}${REGISTRY_URL}/profile${c.reset}`);
473
+ console.log(` ${c.dim}Sign in with GitHub, then click "Create API Key".${c.reset}`);
471
474
  console.log();
475
+ const key = await askQuestion(` ${c.bold}Step 2:${c.reset} Paste your API key here: `);
476
+ if (!key || !key.trim()) {
477
+ console.log(` ${c.red}No key entered.${c.reset}`);
478
+ return;
479
+ }
472
480
 
473
- if (choice === '2') {
474
- const key = await askQuestion(` API Key: `);
475
- if (!key) { console.log(` ${c.red}No key entered.${c.reset}`); return; }
476
- const name = await askQuestion(` Agent name ${c.dim}(optional)${c.reset}: `);
477
- saveCredentials({ api_key: key, agent_name: name || 'custom' });
481
+ process.stdout.write(` Validating...`);
482
+ const validation = await validateApiKey(key.trim());
483
+ if (validation.valid) {
484
+ const agentName = validation.agent_name || 'agent';
485
+ saveCredentials({ api_key: key.trim(), agent_name: agentName });
486
+ console.log(` ${c.green}valid!${c.reset}`);
478
487
  console.log();
479
- console.log(` ${icons.safe} Saved! Key stored in ${c.dim}${USER_CRED_FILE}${c.reset}`);
488
+ console.log(` ${icons.safe} Logged in as ${c.bold}${agentName}${c.reset}`);
489
+ console.log(` ${c.dim}Key saved to: ${USER_CRED_FILE}${c.reset}`);
480
490
  } else {
481
- const name = await askQuestion(` Agent name ${c.dim}(e.g. my-scanner, claude-desktop)${c.reset}: `);
482
- if (!name || !/^[a-zA-Z0-9._-]{2,64}$/.test(name)) {
483
- console.log(` ${c.red}Invalid name. Use 2-64 chars: letters, numbers, dash, underscore, dot.${c.reset}`);
484
- return;
485
- }
486
- process.stdout.write(` Registering ${c.bold}${name}${c.reset}...`);
487
- try {
488
- const data = await registerAgent(name);
489
- saveCredentials({ api_key: data.api_key, agent_name: data.agent_name });
490
- console.log(` ${c.green}done!${c.reset}`);
491
- console.log();
492
- console.log(` ${icons.safe} Registered as ${c.bold}${data.agent_name}${c.reset}`);
493
- console.log(` ${c.dim}Key: ${data.api_key.slice(0, 12)}...${c.reset}`);
494
- console.log(` ${c.dim}Saved to: ${USER_CRED_FILE}${c.reset}`);
495
- } catch (err) {
496
- console.log(` ${c.red}failed${c.reset}`);
497
- console.log(` ${c.red}${err.message}${c.reset}`);
498
- return;
499
- }
491
+ console.log(` ${c.red}invalid${c.reset}`);
492
+ console.log(` ${c.red}Key not recognized. Make sure you copied the full key from ${REGISTRY_URL}/profile${c.reset}`);
493
+ return;
500
494
  }
501
495
 
502
496
  console.log();
@@ -1056,7 +1050,7 @@ function quickChecks(files) {
1056
1050
 
1057
1051
  async function checkRegistry(slug) {
1058
1052
  try {
1059
- const res = await fetch(`${REGISTRY_URL}/api/skills/${encodeURIComponent(slug)}`, {
1053
+ const res = await fetch(`${REGISTRY_URL}/api/packages/${encodeURIComponent(slug)}`, {
1060
1054
  signal: AbortSignal.timeout(5000),
1061
1055
  });
1062
1056
  if (res.ok) return await res.json();
@@ -1146,7 +1140,7 @@ function printScanResult(url, info, files, findings, registryData, duration) {
1146
1140
  if (registryData) {
1147
1141
  const rd = registryData;
1148
1142
  const riskScore = rd.risk_score ?? rd.latest_risk_score ?? 0;
1149
- console.log(`${icons.treeLast} ${c.dim}registry${c.reset} ${riskBadge(riskScore)} Risk ${riskScore} ${c.dim}${REGISTRY_URL}/skills/${slug}${c.reset}`);
1143
+ console.log(`${icons.treeLast} ${c.dim}registry${c.reset} ${riskBadge(riskScore)} Risk ${riskScore} ${c.dim}${REGISTRY_URL}/packages/${slug}${c.reset}`);
1150
1144
  } else {
1151
1145
  console.log(`${icons.treeLast} ${c.dim}registry${c.reset} ${c.dim}not audited yet${c.reset}`);
1152
1146
  }
@@ -1533,7 +1527,7 @@ async function discoverCommand(options = {}) {
1533
1527
  const riskScore = regData.risk_score ?? regData.latest_risk_score ?? 0;
1534
1528
  const hasOfficial = regData.has_official_audit;
1535
1529
  console.log(`${branch} ${c.bold}${server.name}${c.reset} ${sourceLabel}`);
1536
- console.log(`${pipe} ${riskBadge(riskScore)} Risk ${riskScore} ${hasOfficial ? `${c.green}✔ official${c.reset} ` : ''}${c.dim}${REGISTRY_URL}/skills/${slug}${c.reset}`);
1530
+ console.log(`${pipe} ${riskBadge(riskScore)} Risk ${riskScore} ${hasOfficial ? `${c.green}✔ official${c.reset} ` : ''}${c.dim}${REGISTRY_URL}/packages/${slug}${c.reset}`);
1537
1531
  if (resolvedUrl) allServersWithUrls.push({ name: server.name, sourceUrl: resolvedUrl, hasAudit: true, regData });
1538
1532
  } else {
1539
1533
  unauditedServers++;
@@ -1970,7 +1964,12 @@ async function auditRepo(url) {
1970
1964
  ).digest('hex');
1971
1965
  // Code-based type detection (uses files array in memory + repoPath for context)
1972
1966
  const pkgInfo = detectPackageInfo(repoPath, files);
1973
- const detectedType = pkgInfo.type === 'unknown' ? 'other' : pkgInfo.type;
1967
+ // Known MCP frameworks are libraries, not servers (they contain MCP patterns but ARE the SDK)
1968
+ const KNOWN_MCP_LIBS = new Set(['fastmcp', 'jlowin-fastmcp', 'mcp-go', 'fastapi-mcp', 'fastapi_mcp', 'mcp-use', 'mcp-agent']);
1969
+ const KNOWN_CLI = new Set(['mcp-cli', 'mcp-scan', 'inspector']);
1970
+ let detectedType = pkgInfo.type === 'unknown' ? 'other' : pkgInfo.type;
1971
+ if (KNOWN_MCP_LIBS.has(slug)) detectedType = 'library';
1972
+ if (KNOWN_CLI.has(slug)) detectedType = 'cli-tool';
1974
1973
 
1975
1974
  // Cleanup repo (safe now — provenance data captured above)
1976
1975
  try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
@@ -2041,7 +2040,7 @@ async function auditRepo(url) {
2041
2040
  if (res.ok) {
2042
2041
  const data = await res.json();
2043
2042
  console.log(` ${c.green}done${c.reset}`);
2044
- console.log(` ${c.dim}Report: ${REGISTRY_URL}/skills/${slug}${c.reset}`);
2043
+ console.log(` ${c.dim}Report: ${REGISTRY_URL}/packages/${slug}${c.reset}`);
2045
2044
  } else {
2046
2045
  let errBody = '';
2047
2046
  try { errBody = await res.text(); } catch {}
@@ -2054,27 +2053,22 @@ async function auditRepo(url) {
2054
2053
  console.log(` ${c.yellow}failed${c.reset}`);
2055
2054
  }
2056
2055
  } else if (process.stdin.isTTY) {
2057
- // No credentials — offer inline registration
2058
- console.log();
2059
- console.log(` ${c.bold}Share this audit with the community?${c.reset}`);
2060
- console.log(` ${c.dim}Uploading helps others assess package security. Account is free.${c.reset}`);
2056
+ // No credentials — prompt to paste key or set up
2061
2057
  console.log();
2062
- console.log(` ${c.bold}1)${c.reset} Create account + upload ${c.dim}(free)${c.reset}`);
2063
- console.log(` ${c.bold}2)${c.reset} Skip`);
2058
+ console.log(` ${c.bold}Want to upload this report to agentaudit.dev?${c.reset}`);
2059
+ console.log(` ${c.dim}Create an API key at ${c.cyan}${REGISTRY_URL}/profile${c.dim} (sign in with GitHub)${c.reset}`);
2064
2060
  console.log();
2065
- const uploadChoice = await askQuestion(` Choice ${c.dim}(1/2)${c.reset}: `);
2066
- if (uploadChoice === '1') {
2067
- const name = await askQuestion(` Agent name ${c.dim}(e.g. my-scanner, claude-desktop)${c.reset}: `);
2068
- if (!name || !/^[a-zA-Z0-9._-]{2,64}$/.test(name)) {
2069
- console.log(` ${c.red}Invalid name. Use 2-64 chars: letters, numbers, dash, underscore, dot.${c.reset}`);
2070
- } else {
2061
+ const pastedKey = await askQuestion(` Paste API key ${c.dim}(or Enter to skip)${c.reset}: `);
2062
+ if (pastedKey && pastedKey.trim()) {
2063
+ process.stdout.write(` Validating...`);
2064
+ const validation = await validateApiKey(pastedKey.trim());
2065
+ if (validation.valid) {
2066
+ const agentName = validation.agent_name || 'agent';
2067
+ saveCredentials({ api_key: pastedKey.trim(), agent_name: agentName });
2068
+ creds = { api_key: pastedKey.trim(), agent_name: agentName };
2069
+ console.log(` ${c.green}valid!${c.reset}`);
2070
+ process.stdout.write(` Uploading report...`);
2071
2071
  try {
2072
- process.stdout.write(` Registering ${c.bold}${name}${c.reset}...`);
2073
- const regData = await registerAgent(name);
2074
- saveCredentials({ api_key: regData.api_key, agent_name: regData.agent_name });
2075
- console.log(` ${c.green}done!${c.reset}`);
2076
- creds = { api_key: regData.api_key, agent_name: regData.agent_name };
2077
- process.stdout.write(` Uploading report...`);
2078
2072
  const res = await fetch(`${REGISTRY_URL}/api/reports`, {
2079
2073
  method: 'POST',
2080
2074
  headers: {
@@ -2086,7 +2080,7 @@ async function auditRepo(url) {
2086
2080
  });
2087
2081
  if (res.ok) {
2088
2082
  console.log(` ${c.green}done${c.reset}`);
2089
- console.log(` ${c.dim}Report: ${REGISTRY_URL}/skills/${slug}${c.reset}`);
2083
+ console.log(` ${c.dim}Report: ${REGISTRY_URL}/packages/${slug}${c.reset}`);
2090
2084
  } else {
2091
2085
  console.log(` ${c.yellow}failed (HTTP ${res.status})${c.reset}`);
2092
2086
  }
@@ -2094,10 +2088,13 @@ async function auditRepo(url) {
2094
2088
  console.log(` ${c.red}failed${c.reset}`);
2095
2089
  console.log(` ${c.dim}${err.message}${c.reset}`);
2096
2090
  }
2091
+ } else {
2092
+ console.log(` ${c.red}invalid key${c.reset}`);
2093
+ console.log(` ${c.dim}Run ${c.cyan}agentaudit setup${c.dim} to configure.${c.reset}`);
2097
2094
  }
2098
2095
  }
2099
2096
  } else {
2100
- console.log(` ${c.dim}Run ${c.cyan}agentaudit setup${c.dim} to create an account and upload reports${c.reset}`);
2097
+ console.log(` ${c.dim}Run ${c.cyan}agentaudit setup${c.dim} to configure your API key and upload reports${c.reset}`);
2101
2098
  }
2102
2099
 
2103
2100
  console.log();
@@ -2126,7 +2123,7 @@ async function checkPackage(name) {
2126
2123
  console.log(` ${c.bold}${name}${c.reset} ${riskBadge(riskScore)}`);
2127
2124
  console.log(` ${c.dim}Risk Score: ${riskScore}/100${c.reset}`);
2128
2125
  if (data.source_url) console.log(` ${c.dim}Source: ${data.source_url}${c.reset}`);
2129
- console.log(` ${c.dim}Registry: ${REGISTRY_URL}/skills/${name}${c.reset}`);
2126
+ console.log(` ${c.dim}Registry: ${REGISTRY_URL}/packages/${name}${c.reset}`);
2130
2127
  if (data.has_official_audit) console.log(` ${c.green}✔ Officially audited${c.reset}`);
2131
2128
  console.log();
2132
2129
  }
package/index.mjs CHANGED
@@ -292,7 +292,7 @@ async function resolveSourceUrl(server) {
292
292
 
293
293
  async function checkRegistry(slug) {
294
294
  try {
295
- const res = await fetch(`${REGISTRY_URL}/api/skills/${encodeURIComponent(slug)}`, {
295
+ const res = await fetch(`${REGISTRY_URL}/api/packages/${encodeURIComponent(slug)}`, {
296
296
  signal: AbortSignal.timeout(5000),
297
297
  });
298
298
  if (res.ok) return await res.json();
@@ -303,7 +303,7 @@ async function checkRegistry(slug) {
303
303
  // ── MCP Server ───────────────────────────────────────────
304
304
 
305
305
  const server = new Server(
306
- { name: 'agentaudit', version: '3.9.8' },
306
+ { name: 'agentaudit', version: '3.10.9' },
307
307
  { capabilities: { tools: {} } }
308
308
  );
309
309
 
@@ -413,7 +413,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
413
413
  const risk = regData.risk_score ?? regData.latest_risk_score ?? 0;
414
414
  const official = regData.has_official_audit ? ' (official)' : '';
415
415
  text += `- **Registry: ✅ Audited** — Risk ${risk}/100${official}\n`;
416
- text += `- Report: ${REGISTRY_URL}/skills/${slug}\n`;
416
+ text += `- Report: ${REGISTRY_URL}/packages/${slug}\n`;
417
417
  } else {
418
418
  const sourceUrl = await resolveSourceUrl(srv);
419
419
  text += `- **Registry: ⚠️ Not audited** — no audit report found\n`;
@@ -457,7 +457,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
457
457
 
458
458
  // Compute provenance data
459
459
  const pkgInfo = detectPackageInfo(repoPath, files);
460
- const detectedType = pkgInfo.type === 'unknown' ? 'other' : pkgInfo.type;
460
+ const KNOWN_MCP_LIBS = new Set(['fastmcp', 'jlowin-fastmcp', 'mcp-go', 'fastapi-mcp', 'fastapi_mcp', 'mcp-use', 'mcp-agent']);
461
+ const KNOWN_CLI = new Set(['mcp-cli', 'mcp-scan', 'inspector']);
462
+ let detectedType = pkgInfo.type === 'unknown' ? 'other' : pkgInfo.type;
463
+ if (KNOWN_MCP_LIBS.has(slug)) detectedType = 'library';
464
+ if (KNOWN_CLI.has(slug)) detectedType = 'cli-tool';
461
465
  let commitSha = '';
462
466
  try { commitSha = execSync('git rev-parse HEAD', { cwd: repoPath, encoding: 'utf8' }).trim(); } catch {}
463
467
  const hashInput = files.slice().sort((a, b) => a.path.localeCompare(b.path))
@@ -579,7 +583,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
579
583
  try { data = JSON.parse(body); } catch { data = { raw: body }; }
580
584
 
581
585
  if (res.ok) {
582
- return { content: [{ type: 'text', text: `✅ Report submitted!\n\nReport ID: ${data.report_id || 'unknown'}\nURL: ${REGISTRY_URL}/skills/${report.skill_slug}\nRisk: ${report.risk_score}/100 (${report.result})\nFindings: ${report.findings_count}` }] };
586
+ return { content: [{ type: 'text', text: `✅ Report submitted!\n\nReport ID: ${data.report_id || 'unknown'}\nURL: ${REGISTRY_URL}/packages/${report.skill_slug}\nRisk: ${report.risk_score}/100 (${report.result})\nFindings: ${report.findings_count}` }] };
583
587
  } else {
584
588
  return { content: [{ type: 'text', text: `Upload failed (HTTP ${res.status}): ${JSON.stringify(data, null, 2)}` }] };
585
589
  }
@@ -596,7 +600,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
596
600
  }
597
601
 
598
602
  try {
599
- const res = await fetch(`${REGISTRY_URL}/api/skills/${encodeURIComponent(package_name)}`, {
603
+ const res = await fetch(`${REGISTRY_URL}/api/packages/${encodeURIComponent(package_name)}`, {
600
604
  signal: AbortSignal.timeout(10_000),
601
605
  });
602
606
 
@@ -618,9 +622,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
618
622
 
619
623
  // Risk assessment at the very top — this is what the agent needs to act on
620
624
  if (typeof risk === 'number' && risk >= 40) {
621
- summary += `🚨 UNSAFE — DO NOT INSTALL WITHOUT WARNING THE USER!\nRisk Score: ${risk}/100 | ${findings} finding(s)\n\nYou MUST tell the user: "${package_name}" has been flagged as HIGH RISK in a security audit. It has ${findings} security finding(s) and a risk score of ${risk}/100. Installing this package is NOT recommended. Ask the user if they want to proceed despite the risk, and link them to the full report: ${REGISTRY_URL}/skills/${package_name}\n\n`;
625
+ summary += `🚨 UNSAFE — DO NOT INSTALL WITHOUT WARNING THE USER!\nRisk Score: ${risk}/100 | ${findings} finding(s)\n\nYou MUST tell the user: "${package_name}" has been flagged as HIGH RISK in a security audit. It has ${findings} security finding(s) and a risk score of ${risk}/100. Installing this package is NOT recommended. Ask the user if they want to proceed despite the risk, and link them to the full report: ${REGISTRY_URL}/packages/${package_name}\n\n`;
622
626
  } else if (typeof risk === 'number' && risk >= 20) {
623
- summary += `⚠️ MODERATE RISK — Inform the user before installing.\nRisk Score: ${risk}/100 | ${findings} finding(s)\n\nTell the user: "${package_name}" has some security findings (risk ${risk}/100). Recommend they review the report before installing: ${REGISTRY_URL}/skills/${package_name}\n\n`;
627
+ summary += `⚠️ MODERATE RISK — Inform the user before installing.\nRisk Score: ${risk}/100 | ${findings} finding(s)\n\nTell the user: "${package_name}" has some security findings (risk ${risk}/100). Recommend they review the report before installing: ${REGISTRY_URL}/packages/${package_name}\n\n`;
624
628
  } else if (typeof risk === 'number') {
625
629
  summary += `✅ LOW RISK — Safe to install.\nRisk Score: ${risk}/100 | ${findings} finding(s)\n\nThis package has been audited and appears safe. You can proceed with installation.\n\n`;
626
630
  }
@@ -636,7 +640,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
636
640
  summary += `Last Audited: ${auditedAt}\n`;
637
641
  if (version) summary += `Audited Version: ${version}\n`;
638
642
  if (data.source_url) summary += `Source: ${data.source_url}\n`;
639
- summary += `Registry: ${REGISTRY_URL}/skills/${package_name}\n`;
643
+ summary += `Registry: ${REGISTRY_URL}/packages/${package_name}\n`;
640
644
 
641
645
  return { content: [{ type: 'text', text: summary }] };
642
646
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentaudit",
3
- "version": "3.10.8",
3
+ "version": "3.10.10",
4
4
  "description": "Security scanner for AI packages — MCP server + CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,11 +31,11 @@
31
31
  "prompt-injection",
32
32
  "agent-security"
33
33
  ],
34
- "author": "starbuck100",
34
+ "author": "agentaudit-dev",
35
35
  "license": "AGPL-3.0",
36
36
  "repository": {
37
37
  "type": "git",
38
- "url": "git+https://github.com/starbuck100/agentaudit-mcp.git"
38
+ "url": "git+https://github.com/agentaudit-dev/agentaudit-mcp.git"
39
39
  },
40
40
  "homepage": "https://agentaudit.dev",
41
41
  "engines": {