agentverify 1.3.0 → 1.4.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/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `agentverify` CLI/SDK package are documented here.
4
+
5
+ ## [1.4.0] - Unreleased
6
+
7
+ ### Changed — CI exit codes now distinguish a security failure from an execution error (behavior change)
8
+
9
+ Previously, `--ci` mode used exit code `1` for **both** a real `NOT_VERIFIED` verdict **and** any execution error (missing/invalid API key, network failure, missing file). That made it impossible for a pipeline to tell "the agent failed the security check" apart from "the scan itself couldn't run."
10
+
11
+ New exit codes:
12
+
13
+ | Code | Meaning | Previously |
14
+ | --- | --- | --- |
15
+ | `0` | All scanned files verified | `0` (unchanged) |
16
+ | `1` | `NOT_VERIFIED` — a real security/verification failure | `1` (unchanged) |
17
+ | `2` | `NOT_ASSESSED` — insufficient evidence for a verdict | `2` (unchanged) |
18
+ | `3` | **New.** Execution error — bad/missing API key, network failure, missing file, unknown command | was `1` |
19
+
20
+ **If your pipeline only checks `exit code != 0` to block a deployment, this is not a breaking change** — every previously-non-zero case is still non-zero. **If your pipeline branches on the exact code `1`**, an execution error will now surface as `3` instead — update any such logic to treat both `1` and `3` as failures, or to react differently to `3` (an infrastructure problem, not a security finding).
21
+
22
+ ### Fixed
23
+
24
+ - Scanner: findings that reference a detected credential no longer include the live secret value in `evidence` — only a redacted preview (e.g. `api_key: "sk_l****" (redacted)`). Affects report evidence produced by any scan, including those made public via report sharing.
25
+
26
+ ## [1.3.0] - 2026-07-13
27
+
28
+ Initial public release. See git history for prior changes.
package/README.md CHANGED
@@ -51,6 +51,17 @@ agentverify scan ./agents --ci
51
51
 
52
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
53
 
54
+ Exit codes distinguish a security/verification result from an application error, so a pipeline can tell "the agent failed the check" apart from "the scan itself couldn't run":
55
+
56
+ | Code | Meaning |
57
+ | --- | --- |
58
+ | `0` | All scanned files verified |
59
+ | `1` | `NOT_VERIFIED` — a security/verification check failed |
60
+ | `2` | `NOT_ASSESSED` — insufficient evidence for a verdict (see `--allow-not-assessed`) |
61
+ | `3` | Execution error — the scan itself could not run (bad key, network failure, missing file) |
62
+
63
+ All non-zero codes still fail a pipeline step by default, so an existing `if [ $? -ne 0 ]`-style check keeps working unchanged.
64
+
54
65
  ## SDK Usage
55
66
 
56
67
  ```ts
@@ -105,7 +116,7 @@ The workflow requires a valid `AGENTVERIFY_API_KEY`. The local Worker code path
105
116
 
106
117
  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
118
 
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.
119
+ Verified reports can expose a README badge from the report page. A packaged Agent Verify GitHub Action (`AI-Blockchain-Ventures/agentverify@v1`) already exists and produces text/Markdown/JSON output plus an optional workflow artifact — see below. Automatically posting that output as a PR comment (rather than just producing it for you to post) is still a planned enhancement, not yet built.
109
120
 
110
121
  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
122
 
@@ -48,7 +48,12 @@ function normalizeScanResult(data) {
48
48
  scanDuration: 0
49
49
  },
50
50
  saved: data.saved === true,
51
- reportUrl: data.reportUrl ?? null
51
+ reportUrl: data.reportUrl ?? null,
52
+ artifactFingerprint: data.artifactFingerprint,
53
+ reportIntegrity: data.reportIntegrity,
54
+ policyProfile: data.policyProfile ?? null,
55
+ policyResult: data.policyResult ?? null,
56
+ attestation: data.attestation ?? null
52
57
  };
53
58
  }
54
59
  async function scan(input, options) {
@@ -74,7 +79,8 @@ async function scan(input, options) {
74
79
  body: JSON.stringify({
75
80
  content: input.content,
76
81
  fileName: input.fileName ?? "agent",
77
- platform: input.platform
82
+ platform: input.platform,
83
+ policyId: input.policyId
78
84
  }),
79
85
  signal: controller.signal
80
86
  });
package/dist/cli.js CHANGED
@@ -55,7 +55,12 @@ function normalizeScanResult(data) {
55
55
  scanDuration: 0
56
56
  },
57
57
  saved: data.saved === true,
58
- reportUrl: data.reportUrl ?? null
58
+ reportUrl: data.reportUrl ?? null,
59
+ artifactFingerprint: data.artifactFingerprint,
60
+ reportIntegrity: data.reportIntegrity,
61
+ policyProfile: data.policyProfile ?? null,
62
+ policyResult: data.policyResult ?? null,
63
+ attestation: data.attestation ?? null
59
64
  };
60
65
  }
61
66
  async function scan(input, options) {
@@ -81,7 +86,8 @@ async function scan(input, options) {
81
86
  body: JSON.stringify({
82
87
  content: input.content,
83
88
  fileName: input.fileName ?? "agent",
84
- platform: input.platform
89
+ platform: input.platform,
90
+ policyId: input.policyId
85
91
  }),
86
92
  signal: controller.signal
87
93
  });
@@ -110,9 +116,13 @@ async function scan(input, options) {
110
116
  }
111
117
 
112
118
  // src/cli.ts
113
- var VERSION = "1.3.0";
119
+ var VERSION = "1.4.0";
114
120
  var API_URL = process.env.AGENTVERIFY_API_URL ?? "https://agentverify-api.agentverify.workers.dev/v1/scan";
115
121
  var A2SPA_DOCS_URL = "https://aimodularity.com/A2SPA/docs";
122
+ var EXIT_OK = 0;
123
+ var EXIT_NOT_VERIFIED = 1;
124
+ var EXIT_NOT_ASSESSED = 2;
125
+ var EXIT_EXECUTION_ERROR = 3;
116
126
  var c = {
117
127
  reset: "\x1B[0m",
118
128
  bold: "\x1B[1m",
@@ -127,6 +137,7 @@ var c = {
127
137
  var write = (value = "") => process.stdout.write(`${value}
