agentaudit 3.9.37 → 3.9.39

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 (2) hide show
  1. package/cli.mjs +55 -14
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -537,6 +537,22 @@ const SKIP_EXTENSIONS = new Set([
537
537
  '.woff2', '.ttf', '.eot', '.mp3', '.mp4', '.zip', '.tar', '.gz',
538
538
  '.map', '.min.js', '.min.css', '.d.ts', '.pyc', '.pyo', '.so',
539
539
  '.dylib', '.dll', '.exe', '.bin', '.dat', '.db', '.sqlite',
540
+ '.snap', '.patch', '.diff', '.log', '.csv', '.tsv', '.parquet',
541
+ ]);
542
+ // Files that are never security-relevant (config/docs/metadata)
543
+ const SKIP_FILES = new Set([
544
+ 'license', 'license.md', 'license.txt', 'licence', 'licence.md',
545
+ 'changelog.md', 'changelog', 'changes.md', 'history.md',
546
+ 'contributing.md', 'contributors.md', 'authors', 'authors.md',
547
+ 'code_of_conduct.md', 'security.md', 'funding.yml',
548
+ '.prettierrc', '.prettierrc.json', '.prettierrc.js', '.prettierignore',
549
+ '.eslintignore', '.gitignore', '.gitattributes', '.npmignore',
550
+ '.editorconfig', '.browserslistrc', '.nvmrc', '.node-version',
551
+ '.dockerignore', 'renovate.json', '.renovaterc',
552
+ 'jest.config.js', 'jest.config.ts', 'vitest.config.ts', 'vitest.config.js',
553
+ '.babelrc', 'babel.config.js', 'babel.config.json',
554
+ 'postcss.config.js', 'postcss.config.mjs', 'tailwind.config.js', 'tailwind.config.ts',
555
+ 'prettier.config.js', 'prettier.config.mjs',
540
556
  ]);
541
557
 
