agentcert 0.4.0 → 0.5.1

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 CHANGED
@@ -6,6 +6,10 @@ AgentCert checks what an agent may do, whether it passed pre-release evidence,
6
6
  whether a high-risk runtime action may proceed, and who can verify the observed
7
7
  outcome. It writes portable reports and accumulates a local failure corpus.
8
8
 
9
+ [Public assurance demo](https://agentcert-control-plane.onrender.com/demo) |
10
+ [Private workspace](https://agentcert-control-plane.onrender.com/app) |
11
+ [GitHub source](https://github.com/Kakarottoooo/agentcert)
12
+
9
13
  ## 5-minute local path
10
14
 
11
15
  ```bash
@@ -94,6 +98,23 @@ routes, with a 5-second timeout and 10-request-per-minute process-local cap.
94
98
  The Stripe key is environment-only and is never written to output or evidence.
95
99
  See the [Bounded Vendor Sandbox Egress guide](https://github.com/Kakarottoooo/agentcert/blob/main/docs/bounded-vendor-sandbox-egress.md).
96
100
 
101
+ Release maintainers can run the protected, manual
102
+ `Real Stripe sandbox acceptance` GitHub workflow. It performs the bounded read,
103
+ independently scans the generated report before upload, validates it again at
104
+ the CLI boundary, uploads the v0.4 evidence, and compares it with prior
105
+ protected runs. See the [real vendor acceptance guide](https://github.com/Kakarottoooo/agentcert/blob/main/docs/real-vendor-acceptance.md).
106
+
107
+ The workflow's upload step is also available as a narrow command for an
108
+ already-generated report:
109
+
110
+ ```bash
111
+ npx agentcert sandbox upload-report --report .agentcert/vendor-sandbox/current-report.json --external-id vendor-acceptance:stripe:<run>:<attempt>
112
+ ```
113
+
114
+ This command accepts only AgentCert sandbox conformance and vendor-egress
115
+ contracts and rejects reports containing credential-shaped values or forbidden
116
+ sensitive fields.
117
+
97
118
  Review/export helpers:
98
119
 
99
120
  ```bash
package/dist/sandbox.js CHANGED
@@ -1,4 +1,4 @@
1
- import { mkdir, writeFile } from "node:fs/promises";
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { dirname, resolve } from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
4
  import { resolveConnection } from "./credentials.js";
@@ -19,6 +19,8 @@ export async function runSandboxCommand(args) {
19
19
  return pushSandboxCertification(args.slice(1));
20
20
  if (action === "stripe-readonly")
21
21
  return runStripeSandboxReadOnly(args.slice(1));
22
+ if (action === "upload-report")
23
+ return uploadSandboxReport(args.slice(1));
22
24
  throw new Error(`Unknown sandbox command ${JSON.stringify(action)}. Run \`npx agentcert sandbox --help\`.`);
23
25
  }
24
26
  export async function initializeSandboxAdapter(args) {
@@ -103,6 +105,59 @@ export async function runStripeSandboxReadOnly(args, runtimeOverride) {
103
105
  }
104
106
  return { exitCode: report.verdict.passed ? 0 : 1, reportPath, report };
105
107
  }
108
+ export async function uploadSandboxReport(args, runtimeOverride) {
109
+ const requestedPath = flag(args, "--report");
110
+ if (!requestedPath)
111
+ throw new Error("Sandbox report upload requires --report <report.json>.");
112
+ const reportPath = resolve(requestedPath);
113
+ const report = parseUploadableSandboxReport(await readFile(reportPath, "utf8"));
114
+ const runtime = runtimeOverride ?? await loadSandboxRuntime();
115
+ const connection = await resolveConnection({
116
+ name: flag(args, "--connection"),
117
+ server: flag(args, "--server"),
118
+ projectId: flag(args, "--project"),
119
+ apiKey: flag(args, "--api-key"),
120
+ });
121
+ const uploaded = await runtime.uploadSandboxCertificationReport(report, {
122
+ baseUrl: connection.server,
123
+ projectId: connection.projectId,
124
+ apiKey: connection.apiKey,
125
+ externalId: flag(args, "--external-id"),
126
+ });
127
+ process.stdout.write(`Uploaded validated sandbox report: ${reportPath}\n`);
128
+ process.stdout.write(`Hosted sandbox run: ${String(uploaded.run.id ?? "created")}\n`);
129
+ process.stdout.write(`Hosted evidence: ${String(uploaded.evidence.id ?? "created")}\n`);
130
+ return { exitCode: report.verdict.passed ? 0 : 1, reportPath, report };
131
+ }
132
+ export function parseUploadableSandboxReport(rawText) {
133
+ let value;
134
+ try {
135
+ value = JSON.parse(rawText);
136
+ }
137
+ catch {
138
+ throw new Error("Sandbox report is not valid JSON.");
139
+ }
140
+ if (!isRecord(value))
141
+ throw new Error("Sandbox report must be a JSON object.");
142
+ const supported = (value.kind === "agentcert.sandbox_adapter_conformance"
143
+ && value.schemaVersion === "agentcert.sandbox_adapter_conformance.v0.2")
144
+ || (value.kind === "agentcert.sandbox_vendor_egress"
145
+ && value.schemaVersion === "agentcert.sandbox_vendor_egress.v0.4");
146
+ if (!supported)
147
+ throw new Error("Sandbox report kind or schema version is not supported for upload.");
148
+ if (typeof value.implementation !== "string" || !value.implementation.trim()
149
+ || typeof value.generatedAt !== "string" || !Number.isFinite(Date.parse(value.generatedAt))
150
+ || !isRecord(value.verdict) || typeof value.verdict.passed !== "boolean"
151
+ || typeof value.verdict.score !== "number" || value.verdict.score < 0 || value.verdict.score > 100
152
+ || !Array.isArray(value.checks)) {
153
+ throw new Error("Sandbox report is missing required conformance fields.");
154
+ }
155
+ const findings = sensitiveReportFindings(value, rawText);
156
+ if (findings.length > 0) {
157
+ throw new Error(`Sandbox report contains forbidden credential material: ${findings.join(", ")}.`);
158
+ }
159
+ return value;
160
+ }
106
161
  export async function loadSandboxAdapter(adapterPath) {
107
162
  let module;
108
163
  try {
@@ -146,18 +201,28 @@ Use \`agentcert connect\` first, or pass --server, --project, and --api-key.
146
201
  Reads one Stripe sandbox PaymentIntent through a fixed GET/resource allowlist,
147
202
  writes a redacted evidence report, and optionally uploads it to AgentCert Hosted.
148
203
  The Stripe key is read only from STRIPE_RESTRICTED_TEST_KEY.
204
+ `;
205
+ if (action === "upload-report")
206
+ return `Usage:
207
+ agentcert sandbox upload-report --report .agentcert/vendor-sandbox/current-report.json --external-id <id>
208
+
209
+ Validates a recognized AgentCert sandbox report, rejects credential material,
210
+ and uploads it through the saved AgentCert connection. This command never
211
+ executes a vendor request.
149
212
  `;
150
213
  return `Usage:
151
214
  agentcert sandbox init [--adapter agentcert.sandbox.mjs]
152
215
  agentcert sandbox certify --adapter ./my-sandbox-adapter.js
153
216
  agentcert sandbox push --adapter ./my-sandbox-adapter.js
154
217
  agentcert sandbox stripe-readonly --payment-intent pi_... [--push]
218
+ agentcert sandbox upload-report --report <report.json>
155
219
 
156
220
  Commands:
157
221
  init Write one dependency-free adapter template
158
222
  certify Run deterministic local conformance checks
159
223
  push Certify and upload the report to AgentCert Hosted
160
224
  stripe-readonly Run one bounded Stripe sandbox read and write evidence
225
+ upload-report Validate and upload an existing sandbox report
161
226
 
162
227
  Common options:
163
228
  --adapter <path> Adapter module (default: agentcert.sandbox.mjs)
@@ -199,6 +264,38 @@ function isSandboxSystem(value) {
199
264
  && ["createTenant", "deleteTenant", "resetTenant", "seedTenant", "hasTenant", "snapshotTenant", "adapterForTenant"]
200
265
  .every((method) => typeof system[method] === "function");
201
266
  }
267
+ function sensitiveReportFindings(value, rawText) {
268
+ const findings = new Set();
269
+ const forbiddenFields = new Set(["authorization", "headers", "apikey", "restrictedapikey", "secretkey", "clientsecret", "metadata", "rawresponse", "responsebody"]);
270
+ const inspect = (entry) => {
271
+ if (Array.isArray(entry)) {
272
+ entry.forEach(inspect);
273
+ return;
274
+ }
275
+ if (!isRecord(entry))
276
+ return;
277
+ for (const [key, nested] of Object.entries(entry)) {
278
+ if (forbiddenFields.has(key.replace(/[^a-z]/gi, "").toLowerCase()))
279
+ findings.add(`field:${key}`);
280
+ inspect(nested);
281
+ }
282
+ };
283
+ inspect(value);
284
+ if (/\b(?:rk|sk)_(?:test|live)_[A-Za-z0-9_]{8,}\b/.test(rawText))
285
+ findings.add("stripe-key");
286
+ if (/\bac_live_[A-Za-z0-9_-]{8,}\b/.test(rawText))
287
+ findings.add("agentcert-key");
288
+ if (/\bBearer\s+\S+/i.test(rawText))
289
+ findings.add("authorization-value");
290
+ for (const secret of [process.env.STRIPE_RESTRICTED_TEST_KEY, process.env.AGENTCERT_API_KEY]) {
291
+ if (secret && secret.length >= 8 && rawText.includes(secret))
292
+ findings.add("exact-secret");
293
+ }
294
+ return [...findings].sort();
295
+ }
296
+ function isRecord(value) {
297
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
298
+ }
202
299
  function flag(args, name) {
203
300
  const index = args.indexOf(name);
204
301
  return index >= 0 ? args[index + 1] : undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"sandbox-hosted.d.ts","sourceRoot":"","sources":["../../../../onegent-runtime/src/sandbox-hosted.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sandbox-hosted.d.ts","sourceRoot":"","sources":["../../../../onegent-runtime/src/sandbox-hosted.ts"],"names":[],"mappings":"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"}
@@ -68,6 +68,15 @@ export async function uploadSandboxCertificationReport(report, options) {
68
68
  const digest = createHash("sha256").update(bytes).digest("hex");
69
69
  const operationId = `sandbox-upload-${digest.slice(0, 32)}`;
70
70
  const externalId = options.externalId?.trim() || `${report.kind}:${report.implementation}:${report.generatedAt}`;
71
+ const vendorMetadata = report.kind === "agentcert.sandbox_vendor_egress"
72
+ ? {
73
+ vendor: report.vendor,
74
+ environment: report.environment,
75
+ policySha256: createHash("sha256").update(canonicalJson(report.policy)).digest("hex"),
76
+ requestDurationMs: report.audit.find((entry) => entry.outcome === "allowed")?.durationMs,
77
+ acceptanceType: externalId.startsWith("vendor-acceptance:stripe:") ? "real_vendor_sandbox" : "vendor_sandbox",
78
+ }
79
+ : {};
71
80
  const run = await jsonRequest(requestFetch, `${projectUrl}/runs`, apiKey, {
72
81
  method: "POST",
73
82
  headers: { "idempotency-key": `${operationId}:run` },
@@ -81,6 +90,7 @@ export async function uploadSandboxCertificationReport(report, options) {
81
90
  evidenceType: report.kind,
82
91
  implementation: report.implementation,
83
92
  sandboxOnly: true,
93
+ ...vendorMetadata,
84
94
  },
85
95
  }),
86
96
  });
@@ -131,3 +141,12 @@ function required(value, name) {
131
141
  throw new Error(`Sandbox hosted upload ${name} is required.`);
132
142
  return normalized;
133
143
  }
144
+ function canonicalJson(value) {
145
+ if (Array.isArray(value))
146
+ return `[${value.map(canonicalJson).join(",")}]`;
147
+ if (value && typeof value === "object") {
148
+ const record = value;
149
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
150
+ }
151
+ return JSON.stringify(value);
152
+ }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "agentcert",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Assurance release gates, runtime evidence, and regression CI for AI agents.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
- "homepage": "https://github.com/Kakarottoooo/agentcert#readme",
7
+ "homepage": "https://agentcert-control-plane.onrender.com/demo",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/Kakarottoooo/agentcert.git",