128
138
  `);
129
139
  var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".ts", ".py", ".json", ".yaml", ".yml", ".md", ".mjs", ".cjs"]);
140
+ var MAX_SCAN_FILE_BYTES = 5 * 1024 * 1024;
130
141
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "vendor", "coverage", ".cache"]);
131
142
  function collectFiles(dir) {
132
143
  const files = [];
@@ -158,7 +169,9 @@ function parseArgs(argv) {
158
169
  ci: false,
159
170
  allowNotAssessed: false,
160
171
  help: false,
161
- version: false
172
+ version: false,
173
+ policy: "",
174
+ summaryFile: ""
162
175
  };
163
176
  if (result.command === "--help" || result.command === "-h") {
164
177
  result.command = "";
@@ -176,6 +189,8 @@ function parseArgs(argv) {
176
189
  else if (arg === "--markdown") result.markdown = true;
177
190
  else if (arg === "--ci") result.ci = true;
178
191
  else if (arg === "--allow-not-assessed") result.allowNotAssessed = true;
192
+ else if (arg === "--policy") result.policy = args[++i] ?? "";
193
+ else if (arg === "--summary-file") result.summaryFile = args[++i] ?? "";
179
194
  else if (arg === "--help" || arg === "-h") result.help = true;
180
195
  else if (arg === "--version" || arg === "-v") result.version = true;
181
196
  else if (!arg.startsWith("-")) result.dir = arg;
@@ -203,6 +218,10 @@ ${c.bold}Options:${c.reset}
203
218
  --ci CI mode: concise output and non-zero exit for NOT VERIFIED
204
219
  --allow-not-assessed
205
220
  In CI mode, do not fail only because content is NOT_ASSESSED
221
+ --policy <id> Evaluate a built-in policy profile against this scan's evidence
222
+ in addition to the scanner's own verdict (e.g. financial-agent)
223
+ --summary-file <path>
224
+ Write results as JSON to this path (in addition to any other output)
206
225
  --version, -v Print version
207
226
  --help, -h Show this help message
208
227
 
@@ -222,6 +241,12 @@ ${c.bold}Examples:${c.reset}
222
241
  ${c.gray}# Markdown summary for pull requests${c.reset}
223
242
  agentverify scan . --key av_your_key --markdown
224
243
 
244
+ ${c.bold}Exit codes (--ci):${c.reset}
245
+ 0 All scanned files verified
246
+ 1 NOT VERIFIED \u2014 a security/verification check failed
247
+ 2 NOT ASSESSED \u2014 insufficient evidence for a verdict (see --allow-not-assessed)
248
+ 3 Execution error \u2014 the scan itself could not run (bad key, network, missing file)
249
+
225
250
  ${c.bold}Get your API key:${c.reset}
226
251
  https://aimodularity.com/agentverify/dashboard
227
252
 
@@ -243,6 +268,16 @@ function getFixFirst(result) {
243
268
  function getRelevantThreatCategories(result) {
244
269
  return (result.threatCategories ?? []).filter((item) => item.status !== "not_assessed").slice(0, 6).map((item) => `${item.label} (${item.status.replace("_", " ")})`);
245
270
  }
271
+ function criticalCount(result) {
272
+ return result.findings.filter((f) => f.severity === "critical").length;
273
+ }
274
+ function highCount(result) {
275
+ return result.findings.filter((f) => f.severity === "high").length;
276
+ }
277
+ function fingerprintPreview(result) {
278
+ const hash = result.artifactFingerprint?.artifactHash;
279
+ return hash ? `${hash.slice(0, 12)}\u2026` : null;
280
+ }
246
281
  function printDetailedSummary(result, fileName) {
247
282
  const verified = result.verdict === "VERIFIED";
248
283
  const verdictColor = verified ? c.green : result.verdict === "NOT_ASSESSED" ? c.yellow : c.red;
@@ -253,6 +288,10 @@ ${c.bold}${c.cyan}Agent Verify${c.reset}`);
253
288
  write(`${c.bold}Verdict:${c.reset} ${verdictColor}${result.verdict === "NOT_VERIFIED" ? "NOT VERIFIED" : result.verdict.replace("_", " ")}${c.reset}`);
