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.
- package/cli.mjs +24 -6
- 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)
|
|
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
|
-
|
|
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
|
|
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
|
|
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);
|