542
558
  function collectFiles(dir, basePath = '', collected = [], totalSize = { bytes: 0 }) {
@@ -555,6 +571,7 @@ function collectFiles(dir, basePath = '', collected = [], totalSize = { bytes: 0
555
571
  } else {
556
572
  const ext = path.extname(entry.name).toLowerCase();
557
573
  if (SKIP_EXTENSIONS.has(ext)) continue;
574
+ if (SKIP_FILES.has(entry.name.toLowerCase())) continue;
558
575
  try {
559
576
  const stat = fs.statSync(fullPath);
560
577
  if (stat.size > MAX_FILE_SIZE || stat.size === 0) continue;
@@ -1446,7 +1463,9 @@ async function auditRepo(url) {
1446
1463
 
1447
1464
  // Build code chunks for multi-pass analysis.
1448
1465
  // Budget ~45k tokens (~180k chars) per chunk for code, leaving room for prompt + output.
1449
- const MAX_CHUNK_CHARS = 180_000;
1466
+ // ~15k tokens per chunk for code → fits comfortably in 32k+ context models
1467
+ // with room for system prompt (~2k tokens) + output (4k tokens)
1468
+ const MAX_CHUNK_CHARS = 60_000;
1450
1469
  const chunks = []; // array of code block strings
1451
1470
  let currentChunk = '';
1452
1471
  let currentChars = 0;
@@ -1573,6 +1592,34 @@ async function auditRepo(url) {
1573
1592
  }
1574
1593
 
1575
1594
  // ── LLM call helper (reused for multi-pass) ──
1595
+ // Determine optimal max_tokens based on model context size
1596
+ // For large-context models (128k+) we can afford 8192 output tokens
1597
+ // For medium (32k-128k) use 4096, for small (<32k) use 2048
1598
+ let outputTokenBudget = 4096; // safe default
1599
+ if (resolvedProvider.id === 'openrouter') {
1600
+ try {
1601
+ const modelInfoRes = await fetch(`https://openrouter.ai/api/v1/models`, {
1602
+ signal: AbortSignal.timeout(5000),
1603
+ headers: { 'HTTP-Referer': 'https://agentaudit.dev' },
1604
+ });
1605
+ if (modelInfoRes.ok) {
1606
+ const modelData = await modelInfoRes.json();
1607
+ const modelInfo = modelData.data?.find(m => m.id === actualModel);
1608
+ if (modelInfo?.context_length) {
1609
+ const ctx = modelInfo.context_length;
1610
+ outputTokenBudget = ctx >= 128_000 ? 8192 : ctx >= 64_000 ? 4096 : ctx >= 32_000 ? 2048 : 1024;
1611
+ if (process.argv.includes('--debug')) {
1612
+ console.log(` ${c.dim} Model context: ${ctx.toLocaleString()} tokens → max_tokens: ${outputTokenBudget}${c.reset}`);
1613
+ }
1614
+ }
1615
+ }
1616
+ } catch { /* ignore — use default */ }
1617
+ } else if (resolvedProvider.id === 'anthropic') {
1618
+ outputTokenBudget = 8192; // Claude models have 200k context
1619
+ } else if (resolvedProvider.id === 'openai') {
1620
+ outputTokenBudget = 8192; // GPT-4o has 128k context
1621
+ }
1622
+
1576
1623
  async function callLLM(codeContent, passLabel) {
1577
1624
  const systemPrompt = auditPrompt || 'You are a security auditor. Analyze the code and report findings as JSON.';
1578
1625
  const userMessage = [
@@ -1603,11 +1650,11 @@ async function auditRepo(url) {
1603
1650
  },
1604
1651
  body: JSON.stringify({
1605
1652
  model: modelOverride || 'claude-sonnet-4-20250514',
1606
- max_tokens: 8192,
1653
+ max_tokens: outputTokenBudget,
1607
1654
  system: systemPrompt,
1608
1655
  messages: [{ role: 'user', content: userMessage }],
1609
1656
  }),
1610
- signal: AbortSignal.timeout(120_000),
1657
+ signal: AbortSignal.timeout(180_000),
1611
1658
  });
1612
1659
  const data = await res.json();
1613
1660
  if (data.error) {
@@ -1651,13 +1698,13 @@ async function auditRepo(url) {
1651
1698
  headers: { 'Content-Type': 'application/json', ...authHeaders },
1652
1699
  body: JSON.stringify({
1653
1700
  model: modelName,
1654
- max_tokens: 8192,
1701
+ max_tokens: outputTokenBudget,
1655
1702
  messages: [
1656
1703
  { role: 'system', content: systemPrompt },
1657
1704
  { role: 'user', content: userMessage },
1658
1705
  ],
1659
1706
  }),
1660
- signal: AbortSignal.timeout(resolvedProvider.id === 'ollama' ? 300_000 : 120_000),
1707
+ signal: AbortSignal.timeout(resolvedProvider.id === 'ollama' ? 300_000 : 180_000),
1661
1708
  });
1662
1709
  const data = await res.json();
1663
1710
  if (data.error) {
@@ -1700,15 +1747,9 @@ async function auditRepo(url) {
1700
1747
  const result = await callLLM(chunks[i], `pass ${i + 1}`);
1701
1748
 
1702
1749
  if (result.error) {
1703
- console.log(` ${c.red}failed${c.reset}`);
1704
- const errMsg = result.error;
1705
- console.log(` ${c.red}API error: ${errMsg}${c.reset}`);
1706
- if (/context.length|maximum.*tokens|too.many.tokens/i.test(errMsg)) {
1707
- console.log(` ${c.dim}This model's context window is too small even for chunked analysis.${c.reset}`);
1708
- console.log(` ${c.dim}Try: --model anthropic/claude-sonnet-4 (200k) or --model openai/gpt-4o (128k)${c.reset}`);
1709
- }
1710
- try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
1711
- return null;
1750
+ console.log(` ${c.red}failed${c.reset} ${c.dim}(${result.error.slice(0, 80)})${c.reset}`);
1751
+ // Don't abort on individual pass failures — continue with remaining chunks
1752
+ continue;
1712
1753
  }
1713
1754
 
1714
1755
  if (!result.report) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentaudit",
3
- "version": "3.9.37",
3
+ "version": "3.9.39",
4
4
  "description": "Security scanner for AI packages — MCP server + CLI",
5
5
  "type": "module",
6
6
  "bin": {