254
289
  write(`${c.bold}Risk level:${c.reset} ${result.riskLevel}`);
255
290
  write(`${c.bold}Confidence:${c.reset} ${result.confidence}/100`);
291
+ write(`${c.bold}Critical / High findings:${c.reset} ${criticalCount(result)} / ${highCount(result)}`);
292
+ if (result.metadata?.scannerVersion) write(`${c.bold}Ruleset version:${c.reset} ${result.metadata.scannerVersion}`);
293
+ if (fingerprintPreview(result)) write(`${c.bold}Artifact fingerprint:${c.reset} ${fingerprintPreview(result)}`);
294
+ if (result.policyProfile) write(`${c.bold}Policy (${result.policyProfile}):${c.reset} ${result.policyResult === "PASS" ? `${c.green}PASS${c.reset}` : `${c.red}FAIL${c.reset}`}`);
256
295
  write(`${c.bold}Top blocker:${c.reset} ${getTopBlocker(result)}`);
257
296
  write(`
258
297
  ${c.bold}Fix first:${c.reset}`);
@@ -279,7 +318,11 @@ function markdownSummary(results, summary) {
279
318
  lines.push(`## ${item.file}`, "", `Error: ${item.error ?? "Unknown error"}`, "");
280
319
  continue;
281
320
  }
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)}`, "");
321
+ 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}`, `Critical / High findings: ${criticalCount(item.result)} / ${highCount(item.result)}`);
322
+ if (item.result.metadata?.scannerVersion) lines.push(`Ruleset version: ${item.result.metadata.scannerVersion}`);
323
+ if (fingerprintPreview(item.result)) lines.push(`Artifact fingerprint: \`${fingerprintPreview(item.result)}\``);
324
+ if (item.result.policyProfile) lines.push(`Policy (${item.result.policyProfile}): ${item.result.policyResult}`);
325
+ lines.push(`Top blocker: ${getTopBlocker(item.result)}`, "");
283
326
  const fixes = getFixFirst(item.result);
284
327
  if (fixes.length) lines.push("Fix first:", ...fixes.map((fix, index) => `${index + 1}. ${fix}`), "");
285
328
  const threats = getRelevantThreatCategories(item.result);
@@ -289,6 +332,24 @@ function markdownSummary(results, summary) {
289
332
  lines.push(`A2SPA docs: ${A2SPA_DOCS_URL}`);
290
333
  return lines.join("\n");
291
334
  }
335
+ function writeGithubJobSummary(results, summary) {
336
+ const summaryPath = process.env.GITHUB_STEP_SUMMARY;
337
+ if (!summaryPath) return;
338
+ try {
339
+ (0, import_fs.appendFileSync)(summaryPath, `${markdownSummary(results, summary)}
340
+ `);
341
+ } catch (e) {
342
+ console.error(`Warning: could not write GitHub job summary: ${e instanceof Error ? e.message : e}`);
343
+ }
344
+ }
345
+ function writeSummaryFile(path, results, summary) {
346
+ if (!path) return;
347
+ try {
348
+ (0, import_fs.writeFileSync)(path, JSON.stringify({ results, summary }, null, 2));
349
+ } catch (e) {
350
+ console.error(`Warning: could not write summary file: ${e instanceof Error ? e.message : e}`);
351
+ }
352
+ }
292
353
  function printVerdict(result, fileName) {
293
354
  const verified = result.verdict === "VERIFIED";
294
355
  const notAssessed = result.verdict === "NOT_ASSESSED";
@@ -311,7 +372,7 @@ ${c.red}Error:${c.reset} API key required
311
372
  console.error(` Get your key at https://aimodularity.com/agentverify/dashboard/`);
312
373
  console.error(` Example: AGENTVERIFY_API_KEY=av_your_key agentverify scan .
313
374
  `);
314
- process.exit(1);
375
+ process.exit(EXIT_EXECUTION_ERROR);
315
376
  }
316
377
  let files = [];
