agentaudit 3.9.41 → 3.9.42

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 +24 -6
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -558,18 +558,22 @@ const SKIP_FILES = new Set([
558
558
  '.prettierignore', '.eslintignore',
559
559
  ]);
560
560
 
561
- function collectFiles(dir, basePath = '', collected = [], totalSize = { bytes: 0 }) {
562
- if (totalSize.bytes >= MAX_TOTAL_SIZE) return collected;
561
+ function collectFiles(dir, basePath = '', collected = [], totalSize = { bytes: 0, truncated: false, skippedPaths: [] }) {
562
+ if (totalSize.bytes >= MAX_TOTAL_SIZE) { totalSize.truncated = true; return collected; }
563
563
  let entries;
564
564
  try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
565
565
  catch { return collected; }
566
566
  entries.sort((a, b) => a.name.localeCompare(b.name));
567
567
  for (const entry of entries) {
568
- if (totalSize.bytes >= MAX_TOTAL_SIZE) break;
568
+ if (totalSize.bytes >= MAX_TOTAL_SIZE) { totalSize.truncated = true; totalSize.skippedPaths.push(relPath); continue; }
569
569
  const relPath = basePath ? `${basePath}/${entry.name}` : entry.name;
570
570
  const fullPath = path.join(dir, entry.name);
571
+ // SECURITY: Never follow symlinks — attacker could link to /etc/passwd or ~/.ssh/
572
+ if (entry.isSymbolicLink()) continue;
571
573
  if (entry.isDirectory()) {
572
- if (SKIP_DIRS.has(entry.name) || entry.name.startsWith('.')) continue;
574
+ // Allow .github (workflow security), skip other dot-dirs (editor/system config)
575
+ if (SKIP_DIRS.has(entry.name)) continue;
576
+ if (entry.name.startsWith('.') && entry.name !== '.github') continue;
573
577
  collectFiles(fullPath, relPath, collected, totalSize);
574
578
  } else {
575
579
  const ext = path.extname(entry.name).toLowerCase();
@@ -1457,8 +1461,15 @@ async function auditRepo(url) {
1457
1461
 
1458
1462
  // Step 2: Collect files
1459
1463
  process.stdout.write(` ${c.dim}[2/4]${c.reset} Collecting source files...`);
1460
- const files = collectFiles(repoPath);
1464
+ const _collectMeta = { bytes: 0, truncated: false, skippedPaths: [] };
1465
+ const files = collectFiles(repoPath, '', [], _collectMeta);
1461
1466
  console.log(` ${c.green}${files.length} files${c.reset}`);
1467
+ if (_collectMeta.truncated) {
1468
+ console.log(` ${c.yellow}⚠ Size limit reached (${(MAX_TOTAL_SIZE / 1000).toFixed(0)}KB) — ${_collectMeta.skippedPaths.length} files NOT collected:${c.reset}`);
1469
+ const shown = _collectMeta.skippedPaths.slice(0, 5);
1470
+ for (const p of shown) console.log(` ${c.dim} • ${p}${c.reset}`);
1471
+ if (_collectMeta.skippedPaths.length > 5) console.log(` ${c.dim} ... and ${_collectMeta.skippedPaths.length - 5} more${c.reset}`);
1472
+ }
1462
1473
 
1463
1474
  // Step 3: Build audit payload
1464
1475
  process.stdout.write(` ${c.dim}[3/4]${c.reset} Preparing audit payload...`);
@@ -1469,12 +1480,19 @@ async function auditRepo(url) {
1469
1480
  // ~15k tokens per chunk for code → fits comfortably in 32k+ context models
1470
1481
  // with room for system prompt (~2k tokens) + output (4k tokens)
1471
1482
  const MAX_CHUNK_CHARS = 60_000;
1483
+ // Sort files by directory to keep related files in the same chunk.
1484
+ // This preserves cross-file context (imports, shared modules) within each pass.
1485
+ const sortedFiles = [...files].sort((a, b) => {
1486
+ const dirA = a.path.includes('/') ? a.path.substring(0, a.path.lastIndexOf('/')) : '';
1487
+ const dirB = b.path.includes('/') ? b.path.substring(0, b.path.lastIndexOf('/')) : '';
1488
+ return dirA.localeCompare(dirB) || a.path.localeCompare(b.path);
1489
+ });
1472
1490
  const chunks = []; // array of code block strings
1473
1491
  const chunkFileNames = []; // track which files are in each chunk for error reporting
1474
1492
  let currentChunk = '';
1475
1493
  let currentChars = 0;
1476
1494
  let currentFiles = [];
1477
- for (const file of files) {
1495
+ for (const file of sortedFiles) {
1478
1496
  const entry = `\n### FILE: ${file.path}\n\`\`\`\n${file.content}\n\`\`\`\n`;
1479
1497
  if (currentChars + entry.length > MAX_CHUNK_CHARS && currentChars > 0) {
1480
1498
  chunks.push(currentChunk);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentaudit",
3
- "version": "3.9.41",
3
+ "version": "3.9.42",
4
4
  "description": "Security scanner for AI packages — MCP server + CLI",
5
5
  "type": "module",
6
6
  "bin": {