agentverify 1.1.0 → 1.3.0
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 +36 -4
- package/dist/{chunk-YOD7H7MC.mjs → chunk-PY3NS2YH.mjs} +52 -2
- package/dist/cli.js +173 -16
- package/dist/cli.mjs +122 -15
- package/dist/sdk.d.mts +91 -9
- package/dist/sdk.d.ts +91 -9
- package/dist/sdk.js +52 -2
- package/dist/sdk.mjs +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Execution trust analysis for AI agents — CLI and SDK**
|
|
4
4
|
|
|
5
|
-
Scan AI agent configurations for security issues, credential exposure, and governance gaps.
|
|
5
|
+
Scan AI agent configurations for security issues, credential exposure, and governance gaps. Saved CLI reports appear in your Agent Verify dashboard when API report save succeeds.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -37,6 +37,20 @@ agentverify scan ./agents
|
|
|
37
37
|
agentverify scan . --key av_your_key --json
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
### Markdown summary
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
agentverify scan ./agents --markdown
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### CI mode
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
agentverify scan ./agents --ci
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Normal scans print findings without failing the shell just because issues exist. CI mode is stricter: it exits non-zero when an agent is `NOT_VERIFIED`, when a scan errors, or when submitted content cannot be assessed.
|
|
53
|
+
|
|
40
54
|
## SDK Usage
|
|
41
55
|
|
|
42
56
|
```ts
|
|
@@ -51,7 +65,7 @@ const result = await scan(
|
|
|
51
65
|
{ apiKey: process.env.AGENTVERIFY_API_KEY }
|
|
52
66
|
)
|
|
53
67
|
|
|
54
|
-
result.verdict // 'VERIFIED' | '
|
|
68
|
+
result.verdict // 'VERIFIED' | 'NOT_VERIFIED' | 'NOT_ASSESSED'
|
|
55
69
|
result.riskScore // 0-100
|
|
56
70
|
result.findings // Array of security findings
|
|
57
71
|
```
|
|
@@ -73,10 +87,28 @@ const results = await scanMany(
|
|
|
73
87
|
### GitHub Actions
|
|
74
88
|
|
|
75
89
|
```yaml
|
|
76
|
-
- name:
|
|
77
|
-
run: npx agentverify scan ./agents --
|
|
90
|
+
- name: Agent Verify
|
|
91
|
+
run: npx agentverify scan ./agents --ci
|
|
92
|
+
env:
|
|
93
|
+
AGENTVERIFY_API_KEY: ${{ secrets.AGENTVERIFY_API_KEY }}
|
|
78
94
|
```
|
|
79
95
|
|
|
96
|
+
Use this in pull requests to block merges when an agent is `NOT_VERIFIED`. The CLI prints score, verdict, risk level, top blocker, fix-first guidance, threat categories, A2SPA docs, and the canonical report URL:
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
https://aimodularity.com/agentverify/report/?id=REPORT_ID
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
For PR comments, run with `--markdown` and post the output with your preferred GitHub Action. For machine-readable pipelines, run with `--json`.
|
|
103
|
+
|
|
104
|
+
The workflow requires a valid `AGENTVERIFY_API_KEY`. The local Worker code path fails closed for missing, malformed, invalid, disabled, or revoked keys before findings are returned; validate the deployed API path before relying on this as production enforcement. Wire the workflow into branch protection so the non-zero `--ci` exit code blocks merges when an agent is `NOT_VERIFIED`, not assessed, or the API key is unauthorized.
|
|
105
|
+
|
|
106
|
+
Saved CLI reports sync to the dashboard through the API `cliReports` save path. If the API cannot save the report, the CLI still prints local scan output and tells you dashboard sync did not complete.
|
|
107
|
+
|
|
108
|
+
Verified reports can expose a README badge from the report page. A packaged Agent Verify GitHub Action with automatic PR comments is planned after the v1.3.0 app/CLI release candidate stabilizes.
|
|
109
|
+
|
|
110
|
+
See the full GitHub Action guide at `docs/github-action.md` for composite action inputs, JSON/Markdown examples, branch protection, optional PR comments, and badge guidance.
|
|
111
|
+
|
|
80
112
|
## Get your API key
|
|
81
113
|
|
|
82
114
|
Sign up at https://aimodularity.com/agentverify/dashboard and go to Dashboard -> API
|
|
@@ -1,6 +1,56 @@
|
|
|
1
1
|
// src/sdk.ts
|
|
2
2
|
var DEFAULT_API_URL = "https://agentverify-api.agentverify.workers.dev/v1/scan";
|
|
3
3
|
var DEFAULT_TIMEOUT = 3e4;
|
|
4
|
+
function normalizeVerdict(value) {
|
|
5
|
+
if (value === "VERIFIED") return "VERIFIED";
|
|
6
|
+
if (value === "NOT_ASSESSED" || value === "NOT ASSESSED") return "NOT_ASSESSED";
|
|
7
|
+
return "NOT_VERIFIED";
|
|
8
|
+
}
|
|
9
|
+
function normalizeScanResult(data) {
|
|
10
|
+
const findings = Array.isArray(data.findings) ? data.findings : [];
|
|
11
|
+
const riskScore = typeof data.riskScore === "number" ? data.riskScore : 0;
|
|
12
|
+
return {
|
|
13
|
+
...data,
|
|
14
|
+
reportId: typeof data.reportId === "string" ? data.reportId : "",
|
|
15
|
+
verdict: normalizeVerdict(data.verdict),
|
|
16
|
+
riskScore,
|
|
17
|
+
riskLevel: data.riskLevel ?? "High Risk",
|
|
18
|
+
confidence: typeof data.confidence === "number" ? data.confidence : riskScore > 0 ? 80 : 0,
|
|
19
|
+
optimizationScore: typeof data.optimizationScore === "number" ? data.optimizationScore : 0,
|
|
20
|
+
reportInsights: data.reportInsights,
|
|
21
|
+
threatCategories: Array.isArray(data.threatCategories) ? data.threatCategories : [],
|
|
22
|
+
findings,
|
|
23
|
+
categoryScores: Array.isArray(data.categoryScores) ? data.categoryScores : [],
|
|
24
|
+
bom: data.bom ?? {
|
|
25
|
+
detectedLanguage: "Unknown",
|
|
26
|
+
detectedFramework: null,
|
|
27
|
+
detectedPlatform: null,
|
|
28
|
+
agentName: null,
|
|
29
|
+
toolAccessLevel: "Unknown",
|
|
30
|
+
credentialExposure: "Not Detected",
|
|
31
|
+
memoryPersistence: "Unknown",
|
|
32
|
+
auditLogging: "Unknown",
|
|
33
|
+
humanGates: "Unknown",
|
|
34
|
+
rateLimiting: "Unknown",
|
|
35
|
+
promptInjectionSurface: "Unknown",
|
|
36
|
+
delegationScope: "Unknown"
|
|
37
|
+
},
|
|
38
|
+
metadata: data.metadata ?? {
|
|
39
|
+
schemaVersion: "1.3.0",
|
|
40
|
+
scannerVersion: "unknown",
|
|
41
|
+
fileName: "unknown",
|
|
42
|
+
fileSize: 0,
|
|
43
|
+
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
44
|
+
detectedLanguage: "Unknown",
|
|
45
|
+
detectedFramework: null,
|
|
46
|
+
selectedPlatform: null,
|
|
47
|
+
agentName: null,
|
|
48
|
+
scanDuration: 0
|
|
49
|
+
},
|
|
50
|
+
saved: data.saved === true,
|
|
51
|
+
reportUrl: data.reportUrl ?? null
|
|
52
|
+
};
|
|
53
|
+
}
|
|
4
54
|
async function scan(input, options) {
|
|
5
55
|
const { apiKey, apiUrl = DEFAULT_API_URL, timeout = DEFAULT_TIMEOUT } = options;
|
|
6
56
|
if (!apiKey) {
|
|
@@ -30,7 +80,7 @@ async function scan(input, options) {
|
|
|
30
80
|
});
|
|
31
81
|
if (response.status === 401) {
|
|
32
82
|
throw new Error(
|
|
33
|
-
"Invalid API key. Generate
|
|
83
|
+
"Invalid or unauthorized Agent Verify API key. Generate a valid key at https://aimodularity.com/agentverify/dashboard/"
|
|
34
84
|
);
|
|
35
85
|
}
|
|
36
86
|
if (response.status === 400) {
|
|
@@ -41,7 +91,7 @@ async function scan(input, options) {
|
|
|
41
91
|
throw new Error(`API error: ${response.status} ${response.statusText}`);
|
|
42
92
|
}
|
|
43
93
|
const data = await response.json();
|
|
44
|
-
return data;
|
|
94
|
+
return normalizeScanResult(data);
|
|
45
95
|
} catch (err) {
|
|
46
96
|
if (err instanceof Error && err.name === "AbortError") {
|
|
47
97
|
throw new Error(`Request timed out after ${timeout}ms`);
|
package/dist/cli.js
CHANGED
|
@@ -8,6 +8,56 @@ var import_path = require("path");
|
|
|
8
8
|
// src/sdk.ts
|
|
9
9
|
var DEFAULT_API_URL = "https://agentverify-api.agentverify.workers.dev/v1/scan";
|
|
10
10
|
var DEFAULT_TIMEOUT = 3e4;
|
|
11
|
+
function normalizeVerdict(value) {
|
|
12
|
+
if (value === "VERIFIED") return "VERIFIED";
|
|
13
|
+
if (value === "NOT_ASSESSED" || value === "NOT ASSESSED") return "NOT_ASSESSED";
|
|
14
|
+
return "NOT_VERIFIED";
|
|
15
|
+
}
|
|
16
|
+
function normalizeScanResult(data) {
|
|
17
|
+
const findings = Array.isArray(data.findings) ? data.findings : [];
|
|
18
|
+
const riskScore = typeof data.riskScore === "number" ? data.riskScore : 0;
|
|
19
|
+
return {
|
|
20
|
+
...data,
|
|
21
|
+
reportId: typeof data.reportId === "string" ? data.reportId : "",
|
|
22
|
+
verdict: normalizeVerdict(data.verdict),
|
|
23
|
+
riskScore,
|
|
24
|
+
riskLevel: data.riskLevel ?? "High Risk",
|
|
25
|
+
confidence: typeof data.confidence === "number" ? data.confidence : riskScore > 0 ? 80 : 0,
|
|
26
|
+
optimizationScore: typeof data.optimizationScore === "number" ? data.optimizationScore : 0,
|
|
27
|
+
reportInsights: data.reportInsights,
|
|
28
|
+
threatCategories: Array.isArray(data.threatCategories) ? data.threatCategories : [],
|
|
29
|
+
findings,
|
|
30
|
+
categoryScores: Array.isArray(data.categoryScores) ? data.categoryScores : [],
|
|
31
|
+
bom: data.bom ?? {
|
|
32
|
+
detectedLanguage: "Unknown",
|
|
33
|
+
detectedFramework: null,
|
|
34
|
+
detectedPlatform: null,
|
|
35
|
+
agentName: null,
|
|
36
|
+
toolAccessLevel: "Unknown",
|
|
37
|
+
credentialExposure: "Not Detected",
|
|
38
|
+
memoryPersistence: "Unknown",
|
|
39
|
+
auditLogging: "Unknown",
|
|
40
|
+
humanGates: "Unknown",
|
|
41
|
+
rateLimiting: "Unknown",
|
|
42
|
+
promptInjectionSurface: "Unknown",
|
|
43
|
+
delegationScope: "Unknown"
|
|
44
|
+
},
|
|
45
|
+
metadata: data.metadata ?? {
|
|
46
|
+
schemaVersion: "1.3.0",
|
|
47
|
+
scannerVersion: "unknown",
|
|
48
|
+
fileName: "unknown",
|
|
49
|
+
fileSize: 0,
|
|
50
|
+
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
51
|
+
detectedLanguage: "Unknown",
|
|
52
|
+
detectedFramework: null,
|
|
53
|
+
selectedPlatform: null,
|
|
54
|
+
agentName: null,
|
|
55
|
+
scanDuration: 0
|
|
56
|
+
},
|
|
57
|
+
saved: data.saved === true,
|
|
58
|
+
reportUrl: data.reportUrl ?? null
|
|
59
|
+
};
|
|
60
|
+
}
|
|
11
61
|
async function scan(input, options) {
|
|
12
62
|
const { apiKey, apiUrl = DEFAULT_API_URL, timeout = DEFAULT_TIMEOUT } = options;
|
|
13
63
|
if (!apiKey) {
|
|
@@ -37,7 +87,7 @@ async function scan(input, options) {
|
|
|
37
87
|
});
|
|
38
88
|
if (response.status === 401) {
|
|
39
89
|
throw new Error(
|
|
40
|
-
"Invalid API key. Generate
|
|
90
|
+
"Invalid or unauthorized Agent Verify API key. Generate a valid key at https://aimodularity.com/agentverify/dashboard/"
|
|
41
91
|
);
|
|
42
92
|
}
|
|
43
93
|
if (response.status === 400) {
|
|
@@ -48,7 +98,7 @@ async function scan(input, options) {
|
|
|
48
98
|
throw new Error(`API error: ${response.status} ${response.statusText}`);
|
|
49
99
|
}
|
|
50
100
|
const data = await response.json();
|
|
51
|
-
return data;
|
|
101
|
+
return normalizeScanResult(data);
|
|
52
102
|
} catch (err) {
|
|
53
103
|
if (err instanceof Error && err.name === "AbortError") {
|
|
54
104
|
throw new Error(`Request timed out after ${timeout}ms`);
|
|
@@ -60,8 +110,9 @@ async function scan(input, options) {
|
|
|
60
110
|
}
|
|
61
111
|
|
|
62
112
|
// src/cli.ts
|
|
63
|
-
var VERSION = "1.
|
|
64
|
-
var API_URL = "https://agentverify-api.agentverify.workers.dev/v1/scan";
|
|
113
|
+
var VERSION = "1.3.0";
|
|
114
|
+
var API_URL = process.env.AGENTVERIFY_API_URL ?? "https://agentverify-api.agentverify.workers.dev/v1/scan";
|
|
115
|
+
var A2SPA_DOCS_URL = "https://aimodularity.com/A2SPA/docs";
|
|
65
116
|
var c = {
|
|
66
117
|
reset: "\x1B[0m",
|
|
67
118
|
bold: "\x1B[1m",
|
|
@@ -103,6 +154,9 @@ function parseArgs(argv) {
|
|
|
103
154
|
key: "",
|
|
104
155
|
file: "",
|
|
105
156
|
json: false,
|
|
157
|
+
markdown: false,
|
|
158
|
+
ci: false,
|
|
159
|
+
allowNotAssessed: false,
|
|
106
160
|
help: false,
|
|
107
161
|
version: false
|
|
108
162
|
};
|
|
@@ -119,6 +173,9 @@ function parseArgs(argv) {
|
|
|
119
173
|
if (arg === "--key" || arg === "-k") result.key = args[++i] ?? "";
|
|
120
174
|
else if (arg === "--file" || arg === "-f") result.file = args[++i] ?? "";
|
|
121
175
|
else if (arg === "--json") result.json = true;
|
|
176
|
+
else if (arg === "--markdown") result.markdown = true;
|
|
177
|
+
else if (arg === "--ci") result.ci = true;
|
|
178
|
+
else if (arg === "--allow-not-assessed") result.allowNotAssessed = true;
|
|
122
179
|
else if (arg === "--help" || arg === "-h") result.help = true;
|
|
123
180
|
else if (arg === "--version" || arg === "-v") result.version = true;
|
|
124
181
|
else if (!arg.startsWith("-")) result.dir = arg;
|
|
@@ -142,6 +199,10 @@ ${c.bold}Options:${c.reset}
|
|
|
142
199
|
--key, -k API key (or set AGENTVERIFY_API_KEY env var)
|
|
143
200
|
--file, -f Scan a single file instead of a directory
|
|
144
201
|
--json Output results as JSON
|
|
202
|
+
--markdown Output a Markdown summary
|
|
203
|
+
--ci CI mode: concise output and non-zero exit for NOT VERIFIED
|
|
204
|
+
--allow-not-assessed
|
|
205
|
+
In CI mode, do not fail only because content is NOT_ASSESSED
|
|
145
206
|
--version, -v Print version
|
|
146
207
|
--help, -h Show this help message
|
|
147
208
|
|
|
@@ -158,23 +219,87 @@ ${c.bold}Examples:${c.reset}
|
|
|
158
219
|
${c.gray}# Output as JSON (useful for CI/CD)${c.reset}
|
|
159
220
|
agentverify scan . --key av_your_key --json
|
|
160
221
|
|
|
222
|
+
${c.gray}# Markdown summary for pull requests${c.reset}
|
|
223
|
+
agentverify scan . --key av_your_key --markdown
|
|
224
|
+
|
|
161
225
|
${c.bold}Get your API key:${c.reset}
|
|
162
226
|
https://aimodularity.com/agentverify/dashboard
|
|
163
227
|
|
|
164
228
|
${c.bold}Docs:${c.reset}
|
|
165
229
|
https://github.com/AI-Blockchain-Ventures/agentverify
|
|
230
|
+
|
|
231
|
+
${c.bold}A2SPA docs:${c.reset}
|
|
232
|
+
${A2SPA_DOCS_URL}
|
|
166
233
|
`);
|
|
167
234
|
}
|
|
235
|
+
function getTopBlocker(result) {
|
|
236
|
+
return result.reportInsights?.topBlocker ?? result.findings[0]?.title ?? "No blocking finding detected";
|
|
237
|
+
}
|
|
238
|
+
function getFixFirst(result) {
|
|
239
|
+
const fromInsights = result.reportInsights?.fixPriority?.filter((item) => item.priority === "fix_first").map((item) => item.title) ?? [];
|
|
240
|
+
const fallback = result.findings.slice(0, 3).map((item) => item.title);
|
|
241
|
+
return (fromInsights.length ? fromInsights : fallback).slice(0, 3);
|
|
242
|
+
}
|
|
243
|
+
function getRelevantThreatCategories(result) {
|
|
244
|
+
return (result.threatCategories ?? []).filter((item) => item.status !== "not_assessed").slice(0, 6).map((item) => `${item.label} (${item.status.replace("_", " ")})`);
|
|
245
|
+
}
|
|
246
|
+
function printDetailedSummary(result, fileName) {
|
|
247
|
+
const verified = result.verdict === "VERIFIED";
|
|
248
|
+
const verdictColor = verified ? c.green : result.verdict === "NOT_ASSESSED" ? c.yellow : c.red;
|
|
249
|
+
write(`
|
|
250
|
+
${c.bold}${c.cyan}Agent Verify${c.reset}`);
|
|
251
|
+
write(`${c.gray}${fileName}${c.reset}`);
|
|
252
|
+
write(`${c.bold}Score:${c.reset} ${result.riskScore}/100`);
|
|
253
|
+
write(`${c.bold}Verdict:${c.reset} ${verdictColor}${result.verdict === "NOT_VERIFIED" ? "NOT VERIFIED" : result.verdict.replace("_", " ")}${c.reset}`);
|
|
254
|
+
write(`${c.bold}Risk level:${c.reset} ${result.riskLevel}`);
|
|
255
|
+
write(`${c.bold}Confidence:${c.reset} ${result.confidence}/100`);
|
|
256
|
+
write(`${c.bold}Top blocker:${c.reset} ${getTopBlocker(result)}`);
|
|
257
|
+
write(`
|
|
258
|
+
${c.bold}Fix first:${c.reset}`);
|
|
259
|
+
const fixes = getFixFirst(result);
|
|
260
|
+
if (fixes.length) fixes.forEach((item, index) => write(` ${index + 1}. ${item}`));
|
|
261
|
+
else write(` No blocking fixes detected. Re-scan after any material change.`);
|
|
262
|
+
if (result.reportInsights?.nextAction) write(`
|
|
263
|
+
${c.bold}Next action:${c.reset} ${result.reportInsights.nextAction}`);
|
|
264
|
+
const threats = getRelevantThreatCategories(result);
|
|
265
|
+
if (threats.length) {
|
|
266
|
+
write(`
|
|
267
|
+
${c.bold}Threat categories:${c.reset}`);
|
|
268
|
+
threats.forEach((item) => write(` - ${item}`));
|
|
269
|
+
}
|
|
270
|
+
if (result.saved && result.reportId) write(`${c.bold}View report:${c.reset} https://aimodularity.com/agentverify/report/?id=${result.reportId}`);
|
|
271
|
+
write(`${c.bold}A2SPA docs:${c.reset} ${A2SPA_DOCS_URL}`);
|
|
272
|
+
write(`${c.gray}Re-scan:${c.reset} agentverify scan --file ${fileName} --key $AGENTVERIFY_API_KEY
|
|
273
|
+
`);
|
|
274
|
+
}
|
|
275
|
+
function markdownSummary(results, summary) {
|
|
276
|
+
const lines = ["# Agent Verify", "", `Files scanned: ${summary.total}`, `Verified: ${summary.verified}`, `Not verified: ${summary.notVerified}`, `Not assessed: ${summary.notAssessed}`, `Errors: ${summary.errors}`, ""];
|
|
277
|
+
for (const item of results) {
|
|
278
|
+
if (!item.result) {
|
|
279
|
+
lines.push(`## ${item.file}`, "", `Error: ${item.error ?? "Unknown error"}`, "");
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
lines.push(`## ${item.file}`, "", `Score: ${item.result.riskScore}/100`, `Verdict: ${item.result.verdict === "NOT_VERIFIED" ? "NOT VERIFIED" : item.result.verdict.replace("_", " ")}`, `Risk level: ${item.result.riskLevel}`, `Top blocker: ${getTopBlocker(item.result)}`, "");
|
|
283
|
+
const fixes = getFixFirst(item.result);
|
|
284
|
+
if (fixes.length) lines.push("Fix first:", ...fixes.map((fix, index) => `${index + 1}. ${fix}`), "");
|
|
285
|
+
const threats = getRelevantThreatCategories(item.result);
|
|
286
|
+
if (threats.length) lines.push("Threat categories:", ...threats.map((threat) => `- ${threat}`), "");
|
|
287
|
+
if (item.result.reportId) lines.push(`Report: https://aimodularity.com/agentverify/report/?id=${item.result.reportId}`, "");
|
|
288
|
+
}
|
|
289
|
+
lines.push(`A2SPA docs: ${A2SPA_DOCS_URL}`);
|
|
290
|
+
return lines.join("\n");
|
|
291
|
+
}
|
|
168
292
|
function printVerdict(result, fileName) {
|
|
169
293
|
const verified = result.verdict === "VERIFIED";
|
|
170
|
-
const
|
|
171
|
-
const
|
|
294
|
+
const notAssessed = result.verdict === "NOT_ASSESSED";
|
|
295
|
+
const icon = verified ? `${c.green}\u2713${c.reset}` : notAssessed ? `${c.yellow}\u26A0${c.reset}` : `${c.red}\u2717${c.reset}`;
|
|
296
|
+
const verdict = verified ? `${c.green}VERIFIED${c.reset} ` : notAssessed ? `${c.yellow}NOT ASSESSED${c.reset}` : `${c.red}NOT VERIFIED${c.reset}`;
|
|
172
297
|
const score = verified ? `${c.green}${result.riskScore}/100${c.reset}` : result.riskScore >= 50 ? `${c.yellow}${result.riskScore}/100${c.reset}` : `${c.red}${result.riskScore}/100${c.reset}`;
|
|
173
298
|
const findings = result.findings.length > 0 ? `${c.gray}\u2014 ${result.findings.length} finding${result.findings.length === 1 ? "" : "s"}${c.reset}` : "";
|
|
174
299
|
const name = fileName.padEnd(30);
|
|
175
300
|
write(` ${icon} ${verdict} ${name} score: ${score} ${findings}`);
|
|
176
301
|
if (result.saved && result.reportId) {
|
|
177
|
-
write(` ${c.gray}\u2192 Report: https://aimodularity.com/agentverify/report
|
|
302
|
+
write(` ${c.gray}\u2192 Report: https://aimodularity.com/agentverify/report/?id=${result.reportId}${c.reset}`);
|
|
178
303
|
}
|
|
179
304
|
}
|
|
180
305
|
async function runScan(args) {
|
|
@@ -182,8 +307,9 @@ async function runScan(args) {
|
|
|
182
307
|
console.error(`
|
|
183
308
|
${c.red}Error:${c.reset} API key required
|
|
184
309
|
`);
|
|
185
|
-
console.error(` Set with --key av_your_key or AGENTVERIFY_API_KEY env var
|
|
186
|
-
console.error(` Get your key at https://aimodularity.com/agentverify/dashboard
|
|
310
|
+
console.error(` Set with --key av_your_key or AGENTVERIFY_API_KEY env var.`);
|
|
311
|
+
console.error(` Get your key at https://aimodularity.com/agentverify/dashboard/`);
|
|
312
|
+
console.error(` Example: AGENTVERIFY_API_KEY=av_your_key agentverify scan .
|
|
187
313
|
`);
|
|
188
314
|
process.exit(1);
|
|
189
315
|
}
|
|
@@ -192,10 +318,12 @@ ${c.red}Error:${c.reset} API key required
|
|
|
192
318
|
files = [args.file];
|
|
193
319
|
} else {
|
|
194
320
|
const dir = args.dir;
|
|
195
|
-
|
|
321
|
+
if (!args.markdown && !args.json) {
|
|
322
|
+
write(`
|
|
196
323
|
${c.bold}Agent Verify${c.reset} ${c.gray}v${VERSION}${c.reset}`);
|
|
197
|
-
|
|
324
|
+
write(`${c.gray}Scanning ${dir}...${c.reset}
|
|
198
325
|
`);
|
|
326
|
+
}
|
|
199
327
|
files = collectFiles(dir);
|
|
200
328
|
if (files.length === 0) {
|
|
201
329
|
write(`${c.yellow}No agent files found in ${dir}${c.reset}`);
|
|
@@ -207,6 +335,7 @@ ${c.bold}Agent Verify${c.reset} ${c.gray}v${VERSION}${c.reset}`);
|
|
|
207
335
|
const jsonResults = [];
|
|
208
336
|
let verified = 0;
|
|
209
337
|
let notVerified = 0;
|
|
338
|
+
let notAssessed = 0;
|
|
210
339
|
let errors = 0;
|
|
211
340
|
const CONCURRENCY = 3;
|
|
212
341
|
for (let i = 0; i < files.length; i += CONCURRENCY) {
|
|
@@ -226,9 +355,13 @@ ${c.bold}Agent Verify${c.reset} ${c.gray}v${VERSION}${c.reset}`);
|
|
|
226
355
|
const outcome = batchResults[j];
|
|
227
356
|
if (outcome.status === "fulfilled") {
|
|
228
357
|
const { relPath, result } = outcome.value;
|
|
229
|
-
if (!args.json
|
|
358
|
+
if (!args.json && !args.markdown) {
|
|
359
|
+
if (args.file && !args.ci) printDetailedSummary(result, relPath);
|
|
360
|
+
else printVerdict(result, relPath);
|
|
361
|
+
}
|
|
230
362
|
jsonResults.push({ file: relPath, result, error: null });
|
|
231
363
|
if (result.verdict === "VERIFIED") verified++;
|
|
364
|
+
else if (result.verdict === "NOT_ASSESSED") notAssessed++;
|
|
232
365
|
else notVerified++;
|
|
233
366
|
} else {
|
|
234
367
|
const filePath = batch[j];
|
|
@@ -242,22 +375,46 @@ ${c.bold}Agent Verify${c.reset} ${c.gray}v${VERSION}${c.reset}`);
|
|
|
242
375
|
}
|
|
243
376
|
}
|
|
244
377
|
}
|
|
378
|
+
const summary = { total: files.length, verified, notVerified, notAssessed, errors };
|
|
245
379
|
if (args.json) {
|
|
246
|
-
write(JSON.stringify({ results: jsonResults, summary
|
|
380
|
+
write(JSON.stringify({ results: jsonResults, summary }, null, 2));
|
|
381
|
+
if (errors > 0) process.exit(1);
|
|
382
|
+
if (args.ci) {
|
|
383
|
+
if (notVerified > 0) process.exit(1);
|
|
384
|
+
if (notAssessed > 0 && !args.allowNotAssessed) process.exit(2);
|
|
385
|
+
}
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (args.markdown) {
|
|
389
|
+
write(markdownSummary(jsonResults, summary));
|
|
390
|
+
if (errors > 0) process.exit(1);
|
|
391
|
+
if (args.ci) {
|
|
392
|
+
if (notVerified > 0) process.exit(1);
|
|
393
|
+
if (notAssessed > 0 && !args.allowNotAssessed) process.exit(2);
|
|
394
|
+
}
|
|
247
395
|
return;
|
|
248
396
|
}
|
|
397
|
+
const savedReports = jsonResults.filter((item) => item.result?.saved && item.result.reportId).length;
|
|
249
398
|
write(`
|
|
250
399
|
${c.gray}${"\u2500".repeat(60)}${c.reset}`);
|
|
251
400
|
write(`${c.bold}Agent Verify Scan Complete${c.reset}`);
|
|
252
401
|
write(`${c.gray}Files scanned:${c.reset} ${files.length}`);
|
|
253
402
|
write(`${c.green}Verified:${c.reset} ${verified}`);
|
|
254
403
|
if (notVerified > 0) write(`${c.red}Not Verified:${c.reset} ${notVerified}`);
|
|
404
|
+
if (notAssessed > 0) write(`${c.yellow}Not Assessed:${c.reset} ${notAssessed}`);
|
|
255
405
|
if (errors > 0) write(`${c.yellow}Errors:${c.reset} ${errors}`);
|
|
256
406
|
write(`${c.gray}${"\u2500".repeat(60)}${c.reset}`);
|
|
257
|
-
write(`
|
|
258
|
-
${c.gray}
|
|
407
|
+
if (savedReports > 0) write(`
|
|
408
|
+
${c.gray}Saved CLI reports appear in your dashboard: https://aimodularity.com/agentverify/dashboard/${c.reset}`);
|
|
409
|
+
else write(`
|
|
410
|
+
${c.gray}No dashboard report was saved. Check API key access and retry if you need dashboard sync.${c.reset}`);
|
|
411
|
+
write(`${c.gray}A2SPA docs: ${A2SPA_DOCS_URL}${c.reset}
|
|
259
412
|
`);
|
|
260
|
-
if (
|
|
413
|
+
if (errors > 0) process.exit(1);
|
|
414
|
+
if (args.ci) {
|
|
415
|
+
if (notVerified > 0) process.exit(1);
|
|
416
|
+
if (notAssessed > 0 && !args.allowNotAssessed) process.exit(2);
|
|
417
|
+
}
|
|
261
418
|
}
|
|
262
419
|
async function main() {
|
|
263
420
|
const args = parseArgs(process.argv);
|
package/dist/cli.mjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
scan
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-PY3NS2YH.mjs";
|
|
5
5
|
|
|
6
6
|
// src/cli.ts
|
|
7
7
|
import { readFileSync, readdirSync, statSync } from "fs";
|
|
8
8
|
import { join, relative, extname } from "path";
|
|
9
|
-
var VERSION = "1.
|
|
10
|
-
var API_URL = "https://agentverify-api.agentverify.workers.dev/v1/scan";
|
|
9
|
+
var VERSION = "1.3.0";
|
|
10
|
+
var API_URL = process.env.AGENTVERIFY_API_URL ?? "https://agentverify-api.agentverify.workers.dev/v1/scan";
|
|
11
|
+
var A2SPA_DOCS_URL = "https://aimodularity.com/A2SPA/docs";
|
|
11
12
|
var c = {
|
|
12
13
|
reset: "\x1B[0m",
|
|
13
14
|
bold: "\x1B[1m",
|
|
@@ -49,6 +50,9 @@ function parseArgs(argv) {
|
|
|
49
50
|
key: "",
|
|
50
51
|
file: "",
|
|
51
52
|
json: false,
|
|
53
|
+
markdown: false,
|
|
54
|
+
ci: false,
|
|
55
|
+
allowNotAssessed: false,
|
|
52
56
|
help: false,
|
|
53
57
|
version: false
|
|
54
58
|
};
|
|
@@ -65,6 +69,9 @@ function parseArgs(argv) {
|
|
|
65
69
|
if (arg === "--key" || arg === "-k") result.key = args[++i] ?? "";
|
|
66
70
|
else if (arg === "--file" || arg === "-f") result.file = args[++i] ?? "";
|
|
67
71
|
else if (arg === "--json") result.json = true;
|
|
72
|
+
else if (arg === "--markdown") result.markdown = true;
|
|
73
|
+
else if (arg === "--ci") result.ci = true;
|
|
74
|
+
else if (arg === "--allow-not-assessed") result.allowNotAssessed = true;
|
|
68
75
|
else if (arg === "--help" || arg === "-h") result.help = true;
|
|
69
76
|
else if (arg === "--version" || arg === "-v") result.version = true;
|
|
70
77
|
else if (!arg.startsWith("-")) result.dir = arg;
|
|
@@ -88,6 +95,10 @@ ${c.bold}Options:${c.reset}
|
|
|
88
95
|
--key, -k API key (or set AGENTVERIFY_API_KEY env var)
|
|
89
96
|
--file, -f Scan a single file instead of a directory
|
|
90
97
|
--json Output results as JSON
|
|
98
|
+
--markdown Output a Markdown summary
|
|
99
|
+
--ci CI mode: concise output and non-zero exit for NOT VERIFIED
|
|
100
|
+
--allow-not-assessed
|
|
101
|
+
In CI mode, do not fail only because content is NOT_ASSESSED
|
|
91
102
|
--version, -v Print version
|
|
92
103
|
--help, -h Show this help message
|
|
93
104
|
|
|
@@ -104,23 +115,87 @@ ${c.bold}Examples:${c.reset}
|
|
|
104
115
|
${c.gray}# Output as JSON (useful for CI/CD)${c.reset}
|
|
105
116
|
agentverify scan . --key av_your_key --json
|
|
106
117
|
|
|
118
|
+
${c.gray}# Markdown summary for pull requests${c.reset}
|
|
119
|
+
agentverify scan . --key av_your_key --markdown
|
|
120
|
+
|
|
107
121
|
${c.bold}Get your API key:${c.reset}
|
|
108
122
|
https://aimodularity.com/agentverify/dashboard
|
|
109
123
|
|
|
110
124
|
${c.bold}Docs:${c.reset}
|
|
111
125
|
https://github.com/AI-Blockchain-Ventures/agentverify
|
|
126
|
+
|
|
127
|
+
${c.bold}A2SPA docs:${c.reset}
|
|
128
|
+
${A2SPA_DOCS_URL}
|
|
129
|
+
`);
|
|
130
|
+
}
|
|
131
|
+
function getTopBlocker(result) {
|
|
132
|
+
return result.reportInsights?.topBlocker ?? result.findings[0]?.title ?? "No blocking finding detected";
|
|
133
|
+
}
|
|
134
|
+
function getFixFirst(result) {
|
|
135
|
+
const fromInsights = result.reportInsights?.fixPriority?.filter((item) => item.priority === "fix_first").map((item) => item.title) ?? [];
|
|
136
|
+
const fallback = result.findings.slice(0, 3).map((item) => item.title);
|
|
137
|
+
return (fromInsights.length ? fromInsights : fallback).slice(0, 3);
|
|
138
|
+
}
|
|
139
|
+
function getRelevantThreatCategories(result) {
|
|
140
|
+
return (result.threatCategories ?? []).filter((item) => item.status !== "not_assessed").slice(0, 6).map((item) => `${item.label} (${item.status.replace("_", " ")})`);
|
|
141
|
+
}
|
|
142
|
+
function printDetailedSummary(result, fileName) {
|
|
143
|
+
const verified = result.verdict === "VERIFIED";
|
|
144
|
+
const verdictColor = verified ? c.green : result.verdict === "NOT_ASSESSED" ? c.yellow : c.red;
|
|
145
|
+
write(`
|
|
146
|
+
${c.bold}${c.cyan}Agent Verify${c.reset}`);
|
|
147
|
+
write(`${c.gray}${fileName}${c.reset}`);
|
|
148
|
+
write(`${c.bold}Score:${c.reset} ${result.riskScore}/100`);
|
|
149
|
+
write(`${c.bold}Verdict:${c.reset} ${verdictColor}${result.verdict === "NOT_VERIFIED" ? "NOT VERIFIED" : result.verdict.replace("_", " ")}${c.reset}`);
|
|
150
|
+
write(`${c.bold}Risk level:${c.reset} ${result.riskLevel}`);
|
|
151
|
+
write(`${c.bold}Confidence:${c.reset} ${result.confidence}/100`);
|
|
152
|
+
write(`${c.bold}Top blocker:${c.reset} ${getTopBlocker(result)}`);
|
|
153
|
+
write(`
|
|
154
|
+
${c.bold}Fix first:${c.reset}`);
|
|
155
|
+
const fixes = getFixFirst(result);
|
|
156
|
+
if (fixes.length) fixes.forEach((item, index) => write(` ${index + 1}. ${item}`));
|
|
157
|
+
else write(` No blocking fixes detected. Re-scan after any material change.`);
|
|
158
|
+
if (result.reportInsights?.nextAction) write(`
|
|
159
|
+
${c.bold}Next action:${c.reset} ${result.reportInsights.nextAction}`);
|
|
160
|
+
const threats = getRelevantThreatCategories(result);
|
|
161
|
+
if (threats.length) {
|
|
162
|
+
write(`
|
|
163
|
+
${c.bold}Threat categories:${c.reset}`);
|
|
164
|
+
threats.forEach((item) => write(` - ${item}`));
|
|
165
|
+
}
|
|
166
|
+
if (result.saved && result.reportId) write(`${c.bold}View report:${c.reset} https://aimodularity.com/agentverify/report/?id=${result.reportId}`);
|
|
167
|
+
write(`${c.bold}A2SPA docs:${c.reset} ${A2SPA_DOCS_URL}`);
|
|
168
|
+
write(`${c.gray}Re-scan:${c.reset} agentverify scan --file ${fileName} --key $AGENTVERIFY_API_KEY
|
|
112
169
|
`);
|
|
113
170
|
}
|
|
171
|
+
function markdownSummary(results, summary) {
|
|
172
|
+
const lines = ["# Agent Verify", "", `Files scanned: ${summary.total}`, `Verified: ${summary.verified}`, `Not verified: ${summary.notVerified}`, `Not assessed: ${summary.notAssessed}`, `Errors: ${summary.errors}`, ""];
|
|
173
|
+
for (const item of results) {
|
|
174
|
+
if (!item.result) {
|
|
175
|
+
lines.push(`## ${item.file}`, "", `Error: ${item.error ?? "Unknown error"}`, "");
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
lines.push(`## ${item.file}`, "", `Score: ${item.result.riskScore}/100`, `Verdict: ${item.result.verdict === "NOT_VERIFIED" ? "NOT VERIFIED" : item.result.verdict.replace("_", " ")}`, `Risk level: ${item.result.riskLevel}`, `Top blocker: ${getTopBlocker(item.result)}`, "");
|
|
179
|
+
const fixes = getFixFirst(item.result);
|
|
180
|
+
if (fixes.length) lines.push("Fix first:", ...fixes.map((fix, index) => `${index + 1}. ${fix}`), "");
|
|
181
|
+
const threats = getRelevantThreatCategories(item.result);
|
|
182
|
+
if (threats.length) lines.push("Threat categories:", ...threats.map((threat) => `- ${threat}`), "");
|
|
183
|
+
if (item.result.reportId) lines.push(`Report: https://aimodularity.com/agentverify/report/?id=${item.result.reportId}`, "");
|
|
184
|
+
}
|
|
185
|
+
lines.push(`A2SPA docs: ${A2SPA_DOCS_URL}`);
|
|
186
|
+
return lines.join("\n");
|
|
187
|
+
}
|
|
114
188
|
function printVerdict(result, fileName) {
|
|
115
189
|
const verified = result.verdict === "VERIFIED";
|
|
116
|
-
const
|
|
117
|
-
const
|
|
190
|
+
const notAssessed = result.verdict === "NOT_ASSESSED";
|
|
191
|
+
const icon = verified ? `${c.green}\u2713${c.reset}` : notAssessed ? `${c.yellow}\u26A0${c.reset}` : `${c.red}\u2717${c.reset}`;
|
|
192
|
+
const verdict = verified ? `${c.green}VERIFIED${c.reset} ` : notAssessed ? `${c.yellow}NOT ASSESSED${c.reset}` : `${c.red}NOT VERIFIED${c.reset}`;
|
|
118
193
|
const score = verified ? `${c.green}${result.riskScore}/100${c.reset}` : result.riskScore >= 50 ? `${c.yellow}${result.riskScore}/100${c.reset}` : `${c.red}${result.riskScore}/100${c.reset}`;
|
|
119
194
|
const findings = result.findings.length > 0 ? `${c.gray}\u2014 ${result.findings.length} finding${result.findings.length === 1 ? "" : "s"}${c.reset}` : "";
|
|
120
195
|
const name = fileName.padEnd(30);
|
|
121
196
|
write(` ${icon} ${verdict} ${name} score: ${score} ${findings}`);
|
|
122
197
|
if (result.saved && result.reportId) {
|
|
123
|
-
write(` ${c.gray}\u2192 Report: https://aimodularity.com/agentverify/report
|
|
198
|
+
write(` ${c.gray}\u2192 Report: https://aimodularity.com/agentverify/report/?id=${result.reportId}${c.reset}`);
|
|
124
199
|
}
|
|
125
200
|
}
|
|
126
201
|
async function runScan(args) {
|
|
@@ -128,8 +203,9 @@ async function runScan(args) {
|
|
|
128
203
|
console.error(`
|
|
129
204
|
${c.red}Error:${c.reset} API key required
|
|
130
205
|
`);
|
|
131
|
-
console.error(` Set with --key av_your_key or AGENTVERIFY_API_KEY env var
|
|
132
|
-
console.error(` Get your key at https://aimodularity.com/agentverify/dashboard
|
|
206
|
+
console.error(` Set with --key av_your_key or AGENTVERIFY_API_KEY env var.`);
|
|
207
|
+
console.error(` Get your key at https://aimodularity.com/agentverify/dashboard/`);
|
|
208
|
+
console.error(` Example: AGENTVERIFY_API_KEY=av_your_key agentverify scan .
|
|
133
209
|
`);
|
|
134
210
|
process.exit(1);
|
|
135
211
|
}
|
|
@@ -138,10 +214,12 @@ ${c.red}Error:${c.reset} API key required
|
|
|
138
214
|
files = [args.file];
|
|
139
215
|
} else {
|
|
140
216
|
const dir = args.dir;
|
|
141
|
-
|
|
217
|
+
if (!args.markdown && !args.json) {
|
|
218
|
+
write(`
|
|
142
219
|
${c.bold}Agent Verify${c.reset} ${c.gray}v${VERSION}${c.reset}`);
|
|
143
|
-
|
|
220
|
+
write(`${c.gray}Scanning ${dir}...${c.reset}
|
|
144
221
|
`);
|
|
222
|
+
}
|
|
145
223
|
files = collectFiles(dir);
|
|
146
224
|
if (files.length === 0) {
|
|
147
225
|
write(`${c.yellow}No agent files found in ${dir}${c.reset}`);
|
|
@@ -153,6 +231,7 @@ ${c.bold}Agent Verify${c.reset} ${c.gray}v${VERSION}${c.reset}`);
|
|
|
153
231
|
const jsonResults = [];
|
|
154
232
|
let verified = 0;
|
|
155
233
|
let notVerified = 0;
|
|
234
|
+
let notAssessed = 0;
|
|
156
235
|
let errors = 0;
|
|
157
236
|
const CONCURRENCY = 3;
|
|
158
237
|
for (let i = 0; i < files.length; i += CONCURRENCY) {
|
|
@@ -172,9 +251,13 @@ ${c.bold}Agent Verify${c.reset} ${c.gray}v${VERSION}${c.reset}`);
|
|
|
172
251
|
const outcome = batchResults[j];
|
|
173
252
|
if (outcome.status === "fulfilled") {
|
|
174
253
|
const { relPath, result } = outcome.value;
|
|
175
|
-
if (!args.json
|
|
254
|
+
if (!args.json && !args.markdown) {
|
|
255
|
+
if (args.file && !args.ci) printDetailedSummary(result, relPath);
|
|
256
|
+
else printVerdict(result, relPath);
|
|
257
|
+
}
|
|
176
258
|
jsonResults.push({ file: relPath, result, error: null });
|
|
177
259
|
if (result.verdict === "VERIFIED") verified++;
|
|
260
|
+
else if (result.verdict === "NOT_ASSESSED") notAssessed++;
|
|
178
261
|
else notVerified++;
|
|
179
262
|
} else {
|
|
180
263
|
const filePath = batch[j];
|
|
@@ -188,22 +271,46 @@ ${c.bold}Agent Verify${c.reset} ${c.gray}v${VERSION}${c.reset}`);
|
|
|
188
271
|
}
|
|
189
272
|
}
|
|
190
273
|
}
|
|
274
|
+
const summary = { total: files.length, verified, notVerified, notAssessed, errors };
|
|
191
275
|
if (args.json) {
|
|
192
|
-
write(JSON.stringify({ results: jsonResults, summary
|
|
276
|
+
write(JSON.stringify({ results: jsonResults, summary }, null, 2));
|
|
277
|
+
if (errors > 0) process.exit(1);
|
|
278
|
+
if (args.ci) {
|
|
279
|
+
if (notVerified > 0) process.exit(1);
|
|
280
|
+
if (notAssessed > 0 && !args.allowNotAssessed) process.exit(2);
|
|
281
|
+
}
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (args.markdown) {
|
|
285
|
+
write(markdownSummary(jsonResults, summary));
|
|
286
|
+
if (errors > 0) process.exit(1);
|
|
287
|
+
if (args.ci) {
|
|
288
|
+
if (notVerified > 0) process.exit(1);
|
|
289
|
+
if (notAssessed > 0 && !args.allowNotAssessed) process.exit(2);
|
|
290
|
+
}
|
|
193
291
|
return;
|
|
194
292
|
}
|
|
293
|
+
const savedReports = jsonResults.filter((item) => item.result?.saved && item.result.reportId).length;
|
|
195
294
|
write(`
|
|
196
295
|
${c.gray}${"\u2500".repeat(60)}${c.reset}`);
|
|
197
296
|
write(`${c.bold}Agent Verify Scan Complete${c.reset}`);
|
|
198
297
|
write(`${c.gray}Files scanned:${c.reset} ${files.length}`);
|
|
199
298
|
write(`${c.green}Verified:${c.reset} ${verified}`);
|
|
200
299
|
if (notVerified > 0) write(`${c.red}Not Verified:${c.reset} ${notVerified}`);
|
|
300
|
+
if (notAssessed > 0) write(`${c.yellow}Not Assessed:${c.reset} ${notAssessed}`);
|
|
201
301
|
if (errors > 0) write(`${c.yellow}Errors:${c.reset} ${errors}`);
|
|
202
302
|
write(`${c.gray}${"\u2500".repeat(60)}${c.reset}`);
|
|
203
|
-
write(`
|
|
204
|
-
${c.gray}
|
|
303
|
+
if (savedReports > 0) write(`
|
|
304
|
+
${c.gray}Saved CLI reports appear in your dashboard: https://aimodularity.com/agentverify/dashboard/${c.reset}`);
|
|
305
|
+
else write(`
|
|
306
|
+
${c.gray}No dashboard report was saved. Check API key access and retry if you need dashboard sync.${c.reset}`);
|
|
307
|
+
write(`${c.gray}A2SPA docs: ${A2SPA_DOCS_URL}${c.reset}
|
|
205
308
|
`);
|
|
206
|
-
if (
|
|
309
|
+
if (errors > 0) process.exit(1);
|
|
310
|
+
if (args.ci) {
|
|
311
|
+
if (notVerified > 0) process.exit(1);
|
|
312
|
+
if (notAssessed > 0 && !args.allowNotAssessed) process.exit(2);
|
|
313
|
+
}
|
|
207
314
|
}
|
|
208
315
|
async function main() {
|
|
209
316
|
const args = parseArgs(process.argv);
|
package/dist/sdk.d.mts
CHANGED
|
@@ -3,24 +3,106 @@ interface ScanInput {
|
|
|
3
3
|
fileName?: string;
|
|
4
4
|
platform?: string;
|
|
5
5
|
}
|
|
6
|
+
type Verdict = 'VERIFIED' | 'NOT_VERIFIED' | 'NOT_ASSESSED';
|
|
7
|
+
type RiskLevel = 'Low Risk' | 'Moderate Risk' | 'High Risk';
|
|
8
|
+
type Category = 'A' | 'B';
|
|
9
|
+
type Severity = 'critical' | 'high' | 'medium' | 'low';
|
|
10
|
+
type ThreatCategoryStatus = 'detected' | 'possible' | 'missing_evidence' | 'not_assessed';
|
|
11
|
+
interface ThreatCategoryAssessment {
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
status: ThreatCategoryStatus;
|
|
15
|
+
severity: Severity;
|
|
16
|
+
whatItMeans: string;
|
|
17
|
+
evidencePattern: string;
|
|
18
|
+
whyItMatters: string;
|
|
19
|
+
recommendedFix: string;
|
|
20
|
+
a2spaImpact: string;
|
|
21
|
+
}
|
|
6
22
|
interface Finding {
|
|
23
|
+
id: string;
|
|
24
|
+
code: string;
|
|
7
25
|
title: string;
|
|
8
|
-
category:
|
|
9
|
-
severity:
|
|
26
|
+
category: Category;
|
|
27
|
+
severity: Severity;
|
|
10
28
|
whatIsWrong: string;
|
|
11
29
|
whyItMatters: string;
|
|
12
|
-
recommendedFix: string;
|
|
13
30
|
evidence?: string;
|
|
31
|
+
recommendedFix: string;
|
|
32
|
+
fixCode?: string;
|
|
33
|
+
quickFix?: string;
|
|
34
|
+
compliance?: {
|
|
35
|
+
owasp?: string[];
|
|
36
|
+
nist?: string[];
|
|
37
|
+
soc2?: string[];
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
interface RuntimeBOM {
|
|
41
|
+
detectedLanguage: string;
|
|
42
|
+
detectedFramework: string | null;
|
|
43
|
+
detectedPlatform: string | null;
|
|
44
|
+
agentName: string | null;
|
|
45
|
+
toolAccessLevel: 'Restricted' | 'Unrestricted' | 'Unknown';
|
|
46
|
+
credentialExposure: 'Detected' | 'Not Detected';
|
|
47
|
+
memoryPersistence: 'Bounded' | 'Unbounded' | 'Unknown';
|
|
48
|
+
auditLogging: 'Present' | 'Absent' | 'Unknown';
|
|
49
|
+
humanGates: 'Present' | 'Absent' | 'Unknown';
|
|
50
|
+
rateLimiting: 'Present' | 'Absent' | 'Unknown';
|
|
51
|
+
promptInjectionSurface: 'Detected' | 'Not Detected' | 'Unknown';
|
|
52
|
+
delegationScope: 'Scoped' | 'Unscoped' | 'Unknown';
|
|
53
|
+
}
|
|
54
|
+
interface CategoryScore {
|
|
55
|
+
category: Category;
|
|
56
|
+
label: string;
|
|
57
|
+
score: number;
|
|
58
|
+
maxScore: number;
|
|
59
|
+
findingCount: number;
|
|
60
|
+
}
|
|
61
|
+
interface FixPriorityItem {
|
|
62
|
+
code: string;
|
|
63
|
+
title: string;
|
|
64
|
+
severity: Severity;
|
|
65
|
+
priority: 'fix_first' | 'fix_next' | 'nice_to_have';
|
|
66
|
+
reason: string;
|
|
67
|
+
}
|
|
68
|
+
interface ReportInsights {
|
|
69
|
+
executionReadinessScore: number;
|
|
70
|
+
a2spaReadinessScore: number;
|
|
71
|
+
remediationProgressScore: number;
|
|
72
|
+
topBlocker: string | null;
|
|
73
|
+
nextAction: string;
|
|
74
|
+
scoreExplanation: string[];
|
|
75
|
+
improvesScore: string[];
|
|
76
|
+
verificationBlockers: string[];
|
|
77
|
+
fixPriority: FixPriorityItem[];
|
|
14
78
|
}
|
|
15
79
|
interface ScanResult {
|
|
16
|
-
|
|
80
|
+
schemaVersion: '1.3.0';
|
|
81
|
+
reportId: string;
|
|
82
|
+
verdict: Verdict;
|
|
17
83
|
riskScore: number;
|
|
18
|
-
riskLevel:
|
|
84
|
+
riskLevel: RiskLevel;
|
|
85
|
+
confidence: number;
|
|
86
|
+
optimizationScore: number;
|
|
87
|
+
reportInsights?: ReportInsights;
|
|
88
|
+
threatCategories?: ThreatCategoryAssessment[];
|
|
19
89
|
findings: Finding[];
|
|
20
|
-
|
|
21
|
-
|
|
90
|
+
categoryScores: CategoryScore[];
|
|
91
|
+
bom: RuntimeBOM;
|
|
92
|
+
metadata: {
|
|
93
|
+
schemaVersion: '1.3.0';
|
|
94
|
+
scannerVersion: string;
|
|
95
|
+
fileName: string;
|
|
96
|
+
fileSize: number;
|
|
97
|
+
scannedAt: string;
|
|
98
|
+
detectedLanguage: string;
|
|
99
|
+
detectedFramework: string | null;
|
|
100
|
+
selectedPlatform: string | null;
|
|
101
|
+
agentName: string | null;
|
|
102
|
+
scanDuration: number;
|
|
103
|
+
};
|
|
22
104
|
saved: boolean;
|
|
23
|
-
reportUrl?: string;
|
|
105
|
+
reportUrl?: string | null;
|
|
24
106
|
}
|
|
25
107
|
interface ScanOptions {
|
|
26
108
|
apiKey: string;
|
|
@@ -39,7 +121,7 @@ interface ScanOptions {
|
|
|
39
121
|
* { apiKey: process.env.AGENTVERIFY_API_KEY }
|
|
40
122
|
* )
|
|
41
123
|
*
|
|
42
|
-
* result.verdict // 'VERIFIED' | '
|
|
124
|
+
* result.verdict // 'VERIFIED' | 'NOT_VERIFIED' | 'NOT_ASSESSED'
|
|
43
125
|
* result.riskScore // 0-100
|
|
44
126
|
* result.findings // Array of findings
|
|
45
127
|
*/
|
package/dist/sdk.d.ts
CHANGED
|
@@ -3,24 +3,106 @@ interface ScanInput {
|
|
|
3
3
|
fileName?: string;
|
|
4
4
|
platform?: string;
|
|
5
5
|
}
|
|
6
|
+
type Verdict = 'VERIFIED' | 'NOT_VERIFIED' | 'NOT_ASSESSED';
|
|
7
|
+
type RiskLevel = 'Low Risk' | 'Moderate Risk' | 'High Risk';
|
|
8
|
+
type Category = 'A' | 'B';
|
|
9
|
+
type Severity = 'critical' | 'high' | 'medium' | 'low';
|
|
10
|
+
type ThreatCategoryStatus = 'detected' | 'possible' | 'missing_evidence' | 'not_assessed';
|
|
11
|
+
interface ThreatCategoryAssessment {
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
status: ThreatCategoryStatus;
|
|
15
|
+
severity: Severity;
|
|
16
|
+
whatItMeans: string;
|
|
17
|
+
evidencePattern: string;
|
|
18
|
+
whyItMatters: string;
|
|
19
|
+
recommendedFix: string;
|
|
20
|
+
a2spaImpact: string;
|
|
21
|
+
}
|
|
6
22
|
interface Finding {
|
|
23
|
+
id: string;
|
|
24
|
+
code: string;
|
|
7
25
|
title: string;
|
|
8
|
-
category:
|
|
9
|
-
severity:
|
|
26
|
+
category: Category;
|
|
27
|
+
severity: Severity;
|
|
10
28
|
whatIsWrong: string;
|
|
11
29
|
whyItMatters: string;
|
|
12
|
-
recommendedFix: string;
|
|
13
30
|
evidence?: string;
|
|
31
|
+
recommendedFix: string;
|
|
32
|
+
fixCode?: string;
|
|
33
|
+
quickFix?: string;
|
|
34
|
+
compliance?: {
|
|
35
|
+
owasp?: string[];
|
|
36
|
+
nist?: string[];
|
|
37
|
+
soc2?: string[];
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
interface RuntimeBOM {
|
|
41
|
+
detectedLanguage: string;
|
|
42
|
+
detectedFramework: string | null;
|
|
43
|
+
detectedPlatform: string | null;
|
|
44
|
+
agentName: string | null;
|
|
45
|
+
toolAccessLevel: 'Restricted' | 'Unrestricted' | 'Unknown';
|
|
46
|
+
credentialExposure: 'Detected' | 'Not Detected';
|
|
47
|
+
memoryPersistence: 'Bounded' | 'Unbounded' | 'Unknown';
|
|
48
|
+
auditLogging: 'Present' | 'Absent' | 'Unknown';
|
|
49
|
+
humanGates: 'Present' | 'Absent' | 'Unknown';
|
|
50
|
+
rateLimiting: 'Present' | 'Absent' | 'Unknown';
|
|
51
|
+
promptInjectionSurface: 'Detected' | 'Not Detected' | 'Unknown';
|
|
52
|
+
delegationScope: 'Scoped' | 'Unscoped' | 'Unknown';
|
|
53
|
+
}
|
|
54
|
+
interface CategoryScore {
|
|
55
|
+
category: Category;
|
|
56
|
+
label: string;
|
|
57
|
+
score: number;
|
|
58
|
+
maxScore: number;
|
|
59
|
+
findingCount: number;
|
|
60
|
+
}
|
|
61
|
+
interface FixPriorityItem {
|
|
62
|
+
code: string;
|
|
63
|
+
title: string;
|
|
64
|
+
severity: Severity;
|
|
65
|
+
priority: 'fix_first' | 'fix_next' | 'nice_to_have';
|
|
66
|
+
reason: string;
|
|
67
|
+
}
|
|
68
|
+
interface ReportInsights {
|
|
69
|
+
executionReadinessScore: number;
|
|
70
|
+
a2spaReadinessScore: number;
|
|
71
|
+
remediationProgressScore: number;
|
|
72
|
+
topBlocker: string | null;
|
|
73
|
+
nextAction: string;
|
|
74
|
+
scoreExplanation: string[];
|
|
75
|
+
improvesScore: string[];
|
|
76
|
+
verificationBlockers: string[];
|
|
77
|
+
fixPriority: FixPriorityItem[];
|
|
14
78
|
}
|
|
15
79
|
interface ScanResult {
|
|
16
|
-
|
|
80
|
+
schemaVersion: '1.3.0';
|
|
81
|
+
reportId: string;
|
|
82
|
+
verdict: Verdict;
|
|
17
83
|
riskScore: number;
|
|
18
|
-
riskLevel:
|
|
84
|
+
riskLevel: RiskLevel;
|
|
85
|
+
confidence: number;
|
|
86
|
+
optimizationScore: number;
|
|
87
|
+
reportInsights?: ReportInsights;
|
|
88
|
+
threatCategories?: ThreatCategoryAssessment[];
|
|
19
89
|
findings: Finding[];
|
|
20
|
-
|
|
21
|
-
|
|
90
|
+
categoryScores: CategoryScore[];
|
|
91
|
+
bom: RuntimeBOM;
|
|
92
|
+
metadata: {
|
|
93
|
+
schemaVersion: '1.3.0';
|
|
94
|
+
scannerVersion: string;
|
|
95
|
+
fileName: string;
|
|
96
|
+
fileSize: number;
|
|
97
|
+
scannedAt: string;
|
|
98
|
+
detectedLanguage: string;
|
|
99
|
+
detectedFramework: string | null;
|
|
100
|
+
selectedPlatform: string | null;
|
|
101
|
+
agentName: string | null;
|
|
102
|
+
scanDuration: number;
|
|
103
|
+
};
|
|
22
104
|
saved: boolean;
|
|
23
|
-
reportUrl?: string;
|
|
105
|
+
reportUrl?: string | null;
|
|
24
106
|
}
|
|
25
107
|
interface ScanOptions {
|
|
26
108
|
apiKey: string;
|
|
@@ -39,7 +121,7 @@ interface ScanOptions {
|
|
|
39
121
|
* { apiKey: process.env.AGENTVERIFY_API_KEY }
|
|
40
122
|
* )
|
|
41
123
|
*
|
|
42
|
-
* result.verdict // 'VERIFIED' | '
|
|
124
|
+
* result.verdict // 'VERIFIED' | 'NOT_VERIFIED' | 'NOT_ASSESSED'
|
|
43
125
|
* result.riskScore // 0-100
|
|
44
126
|
* result.findings // Array of findings
|
|
45
127
|
*/
|
package/dist/sdk.js
CHANGED
|
@@ -26,6 +26,56 @@ __export(sdk_exports, {
|
|
|
26
26
|
module.exports = __toCommonJS(sdk_exports);
|
|
27
27
|
var DEFAULT_API_URL = "https://agentverify-api.agentverify.workers.dev/v1/scan";
|
|
28
28
|
var DEFAULT_TIMEOUT = 3e4;
|
|
29
|
+
function normalizeVerdict(value) {
|
|
30
|
+
if (value === "VERIFIED") return "VERIFIED";
|
|
31
|
+
if (value === "NOT_ASSESSED" || value === "NOT ASSESSED") return "NOT_ASSESSED";
|
|
32
|
+
return "NOT_VERIFIED";
|
|
33
|
+
}
|
|
34
|
+
function normalizeScanResult(data) {
|
|
35
|
+
const findings = Array.isArray(data.findings) ? data.findings : [];
|
|
36
|
+
const riskScore = typeof data.riskScore === "number" ? data.riskScore : 0;
|
|
37
|
+
return {
|
|
38
|
+
...data,
|
|
39
|
+
reportId: typeof data.reportId === "string" ? data.reportId : "",
|
|
40
|
+
verdict: normalizeVerdict(data.verdict),
|
|
41
|
+
riskScore,
|
|
42
|
+
riskLevel: data.riskLevel ?? "High Risk",
|
|
43
|
+
confidence: typeof data.confidence === "number" ? data.confidence : riskScore > 0 ? 80 : 0,
|
|
44
|
+
optimizationScore: typeof data.optimizationScore === "number" ? data.optimizationScore : 0,
|
|
45
|
+
reportInsights: data.reportInsights,
|
|
46
|
+
threatCategories: Array.isArray(data.threatCategories) ? data.threatCategories : [],
|
|
47
|
+
findings,
|
|
48
|
+
categoryScores: Array.isArray(data.categoryScores) ? data.categoryScores : [],
|
|
49
|
+
bom: data.bom ?? {
|
|
50
|
+
detectedLanguage: "Unknown",
|
|
51
|
+
detectedFramework: null,
|
|
52
|
+
detectedPlatform: null,
|
|
53
|
+
agentName: null,
|
|
54
|
+
toolAccessLevel: "Unknown",
|
|
55
|
+
credentialExposure: "Not Detected",
|
|
56
|
+
memoryPersistence: "Unknown",
|
|
57
|
+
auditLogging: "Unknown",
|
|
58
|
+
humanGates: "Unknown",
|
|
59
|
+
rateLimiting: "Unknown",
|
|
60
|
+
promptInjectionSurface: "Unknown",
|
|
61
|
+
delegationScope: "Unknown"
|
|
62
|
+
},
|
|
63
|
+
metadata: data.metadata ?? {
|
|
64
|
+
schemaVersion: "1.3.0",
|
|
65
|
+
scannerVersion: "unknown",
|
|
66
|
+
fileName: "unknown",
|
|
67
|
+
fileSize: 0,
|
|
68
|
+
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
69
|
+
detectedLanguage: "Unknown",
|
|
70
|
+
detectedFramework: null,
|
|
71
|
+
selectedPlatform: null,
|
|
72
|
+
agentName: null,
|
|
73
|
+
scanDuration: 0
|
|
74
|
+
},
|
|
75
|
+
saved: data.saved === true,
|
|
76
|
+
reportUrl: data.reportUrl ?? null
|
|
77
|
+
};
|
|
78
|
+
}
|
|
29
79
|
async function scan(input, options) {
|
|
30
80
|
const { apiKey, apiUrl = DEFAULT_API_URL, timeout = DEFAULT_TIMEOUT } = options;
|
|
31
81
|
if (!apiKey) {
|
|
@@ -55,7 +105,7 @@ async function scan(input, options) {
|
|
|
55
105
|
});
|
|
56
106
|
if (response.status === 401) {
|
|
57
107
|
throw new Error(
|
|
58
|
-
"Invalid API key. Generate
|
|
108
|
+
"Invalid or unauthorized Agent Verify API key. Generate a valid key at https://aimodularity.com/agentverify/dashboard/"
|
|
59
109
|
);
|
|
60
110
|
}
|
|
61
111
|
if (response.status === 400) {
|
|
@@ -66,7 +116,7 @@ async function scan(input, options) {
|
|
|
66
116
|
throw new Error(`API error: ${response.status} ${response.statusText}`);
|
|
67
117
|
}
|
|
68
118
|
const data = await response.json();
|
|
69
|
-
return data;
|
|
119
|
+
return normalizeScanResult(data);
|
|
70
120
|
} catch (err) {
|
|
71
121
|
if (err instanceof Error && err.name === "AbortError") {
|
|
72
122
|
throw new Error(`Request timed out after ${timeout}ms`);
|
package/dist/sdk.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentverify",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "CLI and SDK for Agent Verify — execution trust analysis for AI agents",
|
|
5
5
|
"bin": {
|
|
6
6
|
"agentverify": "./dist/cli.js"
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build": "tsup src/cli.ts src/sdk.ts --format cjs,esm --dts --clean",
|
|
22
22
|
"dev": "tsup src/cli.ts src/sdk.ts --format cjs,esm --dts --watch",
|
|
23
|
+
"test": "npm run build && node test/cli.test.mjs",
|
|
23
24
|
"prepublishOnly": "npm run build"
|
|
24
25
|
},
|
|
25
26
|
"engines": { "node": ">=18.0.0" },
|