317
378
  if (args.file) {
@@ -342,10 +403,14 @@ ${c.bold}Agent Verify${c.reset} ${c.gray}v${VERSION}${c.reset}`);
342
403
  const batch = files.slice(i, i + CONCURRENCY);
343
404
  const batchResults = await Promise.allSettled(
344
405
  batch.map(async (filePath) => {
406
+ const fileStat = (0, import_fs.statSync)(filePath);
407
+ if (fileStat.size > MAX_SCAN_FILE_BYTES) {
408
+ throw new Error(`File is ${(fileStat.size / 1024 / 1024).toFixed(1)}MB, over the ${MAX_SCAN_FILE_BYTES / 1024 / 1024}MB scan limit \u2014 skipped`);
409
+ }
345
410
  const content = (0, import_fs.readFileSync)(filePath, "utf-8");
346
411
  const relPath = (0, import_path.relative)(args.dir === "." ? process.cwd() : args.dir, filePath);
347
412
  const result = await scan(
348
- { content, fileName: relPath },
413
+ { content, fileName: relPath, policyId: args.policy || void 0 },
349
414
  { apiKey: args.key, apiUrl: API_URL }
350
415
  );
351
416
  return { filePath, relPath, result };
@@ -376,21 +441,23 @@ ${c.bold}Agent Verify${c.reset} ${c.gray}v${VERSION}${c.reset}`);
376
441
  }
377
442
  }
378
443
  const summary = { total: files.length, verified, notVerified, notAssessed, errors };
444
+ writeGithubJobSummary(jsonResults, summary);
445
+ writeSummaryFile(args.summaryFile, jsonResults, summary);
379
446
  if (args.json) {
380
447
  write(JSON.stringify({ results: jsonResults, summary }, null, 2));
381
- if (errors > 0) process.exit(1);
448
+ if (errors > 0) process.exit(EXIT_EXECUTION_ERROR);
382
449
  if (args.ci) {
383
- if (notVerified > 0) process.exit(1);
384
- if (notAssessed > 0 && !args.allowNotAssessed) process.exit(2);
450
+ if (notVerified > 0) process.exit(EXIT_NOT_VERIFIED);
451
+ if (notAssessed > 0 && !args.allowNotAssessed) process.exit(EXIT_NOT_ASSESSED);
385
452
  }
386
453
  return;
387
454
  }
388
455
  if (args.markdown) {
389
456
  write(markdownSummary(jsonResults, summary));
390
- if (errors > 0) process.exit(1);
457
+ if (errors > 0) process.exit(EXIT_EXECUTION_ERROR);
391
458
  if (args.ci) {
392
- if (notVerified > 0) process.exit(1);
393
- if (notAssessed > 0 && !args.allowNotAssessed) process.exit(2);
459
+ if (notVerified > 0) process.exit(EXIT_NOT_VERIFIED);
460
+ if (notAssessed > 0 && !args.allowNotAssessed) process.exit(EXIT_NOT_ASSESSED);
394
461
  }
395
462
  return;
396
463
  }
@@ -410,21 +477,21 @@ ${c.gray}Saved CLI reports appear in your dashboard: https://aimodularity.com/ag
410
477
  ${c.gray}No dashboard report was saved. Check API key access and retry if you need dashboard sync.${c.reset}`);
411
478
  write(`${c.gray}A2SPA docs: ${A2SPA_DOCS_URL}${c.reset}
412
479
  `);
413
- if (errors > 0) process.exit(1);
480
+ if (errors > 0) process.exit(EXIT_EXECUTION_ERROR);
414
481
  if (args.ci) {
415
- if (notVerified > 0) process.exit(1);
416
- if (notAssessed > 0 && !args.allowNotAssessed) process.exit(2);
482
+ if (notVerified > 0) process.exit(EXIT_NOT_VERIFIED);
483
+ if (notAssessed > 0 && !args.allowNotAssessed) process.exit(EXIT_NOT_ASSESSED);
417
484
  }
418
485
  }
419
486
  async function main() {
420
487
  const args = parseArgs(process.argv);
421
488
  if (args.version) {
422
489
  write(`agentverify v${VERSION}`);
423
- process.exit(0);
490
+ process.exit(EXIT_OK);
424
491
  }
425
492
  if (args.help || !args.command) {
426
493
  printHelp();
427
- process.exit(0);
494
+ process.exit(EXIT_OK);
428
495
  }
429
496
  if (args.command === "scan") {
430
497
  await runScan(args);
@@ -433,11 +500,11 @@ async function main() {
433
500
  ${c.red}Unknown command: ${args.command}${c.reset}`);
434
501
  console.error(`Run ${c.cyan}agentverify --help${c.reset} for usage
435
502
  `);
436
- process.exit(1);
503
+ process.exit(EXIT_EXECUTION_ERROR);
437
504
  }
438
505
  }
439
506
  main().catch((err) => {
440
507
  console.error(`
441
508
  ${c.red}Fatal error:${c.reset}`, err instanceof Error ? err.message : err);
442
- process.exit(1);
509
+ process.exit(EXIT_EXECUTION_ERROR);
443
510
  });
package/dist/cli.mjs CHANGED
@@ -1,14 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  scan
4
- } from "./chunk-PY3NS2YH.mjs";
4
+ } from "./chunk-XVA2M2HS.mjs";
5
5
 
