agentaudit 3.9.10 → 3.9.11

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 +37 -28
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -343,38 +343,47 @@ function extractJSON(text) {
343
343
  // 1. Try parsing the entire text as JSON directly
344
344
  try { return JSON.parse(text.trim()); } catch {}
345
345
 
346
- // 2. Strip markdown code fences (```json ... ``` or ``` ... ```)
347
- const fenceMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
348
- if (fenceMatch) {
349
- try { return JSON.parse(fenceMatch[1].trim()); } catch {}
346
+ // 2. Strip markdown code fences — try last fence first (report is usually at the end)
347
+ const fenceMatches = [...text.matchAll(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/g)];
348
+ for (let i = fenceMatches.length - 1; i >= 0; i--) {
349
+ try {
350
+ const parsed = JSON.parse(fenceMatches[i][1].trim());
351
+ if (parsed && typeof parsed === 'object' && ('risk_score' in parsed || 'findings' in parsed || 'result' in parsed)) return parsed;
352
+ } catch {}
353
+ }
354
+ // Try any fence even without report keys
355
+ for (let i = fenceMatches.length - 1; i >= 0; i--) {
356
+ try { return JSON.parse(fenceMatches[i][1].trim()); } catch {}
350
357
  }
351
358
 
352
- // 3. Find the first balanced top-level { ... } block
353
- const start = text.indexOf('{');
354
- if (start === -1) return null;
355
- let depth = 0;
356
- let inString = false;
357
- let escape = false;
358
- for (let i = start; i < text.length; i++) {
359
- const ch = text[i];
360
- if (escape) { escape = false; continue; }
361
- if (ch === '\\' && inString) { escape = true; continue; }
362
- if (ch === '"') { inString = !inString; continue; }
363
- if (inString) continue;
364
- if (ch === '{') depth++;
365
- else if (ch === '}') {
366
- depth--;
367
- if (depth === 0) {
368
- try { return JSON.parse(text.slice(start, i + 1)); } catch {}
369
- break;
370
- }
359
+ // 3. Find ALL balanced top-level { ... } blocks, try each (prefer largest valid one)
360
+ const blocks = [];
361
+ let searchFrom = 0;
362
+ while (searchFrom < text.length) {
363
+ const start = text.indexOf('{', searchFrom);
364
+ if (start === -1) break;
365
+ let depth = 0, inStr = false, esc = false;
366
+ let end = -1;
367
+ for (let i = start; i < text.length; i++) {
368
+ const ch = text[i];
369
+ if (esc) { esc = false; continue; }
370
+ if (ch === '\\' && inStr) { esc = true; continue; }
371
+ if (ch === '"') { inStr = !inStr; continue; }
372
+ if (inStr) continue;
373
+ if (ch === '{') depth++;
374
+ else if (ch === '}') { depth--; if (depth === 0) { end = i; break; } }
375
+ }
376
+ if (end > start) {
377
+ blocks.push(text.slice(start, end + 1));
378
+ searchFrom = end + 1;
379
+ } else {
380
+ searchFrom = start + 1;
371
381
  }
372
382
  }
373
-
374
- // 4. Last resort: greedy match
375
- const greedy = text.match(/\{[\s\S]*\}/);
376
- if (greedy) {
377
- try { return JSON.parse(greedy[0]); } catch {}
383
+ // Try largest block first (the report JSON is usually the biggest)
384
+ blocks.sort((a, b) => b.length - a.length);
385
+ for (const block of blocks) {
386
+ try { return JSON.parse(block); } catch {}
378
387
  }
379
388
 
380
389
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentaudit",
3
- "version": "3.9.10",
3
+ "version": "3.9.11",
4
4
  "description": "Security scanner for AI packages — MCP server + CLI",
5
5
  "type": "module",
6
6
  "bin": {