agentcert 0.4.0 → 0.5.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 CHANGED
@@ -94,6 +94,23 @@ routes, with a 5-second timeout and 10-request-per-minute process-local cap.
94
94
  The Stripe key is environment-only and is never written to output or evidence.
95
95
  See the [Bounded Vendor Sandbox Egress guide](https://github.com/Kakarottoooo/agentcert/blob/main/docs/bounded-vendor-sandbox-egress.md).
96
96
 
97
+ Release maintainers can run the protected, manual
98
+ `Real Stripe sandbox acceptance` GitHub workflow. It performs the bounded read,
99
+ independently scans the generated report before upload, validates it again at
100
+ the CLI boundary, uploads the v0.4 evidence, and compares it with prior
101
+ protected runs. See the [real vendor acceptance guide](https://github.com/Kakarottoooo/agentcert/blob/main/docs/real-vendor-acceptance.md).
102
+
103
+ The workflow's upload step is also available as a narrow command for an
104
+ already-generated report:
105
+
106
+ ```bash
107
+ npx agentcert sandbox upload-report --report .agentcert/vendor-sandbox/current-report.json --external-id vendor-acceptance:stripe:<run>:<attempt>
108
+ ```
109
+
110
+ This command accepts only AgentCert sandbox conformance and vendor-egress
111
+ contracts and rejects reports containing credential-shaped values or forbidden
112
+ sensitive fields.
113
+
97
114
  Review/export helpers:
98
115
 
99
116
  ```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,6 +1,6 @@
1
1
  {
2
2
  "name": "agentcert",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Assurance release gates, runtime evidence, and regression CI for AI agents.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",