6
6
  // src/cli.ts
7
- import { readFileSync, readdirSync, statSync } from "fs";
7
+ import { readFileSync, readdirSync, statSync, appendFileSync, writeFileSync } from "fs";
8
8
  import { join, relative, extname } from "path";
9
- var VERSION = "1.3.0";
9
+ var VERSION = "1.4.0";
10
10
  var API_URL = process.env.AGENTVERIFY_API_URL ?? "https://agentverify-api.agentverify.workers.dev/v1/scan";
11
11
  var A2SPA_DOCS_URL = "https://aimodularity.com/A2SPA/docs";
12
+ var EXIT_OK = 0;
13
+ var EXIT_NOT_VERIFIED = 1;
14
+ var EXIT_NOT_ASSESSED = 2;
15
+ var EXIT_EXECUTION_ERROR = 3;
12
16
  var c = {
13
17
  reset: "\x1B[0m",
14
18
  bold: "\x1B[1m",
@@ -23,6 +27,7 @@ var c = {
23
27
  var write = (value = "") => process.stdout.write(`${value}
24
28
  `);
25
29
  var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".ts", ".py", ".json", ".yaml", ".yml", ".md", ".mjs", ".cjs"]);
30
+ var MAX_SCAN_FILE_BYTES = 5 * 1024 * 1024;
26
31
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "vendor", "coverage", ".cache"]);
27
32
  function collectFiles(dir) {
28
33
  const files = [];
@@ -54,7 +59,9 @@ function parseArgs(argv) {
54
59
  ci: false,
55
60
  allowNotAssessed: false,
56
61
  help: false,
57
- version: false
62
+ version: false,
63
+ policy: "",
64
+ summaryFile: ""
58
65
  };
59
66
  if (result.command === "--help" || result.command === "-h") {
60
67
  result.command = "";
@@ -72,6 +79,8 @@ function parseArgs(argv) {
72
79
  else if (arg === "--markdown") result.markdown = true;
73
80
  else if (arg === "--ci") result.ci = true;
74
81
  else if (arg === "--allow-not-assessed") result.allowNotAssessed = true;
82
+ else if (arg === "--policy") result.policy = args[++i] ?? "";
83
+ else if (arg === "--summary-file") result.summaryFile = args[++i] ?? "";
75
84
  else if (arg === "--help" || arg === "-h") result.help = true;
76
85
  else if (arg === "--version" || arg === "-v") result.version = true;
77
86
  else if (!arg.startsWith("-")) result.dir = arg;
@@ -99,6 +108,10 @@ ${c.bold}Options:${c.reset}
99
108
  --ci CI mode: concise output and non-zero exit for NOT VERIFIED
100
109
  --allow-not-assessed
101
110
  In CI mode, do not fail only because content is NOT_ASSESSED
111
+ --policy <id> Evaluate a built-in policy profile against this scan's evidence
112
+ in addition to the scanner's own verdict (e.g. financial-agent)
113
+ --summary-file <path>
114
+ Write results as JSON to this path (in addition to any other output)
102
115
  --version, -v Print version
103
116
  --help, -h Show this help message
104
117
 
@@ -118,6 +131,12 @@ ${c.bold}Examples:${c.reset}
118
131
  ${c.gray}# Markdown summary for pull requests${c.reset}
119
132
  agentverify scan . --key av_your_key --markdown
120
133
 
134
+ ${c.bold}Exit codes (--ci):${c.reset}
135
+ 0 All scanned files verified
136
+ 1 NOT VERIFIED \u2014 a security/verification check failed
137
+ 2 NOT ASSESSED \u2014 insufficient evidence for a verdict (see --allow-not-assessed)
138
+ 3 Execution error \u2014 the scan itself could not run (bad key, network, missing file)
139
+
121
140
  ${c.bold}Get your API key:${c.reset}
122
141
  https://aimodularity.com/agentverify/dashboard
123
142
 
@@ -139,6 +158,16 @@ function getFixFirst(result) {
139
158
  function getRelevantThreatCategories(result) {
140
159
  return (result.threatCategories ?? []).filter((item) => item.status !== "not_assessed").slice(0, 6).map((item) => `${item.label} (${item.status.replace("_", " ")})`);
141
160
  }
161
+ function criticalCount(result) {
162
+ return result.findings.filter((f) => f.severity === "critical").length;
163
+ }
164
+ function highCount(result) {
165
+ return result.findings.filter((f) => f.severity === "high").length;
166
+ }
167
+ function fingerprintPreview(result) {
168
+ const hash = result.artifactFingerprint?.artifactHash;
169
+ return hash ? `${hash.slice(0, 12)}\u2026` : null;
170
+ }
142
171
  function printDetailedSummary(result, fileName) {
143
172
  const verified = result.verdict === "VERIFIED";
144
173
  const verdictColor = verified ? c.green : result.verdict === "NOT_ASSESSED" ? c.yellow : c.red;
@@ -149,6 +178,10 @@ ${c.bold}${c.cyan}Agent Verify${c.reset}`);
149
178
  write(`${c.bold}Verdict:${c.reset} ${verdictColor}${result.verdict === "NOT_VERIFIED" ? "NOT VERIFIED" : result.verdict.replace("_", " ")}${c.reset}`);
150
179
  write(`${c.bold}Risk level:${c.reset} ${result.riskLevel}`);
151
180
  write(`${c.bold}Confidence:${c.reset} ${result.confidence}/100`);
181
+ write(`${c.bold}Critical / High findings:${c.reset} ${criticalCount(result)} / ${highCount(result)}`);
182
+ if (result.metadata?.scannerVersion) write(`${c.bold}Ruleset version:${c.reset} ${result.metadata.scannerVersion}`);
183
+ if (fingerprintPreview(result)) write(`${c.bold}Artifact fingerprint:${c.reset} ${fingerprintPreview(result)}`);
184
+ if (result.policyProfile) write(`${c.bold}Policy (${result.policyProfile}):${c.reset} ${result.policyResult === "PASS" ? `${c.green}PASS${c.reset}` : `${c.red}FAIL${c.reset}`}`);
152
185
  write(`${c.bold}Top blocker:${c.reset} ${getTopBlocker(result)}`);
153
186
  write(`
154
187
  ${c.bold}Fix first:${c.reset}`);
@@ -175,7 +208,11 @@ function markdownSummary(results, summary) {
175
208
  lines.push(`## ${item.file}`, "", `Error: ${item.error ?? "Unknown error"}`, "");
176
209
  continue;
177
210
  }
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)}`, "");
211
+ 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}`, `Critical / High findings: ${criticalCount(item.result)} / ${highCount(item.result)}`);
212
+ if (item.result.metadata?.scannerVersion) lines.push(`Ruleset version: ${item.result.metadata.scannerVersion}`);
213
+ if (fingerprintPreview(item.result)) lines.push(`Artifact fingerprint: \`${fingerprintPreview(item.result)}\``);
214
+ if (item.result.policyProfile) lines.push(`Policy (${item.result.policyProfile}): ${item.result.policyResult}`);
215
+ lines.push(`Top blocker: ${getTopBlocker(item.result)}`, "");
179
216
  const fixes = getFixFirst(item.result);
180
217
  if (fixes.length) lines.push("Fix first:", ...fixes.map((fix, index) => `${index + 1}. ${fix}`), "");
181
218
  const threats = getRelevantThreatCategories(item.result);
@@ -185,6 +222,24 @@ function markdownSummary(results, summary) {
185
222
  lines.push(`A2SPA docs: ${A2SPA_DOCS_URL}`);
186
223
  return lines.join("\n");
187
224
  }
225
+ function writeGithubJobSummary(results, summary) {
226
+ const summaryPath = process.env.GITHUB_STEP_SUMMARY;
227
+ if (!summaryPath) return;
228
+ try {
229
+ appendFileSync(summaryPath, `${markdownSummary(results, summary)}
230
+ `);
231
+ } catch (e) {
232
+ console.error(`Warning: could not write GitHub job summary: ${e instanceof Error ? e.message : e}`);
233
+ }
234
+ }
235
+ function writeSummaryFile(path, results, summary) {
236
+ if (!path) return;
237
+ try {
238
+ writeFileSync(path, JSON.stringify({ results, summary }, null, 2));
239
+ } catch (e) {
240
+ console.error(`Warning: could not write summary file: ${e instanceof Error ? e.message : e}`);
241
+ }
242
+ }
188
243
  function printVerdict(result, fileName) {
189
244
  const verified = result.verdict === "VERIFIED";
190
245
  const notAssessed = result.verdict === "NOT_ASSESSED";
@@ -207,7 +262,7 @@ ${c.red}Error:${c.reset} API key required
207
262
  console.error(` Get your key at https://aimodularity.com/agentverify/dashboard/`);
208
263
  console.error(` Example: AGENTVERIFY_API_KEY=av_your_key agentverify scan .
209
264
  `);
210
- process.exit(1);
265
+ process.exit(EXIT_EXECUTION_ERROR);
211
266
  }
212
267
  let files = [];
213
268
  if (args.file) {
@@ -238,10 +293,14 @@ ${c.bold}Agent Verify${c.reset} ${c.gray}v${VERSION}${c.reset}`);
238
293
  const batch = files.slice(i, i + CONCURRENCY);
239
294
  const batchResults = await Promise.allSettled(
240
295
  batch.map(async (filePath) => {
296
+ const fileStat = statSync(filePath);
297
+ if (fileStat.size > MAX_SCAN_FILE_BYTES) {
298
+ throw new Error(`File is ${(fileStat.size / 1024 / 1024).toFixed(1)}MB, over the ${MAX_SCAN_FILE_BYTES / 1024 / 1024}MB scan limit \u2014 skipped`);
299
+ }
241
300
  const content = readFileSync(filePath, "utf-8");
242
301
  const relPath = relative(args.dir === "." ? process.cwd() : args.dir, filePath);
243
302
  const result = await scan(
244
- { content, fileName: relPath },
303
+ { content, fileName: relPath, policyId: args.policy || void 0 },
245
304
  { apiKey: args.key, apiUrl: API_URL }
246
305
  );
247
306
  return { filePath, relPath, result };
@@ -272,21 +331,23 @@ ${c.bold}Agent Verify${c.reset} ${c.gray}v${VERSION}${c.reset}`);
272
331
  }
