agentaudit 3.12.8 โ 3.12.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.
- package/README.md +10 -10
- package/cli.mjs +469 -24
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
# ๐ก๏ธ AgentAudit
|
|
8
8
|
|
|
9
|
-
**Security scanner for AI packages โ MCP server
|
|
9
|
+
**Security scanner for AI agent packages โ CLI + MCP server**
|
|
10
10
|
|
|
11
11
|
Scan MCP servers, AI skills, and packages for vulnerabilities, prompt injection,
|
|
12
12
|
and supply chain attacks. Powered by regex static analysis and deep LLM audits.
|
|
13
13
|
|
|
14
|
-
[](https://www.agentaudit.dev/
|
|
14
|
+
[](https://www.agentaudit.dev/packages/agentaudit-mcp)
|
|
15
15
|
[](https://www.npmjs.com/package/agentaudit)
|
|
16
16
|
[](https://agentaudit.dev)
|
|
17
17
|
[](LICENSE)
|
|
@@ -77,7 +77,7 @@ agentaudit lookup fastmcp
|
|
|
77
77
|
|
|
78
78
|
**Example output:**
|
|
79
79
|
```
|
|
80
|
-
โจ AgentAudit v3.
|
|
80
|
+
โจ AgentAudit v3.12.9 โ my-scanner #3 ยท 280pts ยท 19 audits
|
|
81
81
|
|
|
82
82
|
Discovering MCP servers in your AI editors...
|
|
83
83
|
|
|
@@ -227,7 +227,7 @@ Then ask your agent: *"Check which MCP servers I have installed and audit any un
|
|
|
227
227
|
| Command | Alias | Description |
|
|
228
228
|
|---------|-------|-------------|
|
|
229
229
|
| `agentaudit model` | โ | Interactive LLM provider + model configuration |
|
|
230
|
-
| `agentaudit setup` |
|
|
230
|
+
| `agentaudit setup` | `login` | Sign in with GitHub OAuth or paste API key manually |
|
|
231
231
|
| `agentaudit status` | `whoami` | Show current config, API keys, and personal stats |
|
|
232
232
|
|
|
233
233
|
### Global Flags
|
|
@@ -481,7 +481,7 @@ agentaudit search fastmcp --json # Machine-readable search results
|
|
|
481
481
|
|
|
482
482
|
AgentAudit stores credentials in `~/.config/agentaudit/credentials.json` (or `$XDG_CONFIG_HOME/agentaudit/credentials.json`).
|
|
483
483
|
|
|
484
|
-
Run `agentaudit setup` to
|
|
484
|
+
Run `agentaudit setup` to sign in with GitHub or paste an API key, or set via environment:
|
|
485
485
|
|
|
486
486
|
```bash
|
|
487
487
|
export AGENTAUDIT_API_KEY=asf_your_key_here
|
|
@@ -595,10 +595,10 @@ It checks standard config file locations for Claude Desktop, Cursor, VS Code, an
|
|
|
595
595
|
| | Project | Description |
|
|
596
596
|
|---|---------|-------------|
|
|
597
597
|
| ๐ | [agentaudit.dev](https://agentaudit.dev) | Trust Registry -- browse packages, findings, leaderboard |
|
|
598
|
-
| ๐ก๏ธ | [agentaudit-skill](https://github.com/
|
|
599
|
-
| โก | [agentaudit-github-action](https://github.com/
|
|
600
|
-
| ๐ | [agentaudit-
|
|
601
|
-
| ๐ | [Report Issues](https://github.com/
|
|
598
|
+
| ๐ก๏ธ | [agentaudit-skill](https://github.com/agentaudit-dev/agentaudit-skill) | Agent Skill -- pre-install security gate for Claude Code, Cursor, Windsurf |
|
|
599
|
+
| โก | [agentaudit-github-action](https://github.com/agentaudit-dev/agentaudit-github-action) | GitHub Action -- CI/CD security scanning |
|
|
600
|
+
| ๐ | [agentaudit-cli](https://github.com/agentaudit-dev/agentaudit-cli) | This repo -- CLI + MCP server source |
|
|
601
|
+
| ๐ | [Report Issues](https://github.com/agentaudit-dev/agentaudit-cli/issues) | Bug reports and feature requests |
|
|
602
602
|
|
|
603
603
|
---
|
|
604
604
|
|
|
@@ -612,6 +612,6 @@ It checks standard config file locations for Claude Desktop, Cursor, VS Code, an
|
|
|
612
612
|
|
|
613
613
|
**Protect your AI stack. Scan before you trust.**
|
|
614
614
|
|
|
615
|
-
[Trust Registry](https://agentaudit.dev) ยท [Leaderboard](https://agentaudit.dev/leaderboard) ยท [Report Issues](https://github.com/
|
|
615
|
+
[Trust Registry](https://agentaudit.dev) ยท [Leaderboard](https://agentaudit.dev/leaderboard) ยท [Report Issues](https://github.com/agentaudit-dev/agentaudit-cli/issues)
|
|
616
616
|
|
|
617
617
|
</div>
|
package/cli.mjs
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* profile Your profile โ rank, points, audit stats
|
|
22
22
|
* help [command] Show help
|
|
23
23
|
*
|
|
24
|
-
* Flags: --json, --quiet, --no-color, --no-upload, --model, --export, --debug
|
|
24
|
+
* Flags: --json, --quiet, --no-color, --no-upload, --model, --export, --format, --debug
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
import fs from 'fs';
|
|
@@ -656,9 +656,14 @@ async function loginCommand() {
|
|
|
656
656
|
|
|
657
657
|
// Try to auto-open browser
|
|
658
658
|
try {
|
|
659
|
-
const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
660
659
|
const { exec } = await import('child_process');
|
|
661
|
-
|
|
660
|
+
if (process.platform === 'darwin') {
|
|
661
|
+
exec(`open "${verifyUrl}"`);
|
|
662
|
+
} else if (process.platform === 'win32') {
|
|
663
|
+
exec(`start "" "${verifyUrl}"`);
|
|
664
|
+
} else {
|
|
665
|
+
exec(`xdg-open "${verifyUrl}"`);
|
|
666
|
+
}
|
|
662
667
|
console.log(` ${c.dim}(Browser should open automatically)${c.reset}`);
|
|
663
668
|
} catch {}
|
|
664
669
|
|
|
@@ -2980,7 +2985,120 @@ function enrichFindings(report, files, pkgInfo) {
|
|
|
2980
2985
|
return report;
|
|
2981
2986
|
}
|
|
2982
2987
|
|
|
2988
|
+
// โโ SARIF 2.1.0 output โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
2989
|
+
|
|
2990
|
+
function toSarif(reports) {
|
|
2991
|
+
if (!reports || (Array.isArray(reports) && reports.length === 0)) {
|
|
2992
|
+
reports = [];
|
|
2993
|
+
}
|
|
2994
|
+
const version = getVersion();
|
|
2995
|
+
const LEVEL_MAP = { critical: 'error', high: 'error', medium: 'warning', low: 'note', info: 'note' };
|
|
2996
|
+
const SCORE_MAP = { critical: '9.5', high: '8.0', medium: '5.5', low: '2.0', info: '0.5' };
|
|
2997
|
+
const rules = [];
|
|
2998
|
+
const results = [];
|
|
2999
|
+
const ruleIndex = new Map();
|
|
3000
|
+
|
|
3001
|
+
for (const report of (Array.isArray(reports) ? reports : [reports]).filter(Boolean)) {
|
|
3002
|
+
for (const f of (report.findings || [])) {
|
|
3003
|
+
const ruleId = f.pattern_id || f.id || 'UNKNOWN';
|
|
3004
|
+
const sev = (f.severity || 'medium').toLowerCase();
|
|
3005
|
+
|
|
3006
|
+
if (!ruleIndex.has(ruleId)) {
|
|
3007
|
+
ruleIndex.set(ruleId, rules.length);
|
|
3008
|
+
const tags = ['security'];
|
|
3009
|
+
if (f.cwe_id) tags.push(f.cwe_id.toLowerCase());
|
|
3010
|
+
if (f.category) tags.push(f.category);
|
|
3011
|
+
rules.push({
|
|
3012
|
+
id: ruleId,
|
|
3013
|
+
shortDescription: { text: f.title || ruleId },
|
|
3014
|
+
fullDescription: { text: f.description || f.title || '' },
|
|
3015
|
+
helpUri: f.cwe_id
|
|
3016
|
+
? `https://cwe.mitre.org/data/definitions/${f.cwe_id.replace('CWE-', '')}.html`
|
|
3017
|
+
: `https://agentaudit.dev`,
|
|
3018
|
+
defaultConfiguration: { level: LEVEL_MAP[sev] || 'warning' },
|
|
3019
|
+
properties: { 'security-severity': SCORE_MAP[sev] || '5.5', tags },
|
|
3020
|
+
});
|
|
3021
|
+
}
|
|
3022
|
+
|
|
3023
|
+
const result = {
|
|
3024
|
+
ruleId,
|
|
3025
|
+
ruleIndex: ruleIndex.get(ruleId),
|
|
3026
|
+
level: LEVEL_MAP[sev] || 'warning',
|
|
3027
|
+
message: { text: [f.title, f.description].filter(Boolean).join(': ') },
|
|
3028
|
+
locations: [],
|
|
3029
|
+
};
|
|
3030
|
+
|
|
3031
|
+
const filePath = f.file || f.file_path;
|
|
3032
|
+
const lineNum = f.line || f.line_start;
|
|
3033
|
+
if (filePath) {
|
|
3034
|
+
const loc = {
|
|
3035
|
+
physicalLocation: {
|
|
3036
|
+
artifactLocation: { uri: filePath, uriBaseId: '%SRCROOT%' },
|
|
3037
|
+
},
|
|
3038
|
+
};
|
|
3039
|
+
if (lineNum) {
|
|
3040
|
+
loc.physicalLocation.region = { startLine: lineNum };
|
|
3041
|
+
}
|
|
3042
|
+
const snippet = f.content || f.snippet || f.code_snippet;
|
|
3043
|
+
if (snippet) {
|
|
3044
|
+
loc.physicalLocation.region = loc.physicalLocation.region || {};
|
|
3045
|
+
loc.physicalLocation.region.snippet = { text: snippet };
|
|
3046
|
+
}
|
|
3047
|
+
result.locations.push(loc);
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
if (f.remediation) {
|
|
3051
|
+
result.fixes = [{ description: { text: f.remediation } }];
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
if (f.by_design) {
|
|
3055
|
+
result.suppressions = [{ kind: 'inSource', justification: 'Marked as by-design' }];
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
if (filePath && lineNum) {
|
|
3059
|
+
const hash = crypto.createHash('sha256')
|
|
3060
|
+
.update(`${ruleId}:${filePath}:${lineNum}`)
|
|
3061
|
+
.digest('hex').slice(0, 16);
|
|
3062
|
+
result.partialFingerprints = { primaryLocationLineHash: hash };
|
|
3063
|
+
} else {
|
|
3064
|
+
// Fallback fingerprint from rule + title for findings without file/line
|
|
3065
|
+
const hash = crypto.createHash('sha256')
|
|
3066
|
+
.update(`${ruleId}:${f.title || ''}`)
|
|
3067
|
+
.digest('hex').slice(0, 16);
|
|
3068
|
+
result.partialFingerprints = { primaryLocationLineHash: hash };
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
results.push(result);
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
return {
|
|
3076
|
+
version: '2.1.0',
|
|
3077
|
+
$schema: 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json',
|
|
3078
|
+
runs: [{
|
|
3079
|
+
tool: {
|
|
3080
|
+
driver: {
|
|
3081
|
+
name: 'AgentAudit',
|
|
3082
|
+
semanticVersion: version,
|
|
3083
|
+
informationUri: 'https://agentaudit.dev',
|
|
3084
|
+
rules,
|
|
3085
|
+
},
|
|
3086
|
+
},
|
|
3087
|
+
results,
|
|
3088
|
+
}],
|
|
3089
|
+
};
|
|
3090
|
+
}
|
|
3091
|
+
|
|
2983
3092
|
async function auditRepo(url) {
|
|
3093
|
+
// In quiet mode (SARIF/JSON), redirect all progress output to stderr
|
|
3094
|
+
// so stdout only contains clean machine-readable data
|
|
3095
|
+
const _origConsoleLog = console.log;
|
|
3096
|
+
const _origStdoutWrite = process.stdout.write;
|
|
3097
|
+
if (quietMode) {
|
|
3098
|
+
console.log = console.error;
|
|
3099
|
+
process.stdout.write = process.stderr.write.bind(process.stderr);
|
|
3100
|
+
}
|
|
3101
|
+
try {
|
|
2984
3102
|
const start = Date.now();
|
|
2985
3103
|
|
|
2986
3104
|
// Support local directories
|
|
@@ -3278,15 +3396,52 @@ async function auditRepo(url) {
|
|
|
3278
3396
|
}
|
|
3279
3397
|
|
|
3280
3398
|
if (!activeLlm) {
|
|
3399
|
+
// Check if user is logged in โ offer remote scan as fallback
|
|
3400
|
+
const _creds = loadCredentials();
|
|
3401
|
+
if (_creds && process.stdin.isTTY && !process.argv.includes('--export')) {
|
|
3402
|
+
console.log();
|
|
3403
|
+
console.log(` ${c.yellow}No LLM API key configured.${c.reset}`);
|
|
3404
|
+
console.log();
|
|
3405
|
+
// Fetch quota for display
|
|
3406
|
+
let quotaLabel = '3/day free';
|
|
3407
|
+
try {
|
|
3408
|
+
const qr = await fetch(`${REGISTRY_URL}/api/scan`, {
|
|
3409
|
+
headers: { 'Authorization': `Bearer ${_creds.api_key}` },
|
|
3410
|
+
signal: AbortSignal.timeout(5_000),
|
|
3411
|
+
});
|
|
3412
|
+
if (qr.ok) {
|
|
3413
|
+
const q = await qr.json();
|
|
3414
|
+
quotaLabel = `${q.remaining}/${q.limit} free remaining`;
|
|
3415
|
+
}
|
|
3416
|
+
} catch {}
|
|
3417
|
+
console.log(` ${c.cyan}1${c.reset} Use agentaudit.dev ${c.dim}(${quotaLabel})${c.reset}`);
|
|
3418
|
+
console.log(` ${c.cyan}2${c.reset} Configure local LLM ${c.dim}(agentaudit model)${c.reset}`);
|
|
3419
|
+
console.log();
|
|
3420
|
+
const _choice = await askQuestion(` Choice ${c.dim}(1/2, default: 1):${c.reset} `);
|
|
3421
|
+
console.log();
|
|
3422
|
+
if (_choice.trim() === '2') {
|
|
3423
|
+
console.log(` ${c.dim}Run ${c.cyan}agentaudit model${c.dim} to configure your LLM provider and API key.${c.reset}`);
|
|
3424
|
+
console.log();
|
|
3425
|
+
return null;
|
|
3426
|
+
}
|
|
3427
|
+
// Default: remote audit
|
|
3428
|
+
return await remoteAudit(url);
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
// Not logged in or non-interactive
|
|
3281
3432
|
console.log();
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3433
|
+
if (!_creds) {
|
|
3434
|
+
console.log(` ${c.yellow}No LLM API key found.${c.reset} To run a deep audit, you need either:`);
|
|
3435
|
+
console.log();
|
|
3436
|
+
console.log(` ${c.bold}1.${c.reset} An LLM API key: ${c.cyan}agentaudit model${c.reset}`);
|
|
3437
|
+
console.log(` ${c.bold}2.${c.reset} A free account: ${c.cyan}agentaudit login${c.reset} ${c.dim}(3 free remote scans/day)${c.reset}`);
|
|
3438
|
+
} else {
|
|
3439
|
+
console.log(` ${c.yellow}No LLM API key found.${c.reset} The ${c.bold}audit${c.reset} command needs an LLM to analyze code.`);
|
|
3440
|
+
console.log();
|
|
3441
|
+
console.log(` ${c.bold}Set an API key${c.reset} (e.g. ${c.cyan}export OPENROUTER_API_KEY=sk-or-...${c.reset})`);
|
|
3442
|
+
console.log(` ${c.dim}Run "agentaudit model" to configure provider + model interactively${c.reset}`);
|
|
3443
|
+
console.log(` ${c.dim}Or use ${c.cyan}agentaudit audit ${url} --remote${c.dim} for a free server-side scan${c.reset}`);
|
|
3444
|
+
}
|
|
3290
3445
|
console.log();
|
|
3291
3446
|
if (process.argv.includes('--export')) {
|
|
3292
3447
|
const exportPath = path.join(process.cwd(), `audit-${slug}.md`);
|
|
@@ -3404,6 +3559,215 @@ async function auditRepo(url) {
|
|
|
3404
3559
|
|
|
3405
3560
|
console.log();
|
|
3406
3561
|
return report;
|
|
3562
|
+
|
|
3563
|
+
} finally {
|
|
3564
|
+
console.log = _origConsoleLog;
|
|
3565
|
+
process.stdout.write = _origStdoutWrite;
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3569
|
+
// โโ Remote Audit (server-side free scan via SSE) โโโโโโโโ
|
|
3570
|
+
|
|
3571
|
+
async function remoteAudit(url) {
|
|
3572
|
+
// 1. Check credentials
|
|
3573
|
+
const creds = loadCredentials();
|
|
3574
|
+
if (!creds) {
|
|
3575
|
+
console.log();
|
|
3576
|
+
console.log(` ${c.red}Not logged in.${c.reset} Remote scans require an agentaudit.dev account.`);
|
|
3577
|
+
console.log(` ${c.dim}Run ${c.cyan}agentaudit login${c.dim} to sign in (free).${c.reset}`);
|
|
3578
|
+
console.log();
|
|
3579
|
+
return null;
|
|
3580
|
+
}
|
|
3581
|
+
|
|
3582
|
+
const authHeaders = { 'Authorization': `Bearer ${creds.api_key}`, 'Content-Type': 'application/json' };
|
|
3583
|
+
|
|
3584
|
+
// 2. Check quota
|
|
3585
|
+
if (!quietMode) {
|
|
3586
|
+
try {
|
|
3587
|
+
const quotaRes = await fetch(`${REGISTRY_URL}/api/scan`, {
|
|
3588
|
+
headers: authHeaders,
|
|
3589
|
+
signal: AbortSignal.timeout(10_000),
|
|
3590
|
+
});
|
|
3591
|
+
if (quotaRes.ok) {
|
|
3592
|
+
const quota = await quotaRes.json();
|
|
3593
|
+
if (quota.remaining <= 0) {
|
|
3594
|
+
console.log();
|
|
3595
|
+
console.log(` ${c.red}Rate limit reached${c.reset} โ 0 of ${quota.limit} free remote scans remaining.`);
|
|
3596
|
+
console.log(` ${c.dim}Configure a local LLM for unlimited scans: ${c.cyan}agentaudit model${c.reset}`);
|
|
3597
|
+
console.log();
|
|
3598
|
+
return null;
|
|
3599
|
+
}
|
|
3600
|
+
console.log(` ${c.dim}Remote scans: ${quota.remaining} of ${quota.limit} remaining today${c.reset}`);
|
|
3601
|
+
}
|
|
3602
|
+
} catch {
|
|
3603
|
+
// Quota check failed โ continue, the POST will catch it
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
|
|
3607
|
+
// 3. Start SSE stream
|
|
3608
|
+
if (!quietMode) {
|
|
3609
|
+
console.log();
|
|
3610
|
+
console.log(sectionHeader('Remote Audit'));
|
|
3611
|
+
console.log(` ${c.dim}Server: ${REGISTRY_URL} โข Model: Gemini 2.5 Flash${c.reset}`);
|
|
3612
|
+
console.log();
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3615
|
+
const startTime = Date.now();
|
|
3616
|
+
let report = null;
|
|
3617
|
+
|
|
3618
|
+
try {
|
|
3619
|
+
const res = await fetch(`${REGISTRY_URL}/api/scan`, {
|
|
3620
|
+
method: 'POST',
|
|
3621
|
+
headers: authHeaders,
|
|
3622
|
+
body: JSON.stringify({ url }),
|
|
3623
|
+
signal: AbortSignal.timeout(90_000),
|
|
3624
|
+
});
|
|
3625
|
+
|
|
3626
|
+
if (!res.ok) {
|
|
3627
|
+
let errBody;
|
|
3628
|
+
try { errBody = await res.json(); } catch { errBody = { error: `HTTP ${res.status}` }; }
|
|
3629
|
+
console.log(` ${c.red}${errBody.message || errBody.error || `Server error (${res.status})`}${c.reset}`);
|
|
3630
|
+
console.log();
|
|
3631
|
+
return null;
|
|
3632
|
+
}
|
|
3633
|
+
|
|
3634
|
+
// 4. Parse SSE stream
|
|
3635
|
+
const reader = res.body.getReader();
|
|
3636
|
+
const decoder = new TextDecoder();
|
|
3637
|
+
let buffer = '';
|
|
3638
|
+
const findings = [];
|
|
3639
|
+
let currentStep = '';
|
|
3640
|
+
|
|
3641
|
+
while (true) {
|
|
3642
|
+
const { done, value } = await reader.read();
|
|
3643
|
+
if (done) break;
|
|
3644
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3645
|
+
|
|
3646
|
+
const parts = buffer.split('\n\n');
|
|
3647
|
+
buffer = parts.pop(); // keep incomplete chunk
|
|
3648
|
+
|
|
3649
|
+
for (const part of parts) {
|
|
3650
|
+
const eventMatch = part.match(/^event:\s*(.+)/m);
|
|
3651
|
+
const dataMatch = part.match(/^data:\s*(.+)/m);
|
|
3652
|
+
if (!eventMatch || !dataMatch) continue;
|
|
3653
|
+
|
|
3654
|
+
const event = eventMatch[1].trim();
|
|
3655
|
+
let data;
|
|
3656
|
+
try { data = JSON.parse(dataMatch[1]); } catch { continue; }
|
|
3657
|
+
|
|
3658
|
+
switch (event) {
|
|
3659
|
+
case 'step': {
|
|
3660
|
+
if (quietMode) break;
|
|
3661
|
+
const icon = data.status === 'done' ? `${c.green}โ${c.reset}` : `${c.cyan}โ${c.reset}`;
|
|
3662
|
+
const detail = data.detail ? ` ${c.dim}(${data.detail})${c.reset}` : '';
|
|
3663
|
+
// Clear previous line if updating same step
|
|
3664
|
+
if (currentStep && data.status === 'done') {
|
|
3665
|
+
process.stdout.write(`\r\x1b[K`);
|
|
3666
|
+
}
|
|
3667
|
+
if (data.status === 'done') {
|
|
3668
|
+
console.log(` ${icon} ${data.label}${detail}`);
|
|
3669
|
+
currentStep = '';
|
|
3670
|
+
} else {
|
|
3671
|
+
process.stdout.write(`\r ${icon} ${data.label}${detail}`);
|
|
3672
|
+
currentStep = data.label;
|
|
3673
|
+
}
|
|
3674
|
+
break;
|
|
3675
|
+
}
|
|
3676
|
+
|
|
3677
|
+
case 'finding': {
|
|
3678
|
+
findings.push(data);
|
|
3679
|
+
break;
|
|
3680
|
+
}
|
|
3681
|
+
|
|
3682
|
+
case 'cached': {
|
|
3683
|
+
if (!quietMode) {
|
|
3684
|
+
console.log(` ${c.cyan}โน${c.reset} Using cached result from ${c.bold}${data.scanned_ago}${c.reset}`);
|
|
3685
|
+
}
|
|
3686
|
+
break;
|
|
3687
|
+
}
|
|
3688
|
+
|
|
3689
|
+
case 'result': {
|
|
3690
|
+
report = {
|
|
3691
|
+
cached: data.cached,
|
|
3692
|
+
result: data.result,
|
|
3693
|
+
risk_score: data.risk_score,
|
|
3694
|
+
trust_score: data.trust_score,
|
|
3695
|
+
findings_count: data.findings_count,
|
|
3696
|
+
max_severity: data.max_severity,
|
|
3697
|
+
slug: data.slug,
|
|
3698
|
+
url: data.url,
|
|
3699
|
+
findings: findings,
|
|
3700
|
+
audit_model: 'google/gemini-2.5-flash',
|
|
3701
|
+
audit_provider: 'agentaudit.dev',
|
|
3702
|
+
source_url: url,
|
|
3703
|
+
skill_slug: data.slug,
|
|
3704
|
+
audit_duration_ms: Date.now() - startTime,
|
|
3705
|
+
};
|
|
3706
|
+
break;
|
|
3707
|
+
}
|
|
3708
|
+
|
|
3709
|
+
case 'error': {
|
|
3710
|
+
if (currentStep) {
|
|
3711
|
+
process.stdout.write(`\r\x1b[K`);
|
|
3712
|
+
currentStep = '';
|
|
3713
|
+
}
|
|
3714
|
+
console.log(` ${c.red}${data.message || 'Server error'}${c.reset}`);
|
|
3715
|
+
break;
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3718
|
+
case 'done':
|
|
3719
|
+
break;
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
} catch (err) {
|
|
3724
|
+
if (err.name === 'TimeoutError' || err.name === 'AbortError') {
|
|
3725
|
+
console.log(` ${c.red}Timeout โ server took too long to respond.${c.reset}`);
|
|
3726
|
+
} else {
|
|
3727
|
+
console.log(` ${c.red}Connection error: ${err.message}${c.reset}`);
|
|
3728
|
+
}
|
|
3729
|
+
console.log();
|
|
3730
|
+
return null;
|
|
3731
|
+
}
|
|
3732
|
+
|
|
3733
|
+
if (!report) {
|
|
3734
|
+
console.log(` ${c.red}No result received from server.${c.reset}`);
|
|
3735
|
+
console.log();
|
|
3736
|
+
return null;
|
|
3737
|
+
}
|
|
3738
|
+
|
|
3739
|
+
// 5. Display results
|
|
3740
|
+
if (!quietMode) {
|
|
3741
|
+
console.log();
|
|
3742
|
+
console.log(sectionHeader('Result'));
|
|
3743
|
+
console.log(` ${riskBadge(report.risk_score || 0)}`);
|
|
3744
|
+
console.log();
|
|
3745
|
+
|
|
3746
|
+
if (findings.length > 0) {
|
|
3747
|
+
console.log(sectionHeader(`Findings (${findings.length})`));
|
|
3748
|
+
console.log();
|
|
3749
|
+
for (const f of findings) {
|
|
3750
|
+
const sc = severityColor(f.severity);
|
|
3751
|
+
console.log(` ${sc}โ${c.reset} ${sc}${(f.severity || '').toUpperCase().padEnd(8)}${c.reset} ${c.bold}${f.title}${c.reset}`);
|
|
3752
|
+
if (f.file) console.log(` ${sc}โ${c.reset} ${c.dim}${f.file}${f.line ? ':' + f.line : ''}${c.reset}`);
|
|
3753
|
+
console.log();
|
|
3754
|
+
}
|
|
3755
|
+
} else {
|
|
3756
|
+
console.log(` ${c.green}No findings โ package looks clean.${c.reset}`);
|
|
3757
|
+
console.log();
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
console.log(` ${c.dim}Report: ${REGISTRY_URL}/packages/${report.slug}${c.reset}`);
|
|
3761
|
+
console.log(` ${c.dim}Duration: ${elapsed(startTime)}${c.reset}`);
|
|
3762
|
+
console.log();
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3765
|
+
// JSON output
|
|
3766
|
+
if (jsonMode && !quietMode) {
|
|
3767
|
+
console.log(JSON.stringify(report, null, 2));
|
|
3768
|
+
}
|
|
3769
|
+
|
|
3770
|
+
return report;
|
|
3407
3771
|
}
|
|
3408
3772
|
|
|
3409
3773
|
// โโ Check command โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -4281,16 +4645,32 @@ async function main() {
|
|
|
4281
4645
|
jsonMode = rawArgs.includes('--json');
|
|
4282
4646
|
quietMode = rawArgs.includes('--quiet') || rawArgs.includes('-q');
|
|
4283
4647
|
// --no-color already handled at top level for `c` object
|
|
4284
|
-
|
|
4285
|
-
// Strip global flags from args (including --model <value>)
|
|
4286
|
-
const globalFlags = new Set(['--json', '--quiet', '-q', '--no-color', '--no-upload']);
|
|
4648
|
+
|
|
4649
|
+
// Strip global flags from args (including --model <value>, --format <value>)
|
|
4650
|
+
const globalFlags = new Set(['--json', '--quiet', '-q', '--no-color', '--no-upload', '--remote']);
|
|
4287
4651
|
let args = rawArgs.filter(a => !globalFlags.has(a));
|
|
4288
4652
|
// Remove --model <value> and --models <value> pairs
|
|
4289
4653
|
const modelIdx = args.indexOf('--model');
|
|
4290
4654
|
if (modelIdx !== -1) args.splice(modelIdx, 2);
|
|
4291
4655
|
const modelsIdx = args.indexOf('--models');
|
|
4292
4656
|
if (modelsIdx !== -1) args.splice(modelsIdx, 2);
|
|
4293
|
-
|
|
4657
|
+
// Remove --format <value> pair
|
|
4658
|
+
const formatIdx = args.indexOf('--format');
|
|
4659
|
+
const formatFlag = formatIdx !== -1 ? args.splice(formatIdx, 2)[1] : null;
|
|
4660
|
+
// --json is alias for --format json
|
|
4661
|
+
const outputFormat = formatFlag || (jsonMode ? 'json' : null);
|
|
4662
|
+
// Validate --format value
|
|
4663
|
+
if (outputFormat && !['json', 'sarif'].includes(outputFormat)) {
|
|
4664
|
+
console.error(` ${c.red}Unknown format: ${outputFormat}${c.reset}`);
|
|
4665
|
+
console.error(` ${c.dim}Supported formats: json, sarif${c.reset}`);
|
|
4666
|
+
process.exitCode = 2; return;
|
|
4667
|
+
}
|
|
4668
|
+
// SARIF mode: suppress console output so only clean JSON goes to stdout
|
|
4669
|
+
if (outputFormat === 'sarif') { quietMode = true; jsonMode = true; }
|
|
4670
|
+
|
|
4671
|
+
// --remote: use server-side scan instead of local LLM
|
|
4672
|
+
const remoteFlag = rawArgs.includes('--remote');
|
|
4673
|
+
|
|
4294
4674
|
// Detect per-command --help BEFORE stripping (e.g. `agentaudit model --help`)
|
|
4295
4675
|
const wantsHelp = args.includes('--help') || args.includes('-h');
|
|
4296
4676
|
// Strip --help/-h from args for routing
|
|
@@ -4326,29 +4706,37 @@ async function main() {
|
|
|
4326
4706
|
`(command injection, eval, hardcoded secrets, path traversal, etc.)`,
|
|
4327
4707
|
``,
|
|
4328
4708
|
`${c.bold}Options:${c.reset}`,
|
|
4329
|
-
` --deep
|
|
4709
|
+
` --deep Run deep LLM audit instead (same as \`agentaudit audit\`)`,
|
|
4710
|
+
` --remote Use agentaudit.dev server for --deep (no LLM key needed)`,
|
|
4711
|
+
` --format sarif Output results as SARIF 2.1.0 (for GitHub Code Scanning)`,
|
|
4330
4712
|
``,
|
|
4331
4713
|
`${c.bold}Examples:${c.reset}`,
|
|
4332
4714
|
` agentaudit scan https://github.com/owner/repo`,
|
|
4333
4715
|
` agentaudit scan https://github.com/a/b https://github.com/c/d`,
|
|
4334
4716
|
` agentaudit scan https://github.com/owner/repo --deep`,
|
|
4717
|
+
` agentaudit scan https://github.com/owner/repo --deep --remote`,
|
|
4718
|
+
` agentaudit scan https://github.com/owner/repo --format sarif > results.sarif`,
|
|
4335
4719
|
],
|
|
4336
4720
|
audit: [
|
|
4337
4721
|
`${c.bold}agentaudit audit${c.reset} <url> [url...] [options]`,
|
|
4338
4722
|
``,
|
|
4339
|
-
`Deep LLM-powered 3-pass security audit (~30s)
|
|
4723
|
+
`Deep LLM-powered 3-pass security audit (~30s).`,
|
|
4340
4724
|
``,
|
|
4341
4725
|
`${c.bold}Options:${c.reset}`,
|
|
4726
|
+
` --remote Use agentaudit.dev server (no LLM key needed, 3/day free)`,
|
|
4342
4727
|
` --model <name> Override LLM model for this run`,
|
|
4343
4728
|
` --models <a,b,c> Multi-model audit (parallel calls, consensus comparison)`,
|
|
4344
4729
|
` --no-upload Skip uploading report to registry`,
|
|
4345
4730
|
` --export Export audit payload as markdown (for manual LLM review)`,
|
|
4731
|
+
` --format sarif Output results as SARIF 2.1.0 (for GitHub Code Scanning)`,
|
|
4346
4732
|
` --debug Show raw LLM response on parse errors`,
|
|
4347
4733
|
``,
|
|
4348
4734
|
`${c.bold}Examples:${c.reset}`,
|
|
4349
4735
|
` agentaudit audit https://github.com/owner/repo`,
|
|
4736
|
+
` agentaudit audit https://github.com/owner/repo --remote`,
|
|
4350
4737
|
` agentaudit audit https://github.com/owner/repo --model gpt-4o`,
|
|
4351
4738
|
` agentaudit audit https://github.com/owner/repo --models gemini-2.5-flash,claude-sonnet-4-20250514`,
|
|
4739
|
+
` agentaudit audit https://github.com/owner/repo --format sarif > results.sarif`,
|
|
4352
4740
|
` agentaudit audit https://github.com/owner/repo --export`,
|
|
4353
4741
|
],
|
|
4354
4742
|
lookup: [
|
|
@@ -4790,6 +5178,20 @@ async function main() {
|
|
|
4790
5178
|
if (creds) {
|
|
4791
5179
|
console.log(` Account ${c.bold}${creds.agent_name}${c.reset} ${c.green}โ logged in${c.reset}`);
|
|
4792
5180
|
console.log(` ${c.dim} Key: ${creds.api_key.slice(0, 12)}...${c.reset}`);
|
|
5181
|
+
// Remote scan quota
|
|
5182
|
+
try {
|
|
5183
|
+
const quotaRes = await fetch(`${REGISTRY_URL}/api/scan`, {
|
|
5184
|
+
headers: { 'Authorization': `Bearer ${creds.api_key}` },
|
|
5185
|
+
signal: AbortSignal.timeout(5_000),
|
|
5186
|
+
});
|
|
5187
|
+
if (quotaRes.ok) {
|
|
5188
|
+
const quota = await quotaRes.json();
|
|
5189
|
+
const resetLabel = quota.resets_in_ms
|
|
5190
|
+
? ` ${c.dim}(resets in ${Math.ceil(quota.resets_in_ms / 3600000)}h)${c.reset}`
|
|
5191
|
+
: '';
|
|
5192
|
+
console.log(` Remote ${c.bold}${quota.remaining}${c.reset} of ${quota.limit} free scans remaining${resetLabel}`);
|
|
5193
|
+
}
|
|
5194
|
+
} catch {}
|
|
4793
5195
|
} else {
|
|
4794
5196
|
console.log(` Account ${c.yellow}not configured${c.reset} ${c.dim}โ run ${c.cyan}agentaudit setup${c.dim} to create one${c.reset}`);
|
|
4795
5197
|
}
|
|
@@ -5274,12 +5676,23 @@ async function main() {
|
|
|
5274
5676
|
return;
|
|
5275
5677
|
}
|
|
5276
5678
|
|
|
5277
|
-
// --deep redirects to audit flow
|
|
5679
|
+
// --deep redirects to audit flow (--remote supported)
|
|
5278
5680
|
if (deepFlag) {
|
|
5681
|
+
const auditFn = remoteFlag ? remoteAudit : auditRepo;
|
|
5279
5682
|
let hasFindings = false;
|
|
5683
|
+
const allReports = [];
|
|
5280
5684
|
for (const url of urls) {
|
|
5281
|
-
const report = await
|
|
5282
|
-
if (report
|
|
5685
|
+
const report = await auditFn(url);
|
|
5686
|
+
if (Array.isArray(report)) {
|
|
5687
|
+
allReports.push(...report.filter(Boolean));
|
|
5688
|
+
if (report.some(r => r?.findings?.length > 0)) hasFindings = true;
|
|
5689
|
+
} else if (report) {
|
|
5690
|
+
allReports.push(report);
|
|
5691
|
+
if (report.findings?.length > 0) hasFindings = true;
|
|
5692
|
+
}
|
|
5693
|
+
}
|
|
5694
|
+
if (outputFormat === 'sarif') {
|
|
5695
|
+
console.log(JSON.stringify(toSarif(allReports), null, 2));
|
|
5283
5696
|
}
|
|
5284
5697
|
process.exitCode = hasFindings ? 1 : 0;
|
|
5285
5698
|
return;
|
|
@@ -5293,7 +5706,12 @@ async function main() {
|
|
|
5293
5706
|
else hadErrors = true;
|
|
5294
5707
|
}
|
|
5295
5708
|
|
|
5296
|
-
if (
|
|
5709
|
+
if (outputFormat === 'sarif') {
|
|
5710
|
+
const sarif = toSarif(results.map(r => ({
|
|
5711
|
+
findings: (r.findings || []).map(f => ({ ...f, pattern_id: f.id })),
|
|
5712
|
+
})));
|
|
5713
|
+
console.log(JSON.stringify(sarif, null, 2));
|
|
5714
|
+
} else if (jsonMode || outputFormat === 'json') {
|
|
5297
5715
|
const jsonOut = results.map(r => ({
|
|
5298
5716
|
slug: r.slug,
|
|
5299
5717
|
url: r.url,
|
|
@@ -5327,17 +5745,44 @@ async function main() {
|
|
|
5327
5745
|
process.exitCode = 2;
|
|
5328
5746
|
return;
|
|
5329
5747
|
}
|
|
5330
|
-
|
|
5748
|
+
|
|
5749
|
+
// --remote: use server-side scan
|
|
5750
|
+
if (remoteFlag) {
|
|
5751
|
+
let hasFindings = false;
|
|
5752
|
+
const allReports = [];
|
|
5753
|
+
for (const url of urls) {
|
|
5754
|
+
const result = await remoteAudit(url);
|
|
5755
|
+
if (result) {
|
|
5756
|
+
allReports.push(result);
|
|
5757
|
+
if (result.findings?.length > 0) hasFindings = true;
|
|
5758
|
+
}
|
|
5759
|
+
}
|
|
5760
|
+
if (outputFormat === 'sarif') {
|
|
5761
|
+
console.log(JSON.stringify(toSarif(allReports), null, 2));
|
|
5762
|
+
}
|
|
5763
|
+
process.exitCode = hasFindings ? 1 : 0;
|
|
5764
|
+
return;
|
|
5765
|
+
}
|
|
5766
|
+
|
|
5331
5767
|
let hasFindings = false;
|
|
5768
|
+
const allReports = [];
|
|
5332
5769
|
for (const url of urls) {
|
|
5333
5770
|
const result = await auditRepo(url);
|
|
5334
5771
|
// Multi-model returns array, single-model returns object
|
|
5335
5772
|
if (Array.isArray(result)) {
|
|
5773
|
+
allReports.push(...result.filter(Boolean));
|
|
5336
5774
|
if (result.some(r => r?.findings?.length > 0)) hasFindings = true;
|
|
5337
|
-
} else if (result
|
|
5338
|
-
|
|
5775
|
+
} else if (result) {
|
|
5776
|
+
allReports.push(result);
|
|
5777
|
+
if (result.findings?.length > 0) hasFindings = true;
|
|
5339
5778
|
}
|
|
5340
5779
|
}
|
|
5780
|
+
|
|
5781
|
+
if (outputFormat === 'sarif') {
|
|
5782
|
+
const sarif = toSarif(allReports);
|
|
5783
|
+
console.log(JSON.stringify(sarif, null, 2));
|
|
5784
|
+
}
|
|
5785
|
+
|
|
5341
5786
|
process.exitCode = hasFindings ? 1 : 0;
|
|
5342
5787
|
return;
|
|
5343
5788
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentaudit",
|
|
3
|
-
"version": "3.12.
|
|
4
|
-
"description": "Security scanner for AI packages โ MCP server
|
|
3
|
+
"version": "3.12.10",
|
|
4
|
+
"description": "Security scanner for AI agent packages โ CLI + MCP server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"agentaudit": "cli.mjs"
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"license": "AGPL-3.0",
|
|
40
40
|
"repository": {
|
|
41
41
|
"type": "git",
|
|
42
|
-
"url": "git+https://github.com/agentaudit-dev/agentaudit-
|
|
42
|
+
"url": "git+https://github.com/agentaudit-dev/agentaudit-cli.git"
|
|
43
43
|
},
|
|
44
44
|
"homepage": "https://agentaudit.dev",
|
|
45
45
|
"engines": {
|