273
332
  }
274
333
  const summary = { total: files.length, verified, notVerified, notAssessed, errors };
334
+ writeGithubJobSummary(jsonResults, summary);
335
+ writeSummaryFile(args.summaryFile, jsonResults, summary);
275
336
  if (args.json) {
276
337
  write(JSON.stringify({ results: jsonResults, summary }, null, 2));
277
- if (errors > 0) process.exit(1);
338
+ if (errors > 0) process.exit(EXIT_EXECUTION_ERROR);
278
339
  if (args.ci) {
279
- if (notVerified > 0) process.exit(1);
280
- if (notAssessed > 0 && !args.allowNotAssessed) process.exit(2);
340
+ if (notVerified > 0) process.exit(EXIT_NOT_VERIFIED);
341
+ if (notAssessed > 0 && !args.allowNotAssessed) process.exit(EXIT_NOT_ASSESSED);
281
342
  }
282
343
  return;
283
344
  }
284
345
  if (args.markdown) {
285
346
  write(markdownSummary(jsonResults, summary));
286
- if (errors > 0) process.exit(1);
347
+ if (errors > 0) process.exit(EXIT_EXECUTION_ERROR);
287
348
  if (args.ci) {
288
- if (notVerified > 0) process.exit(1);
289
- if (notAssessed > 0 && !args.allowNotAssessed) process.exit(2);
349
+ if (notVerified > 0) process.exit(EXIT_NOT_VERIFIED);
350
+ if (notAssessed > 0 && !args.allowNotAssessed) process.exit(EXIT_NOT_ASSESSED);
290
351
  }
291
352
  return;
292
353
  }
@@ -306,21 +367,21 @@ ${c.gray}Saved CLI reports appear in your dashboard: https://aimodularity.com/ag
306
367
  ${c.gray}No dashboard report was saved. Check API key access and retry if you need dashboard sync.${c.reset}`);
307
368
  write(`${c.gray}A2SPA docs: ${A2SPA_DOCS_URL}${c.reset}
308
369
  `);
309
- if (errors > 0) process.exit(1);
370
+ if (errors > 0) process.exit(EXIT_EXECUTION_ERROR);
310
371
  if (args.ci) {
311
- if (notVerified > 0) process.exit(1);
312
- if (notAssessed > 0 && !args.allowNotAssessed) process.exit(2);
372
+ if (notVerified > 0) process.exit(EXIT_NOT_VERIFIED);
373
+ if (notAssessed > 0 && !args.allowNotAssessed) process.exit(EXIT_NOT_ASSESSED);
313
374
  }
314
375
  }
315
376
  async function main() {
316
377
  const args = parseArgs(process.argv);
317
378
  if (args.version) {
318
379
  write(`agentverify v${VERSION}`);
319
- process.exit(0);
380
+ process.exit(EXIT_OK);
320
381
  }
321
382
  if (args.help || !args.command) {
322
383
  printHelp();
323
- process.exit(0);
384
+ process.exit(EXIT_OK);
324
385
  }
325
386
  if (args.command === "scan") {
326
387
  await runScan(args);
@@ -329,11 +390,11 @@ async function main() {
329
390
  ${c.red}Unknown command: ${args.command}${c.reset}`);
330
391
  console.error(`Run ${c.cyan}agentverify --help${c.reset} for usage
331
392
  `);
332
- process.exit(1);
393
+ process.exit(EXIT_EXECUTION_ERROR);
333
394
  }
334
395
  }
335
396
  main().catch((err) => {
336
397
  console.error(`
337
398
  ${c.red}Fatal error:${c.reset}`, err instanceof Error ? err.message : err);
338
- process.exit(1);
399
+ process.exit(EXIT_EXECUTION_ERROR);
339
400
  });
package/dist/sdk.d.mts CHANGED
@@ -2,6 +2,27 @@ interface ScanInput {
2
2
  content: string;
3
3
  fileName?: string;
4
4
  platform?: string;
5
+ /** One of the built-in policy profile ids (e.g. "financial-agent") — evaluated server-side against this scan's evidence, in addition to (never instead of) the scanner's own verdict. */
6
+ policyId?: string;
7
+ }
8
+ interface ArtifactFingerprint {
9
+ artifactHash: string;
10
+ artifactHashAlgorithm: string;
11
+ artifactFingerprintVersion: string;
12
+ }
13
+ interface ReportIntegrity {
14
+ reportHash: string;
15
+ algorithm: string;
16
+ schemaVersion: string;
17
+ scannerVersion: string;
18
+ scanId: string;
19
+ timestamp: string;
20
+ }
21
+ interface SignedAttestation {
22
+ payload: Record<string, unknown>;
23
+ signature: string;
24
+ algorithm: string;
25
+ publicKey: Record<string, unknown>;
5
26
  }
6
27
  type Verdict = 'VERIFIED' | 'NOT_VERIFIED' | 'NOT_ASSESSED';
7
28
  type RiskLevel = 'Low Risk' | 'Moderate Risk' | 'High Risk';
@@ -103,6 +124,11 @@ interface ScanResult {
103
124
  };
104
125
  saved: boolean;
105
126
  reportUrl?: string | null;
127
+ artifactFingerprint?: ArtifactFingerprint;
128
+ reportIntegrity?: ReportIntegrity;
129
+ policyProfile?: string | null;
130
+ policyResult?: string | null;
131
+ attestation?: SignedAttestation | null;
106
132
  }
107
133
  interface ScanOptions {
108
134
  apiKey: string;
package/dist/sdk.d.ts CHANGED
@@ -2,6 +2,27 @@ interface ScanInput {
2
2
  content: string;
3
3
  fileName?: string;
4
4
  platform?: string;
5
+ /** One of the built-in policy profile ids (e.g. "financial-agent") — evaluated server-side against this scan's evidence, in addition to (never instead of) the scanner's own verdict. */
6
+ policyId?: string;
7
+ }
8
+ interface ArtifactFingerprint {
9
+ artifactHash: string;
10
+ artifactHashAlgorithm: string;
11
+ artifactFingerprintVersion: string;
12
+ }
13
+ interface ReportIntegrity {
14
+ reportHash: string;
15
+ algorithm: string;
16
+ schemaVersion: string;
17
+ scannerVersion: string;
18
+ scanId: string;
19
+ timestamp: string;
20
+ }
21
+ interface SignedAttestation {
22
+ payload: Record<string, unknown>;
23
+ signature: string;
24
+ algorithm: string;
25
+ publicKey: Record<string, unknown>;
5
26
  }
6
27
  type Verdict = 'VERIFIED' | 'NOT_VERIFIED' | 'NOT_ASSESSED';
7
28
  type RiskLevel = 'Low Risk' | 'Moderate Risk' | 'High Risk';
@@ -103,6 +124,11 @@ interface ScanResult {
103
124
  };
104
125
  saved: boolean;
105
126
  reportUrl?: string | null;
127
+ artifactFingerprint?: ArtifactFingerprint;
128
+ reportIntegrity?: ReportIntegrity;
129
+ policyProfile?: string | null;
130
+ policyResult?: string | null;
131
+ attestation?: SignedAttestation | null;
106
132
  }
107
133
  interface ScanOptions {
108
134
  apiKey: string;
package/dist/sdk.js CHANGED
@@ -73,7 +73,12 @@ function normalizeScanResult(data) {
73
73
  scanDuration: 0
74
74
  },
75
75
  saved: data.saved === true,
76
- reportUrl: data.reportUrl ?? null
76
+ reportUrl: data.reportUrl ?? null,
77
+ artifactFingerprint: data.artifactFingerprint,
78
+ reportIntegrity: data.reportIntegrity,
79
+ policyProfile: data.policyProfile ?? null,
80
+ policyResult: data.policyResult ?? null,
81
+ attestation: data.attestation ?? null
77
82
  };
78
83
  }
79
84
  async function scan(input, options) {
@@ -99,7 +104,8 @@ async function scan(input, options) {
99
104
  body: JSON.stringify({
100
105
  content: input.content,
101
106
  fileName: input.fileName ?? "agent",
102
- platform: input.platform
107
+ platform: input.platform,
108
+ policyId: input.policyId
103
109
  }),
104
110
  signal: controller.signal
105
111
  });
package/dist/sdk.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  scan,
3
3
  scanMany
4
- } from "./chunk-PY3NS2YH.mjs";
4
+ } from "./chunk-XVA2M2HS.mjs";
5
5
  export {
6
6
  scan,
7
7
  scanMany
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "agentverify",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "CLI and SDK for Agent Verify — execution trust analysis for AI agents",
5
5
  "bin": {
6
- "agentverify": "./dist/cli.js"
6
+ "agentverify": "dist/cli.js"
7
7
  },
8
8
  "main": "./dist/sdk.js",
9
9
  "module": "./dist/sdk.mjs",
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "./cli": "./dist/cli.js"
18
18
  },
19
- "files": ["dist", "README.md", "LICENSE"],
19
+ "files": ["dist", "README.md", "LICENSE", "CHANGELOG.md"],
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",