@coproduct_inc/verify 0.1.0 → 0.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/CHANGELOG.md ADDED
@@ -0,0 +1,47 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@coproduct_inc/verify`.
4
+
5
+ ## 0.3.0
6
+
7
+ ### Added
8
+ - **Offline Ed25519 license keys** (`./license` export + `nucleus-license` bin) —
9
+ entitlement with no user database and no callback: the vendor signs a tiny
10
+ claims payload; the Pro path verifies it against a pinned public key.
11
+ `keygen` / `issue` / `verify`; expiry enforced; tamper-evident.
12
+ - **Converting demo** (`examples/converting-demo.sh`) — recordable 4-beat demo
13
+ (clean attests · OpenClaw impersonation refused · forged verdict caught two
14
+ ways · CI gate), runs against the published package or `LOCAL=1`.
15
+ - **`WHY-VERIFY.md`** — positioning one-pager (the independent-recompute
16
+ differentiator + honest Microsoft-AGT contrast) and a static `site/` landing
17
+ page (zero-JS, zero-backend) wired for a Merchant-of-Record checkout.
18
+
19
+ ## 0.2.0
20
+
21
+ ### Added
22
+ - **Producer-side instrumentation** — turn a nucleus-tool-proxy trace into a
23
+ signed in-bounds attestation receipt.
24
+ - `./toolproxy` export: `attestToolProxyRun`, `boundaryFromExport`,
25
+ `traceEventsFromRecords`, `parseExport`. Maps the tool-proxy's verdict
26
+ records (`actor`/`operation`/`verdict`/`subject`/`deny_reason`, mirroring
27
+ the `verdict_sink` `tool_call` span) into the receipt schema and signs it.
28
+ Includes a cross-check that the proxy's own allow/deny agrees with the
29
+ receipt's independently recomputed verdict (surfaces boundary drift).
30
+ - `nucleus-attest` bin: read a trace export (stdin or file), sign a receipt.
31
+ With `--key <pkcs8.pem>` uses a real key; otherwise an ephemeral in-memory
32
+ key (no custody), printing the public key to stderr.
33
+ - Release workflow: OIDC trusted publishing to npm (no stored token).
34
+
35
+ ## 0.1.0
36
+
37
+ ### Added
38
+ - **Capability-boundary in-bounds attestation** — the headline evidence layer.
39
+ - `verifyReceipt` / `verifyReceiptJson`: offline, zero-trust verification of
40
+ an Ed25519-signed, SHA-256-hash-chained receipt that an agent run stayed
41
+ within a declared capability boundary, keyed on a cryptographic principal
42
+ (SPIFFE id + permission fingerprint), never a mutable display name.
43
+ - `signReceipt`, `recomputeVerdict` (the pure soundness floor), `makeBoundary`.
44
+ - `nucleus-verify` CLI (exit 0 in-bounds / 1 refused / 2 usage) and a
45
+ composite GitHub Action.
46
+ - **Auction receipts** (original use case): `verify`, `recomputeClearing`,
47
+ `verifySignature` over the bundled `nucleus-wasm` core.
package/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # @coproduct_inc/verify
2
2
 
3
+ > **New here?** Read [WHY-VERIFY.md](./WHY-VERIFY.md) ("verify, don't trust" — the
4
+ > one-pager + Microsoft-AGT contrast), or run the 90-second demo:
5
+ > `bash examples/converting-demo.sh` (clean attests · OpenClaw bypass refused ·
6
+ > forged verdict caught · CI gate).
7
+
3
8
  Offline, **zero-trust** verification for Nucleus. Two receipt families share one package:
4
9
 
5
10
  1. **Capability-boundary in-bounds attestation** (headline) — given an agent run's tool-call trace and a *declared* capability boundary, emit and offline-verify an Ed25519-signed, hash-chained receipt that the agent **stayed within bounds**. The boundary is keyed on a **cryptographic principal**, never a mutable display name — which is exactly what rules out OpenClaw-class trust-boundary bypass.
@@ -68,6 +73,30 @@ pnpm build && pnpm demo # examples/openclaw-replay.mjs
68
73
  - **The permission fingerprint binds identity to capability.** `permissionFingerprint` is SHA-256 over the sorted, de-duplicated `allowedTools` — mirroring the X.509 permission-fingerprint extension in `nucleus-tool-proxy`/`nucleus-identity` (`identity_fusion`: "who you are" bound to "what you can do"). Widening the tool set changes the fingerprint and invalidates the boundary.
69
74
  - **The hash-chain pins trace order.** `rootHash` is `hᵢ = SHA256(hᵢ₋₁ ‖ canonical(eventᵢ))`; reorder or insert and the root changes.
70
75
 
76
+ ### Producer-side: from a real nucleus-tool-proxy trace
77
+
78
+ The receipt is emitted from the tool-proxy's actual authorization output, not a hand-authored fixture. A trace export pairs the **declared boundary** (a SPIFFE principal + the task-shield allowlist — `portcullis_core::task_shield::TaskWitness`'s allow-set) with one record per observed call mirroring the `verdict_sink` `tool_call` span fields (`actor`, `operation`, `verdict`, `subject`, `deny_reason`):
79
+
80
+ ```jsonc
81
+ {
82
+ "boundary": { "principal": "spiffe://acme/agent/billing", "allowedTools": ["read_files","glob_search","grep_search"] },
83
+ "records": [
84
+ { "actor": "spiffe://acme/agent/billing", "operation": "read_files", "verdict": "allow", "subject": "invoices/2026-06.pdf" }
85
+ ]
86
+ }
87
+ ```
88
+
89
+ Pipe it through the `nucleus-attest` producer to get a signed receipt, then verify:
90
+
91
+ ```sh
92
+ # emit a REAL trace from portcullis (TaskWitness::permits decides each verdict):
93
+ cargo run -q -p nucleus-policy --example emit_toolproxy_trace -- clean \
94
+ | nucleus-attest \
95
+ | nucleus-verify - # exit 0: in bounds · exit 1: refused
96
+ ```
97
+
98
+ `nucleus-attest` also runs a **cross-check**: the proxy's own `allow`/`deny` per call must agree with the receipt's independently recomputed in-bounds verdict — a mismatch surfaces boundary drift. The producer never asserts in-bounds itself; it only records what happened (`attestToolProxyRun`/`./toolproxy`). The Rust emitter lives at `crates/nucleus-policy/examples/emit_toolproxy_trace.rs`.
99
+
71
100
  ### Honest scope
72
101
 
73
102
  This attests that the **observed trace** stayed within the **declared boundary** — integrity-axis, model-level evidence (the bar-2 IFC noninterference guarantee). It is **not**:
package/WHY-VERIFY.md ADDED
@@ -0,0 +1,81 @@
1
+ # Verify, don't trust.
2
+
3
+ **`@coproduct_inc/verify`** turns an agent run into **verifiable evidence** that it
4
+ stayed inside a declared capability boundary — evidence a third party can check
5
+ **offline, without trusting the agent or the platform that produced it.**
6
+
7
+ ```sh
8
+ npx @coproduct_inc/verify nucleus-verify run-receipt.json
9
+ # exit 0 → in bounds · exit 1 → refused
10
+ ```
11
+
12
+ ## The problem everyone now has
13
+
14
+ 97% of security leaders expect a material AI-agent incident in the next 12 months;
15
+ 6% of budgets address it. The dominant failure mode has a name: the **lethal
16
+ trifecta** — an agent with private data + untrusted content + an exfiltration sink
17
+ gets turned into a *confused deputy*, exercising its authority for an attacker
18
+ (OpenClaw, CVE-2026-25253; Invariant Labs' GitHub-MCP exploit). Regulators are
19
+ arriving too: the EU AI Act's high-risk regime (Aug 2, 2026) demands **productized
20
+ evidence tied to real runs and operator identity** — not a dashboard, an artifact.
21
+
22
+ The runtime's job is to make that trifecta safe by *non-interference*. This
23
+ package's job is to **prove it happened**: an offline-verifiable receipt that the
24
+ agent stayed inside its declared boundary — i.e. was *not* a confused deputy.
25
+
26
+ ## The one thing that's different: we recompute the verdict
27
+
28
+ Most "agent receipts" — including Microsoft's Agent Governance Toolkit — are a
29
+ **signed record of what the platform's own engine decided.** You trust the engine.
30
+
31
+ `@coproduct_inc/verify` is a **second, independent check.** Given the declared
32
+ boundary and the observed trace, it **re-derives** the in-bounds verdict and
33
+ confirms it matches the receipt — then checks an Ed25519 signature and a SHA-256
34
+ hash-chain. A **forged or mistaken** verdict is caught two ways: the recompute
35
+ disagrees, *and* the signature (taken over the honest verdict) breaks.
36
+
37
+ > A platform vendor is structurally incentivized to be *trusted*. A neutral
38
+ > verifier is incentivized to be *checkable*. That's the wedge.
39
+
40
+ | | Microsoft Agent Governance Toolkit | `@coproduct_inc/verify` |
41
+ |---|---|---|
42
+ | Capability manifest / policy | ✅ (denies outside the manifest) | relies on yours (any runtime) |
43
+ | Signed, hash-chained receipts | ✅ Ed25519 decision receipts | ✅ Ed25519 + SHA-256 chain |
44
+ | **Independent verdict recompute** | ❌ (trust the engine's decision) | ✅ **soundness floor — catches a forged claim** |
45
+ | Keyed on cryptographic principal, not display name | partial | ✅ rules out OpenClaw by construction |
46
+ | Verdict logic backed by machine-checked theorems | ❌ | ✅ (IFC noninterference, leanchecker-rechecked) |
47
+ | Footprint | a governance framework | one `npx` / one GitHub Action, **zero runtime deps** |
48
+ | Position | the platform | the **neutral verifier above any platform** (incl. AGT) |
49
+
50
+ We don't compete with the guardrail. We're the evidence layer **above** it — and
51
+ we verify *their* agents too.
52
+
53
+ ## See it (90 seconds)
54
+
55
+ ```sh
56
+ npx @coproduct_inc/verify --help # or: bash examples/converting-demo.sh
57
+ ```
58
+
59
+ 1. **Clean run → attests** (exit 0). Auditor-grade evidence the agent stayed in bounds.
60
+ 2. **OpenClaw bypass** — a call wearing an allowlisted *display name* but a different
61
+ cryptographic principal → **refused** (exit 1). The CVE class, ruled out by construction.
62
+ 3. **Forge the verdict** (claim `inBounds:true`) → **caught** by the independent recompute
63
+ *and* the broken signature. This is the difference between a signed log and verifiable evidence.
64
+ 4. **CI gate** — drop the Action in a PR; an out-of-bounds run **fails the build.** A merge gate, not a dashboard.
65
+
66
+ ## Who it's for
67
+
68
+ - **AppSec** — a 30-second drop-in CI/PR gate, no platform migration, works above your existing guardrails.
69
+ - **GRC / SOC 2 / EU AI Act** — the receipt **is** the exportable audit artifact: offline-verifiable, tied to real runs and operator identity.
70
+
71
+ ## Honest scope
72
+
73
+ It attests that the **observed trace** stayed within the **declared boundary** —
74
+ integrity-axis, model-level evidence. It is **not** a proof of arbitrary agent
75
+ behavior, and it does not vouch that the trace is a *complete* record of what the
76
+ agent did (completeness of capture is the runtime's job). It's the verifiable
77
+ evidence layer above probabilistic guardrails, not a replacement for them.
78
+
79
+ ---
80
+
81
+ Sources for the market claims: [VentureBeat / Arkose 2026 agent-security survey](https://venturebeat.com/security/most-enterprises-cant-stop-stage-three-ai-agent-threats-venturebeat-survey-finds), [EU AI Act Art. 16 / Aug 2026](https://artificialintelligenceact.eu/article/16/), [Microsoft Agent Governance Toolkit](https://opensource.microsoft.com/blog/2026/04/02/introducing-the-agent-governance-toolkit-open-source-runtime-security-for-ai-agents/), [PipeLab — mediator receipts / independent attestation](https://pipelab.org/blog/independent-attestation-mediator-receipts/).
package/dist/index.d.ts CHANGED
@@ -94,3 +94,5 @@ export declare function recomputeClearing(receiptJson: string): Promise<Recomput
94
94
  */
95
95
  export declare function verifySignature(receiptJson: string, jwksJson: string): Promise<unknown>;
96
96
  export * from "./attestation.js";
97
+ export * from "./toolproxy.js";
98
+ export * from "./license.js";
package/dist/index.js CHANGED
@@ -67,3 +67,9 @@ export async function verifySignature(receiptJson, jwksJson) {
67
67
  // Pure Node (`node:crypto`), no WASM — runs in CI, the CLI, and the Action.
68
68
  // ---------------------------------------------------------------------------
69
69
  export * from "./attestation.js";
70
+ // Producer-side instrumentation: turn a nucleus-tool-proxy trace into a signed
71
+ // in-bounds receipt. See `./toolproxy` and the `nucleus-attest` CLI.
72
+ export * from "./toolproxy.js";
73
+ // Offline Ed25519 license keys: entitlement with no user DB / no callback.
74
+ // See `./license` and the `nucleus-license` CLI.
75
+ export * from "./license.js";
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `nucleus-license` — issue + verify offline Ed25519 license keys.
4
+ *
5
+ * nucleus-license keygen [--out-key vendor.pem] [--out-pub vendor.pub]
6
+ * → generate a vendor keypair; prints the base64url public key to PIN.
7
+ *
8
+ * nucleus-license issue --key vendor.pem --sub a@b.com --tier pro \
9
+ * [--features dashboard,export] [--exp 2027-06-01] [--iat 2026-06-04]
10
+ * → prints a license token (deliver this string as the MoR "license key").
11
+ *
12
+ * nucleus-license verify <token> --pubkey <base64url> [--now 2026-06-04]
13
+ * → exit 0 if valid, 1 if not.
14
+ *
15
+ * No server, no DB: the Merchant of Record holds the customer; this issues and
16
+ * checks a signature. Keep the vendor private key secret.
17
+ */
18
+ export {};
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `nucleus-license` — issue + verify offline Ed25519 license keys.
4
+ *
5
+ * nucleus-license keygen [--out-key vendor.pem] [--out-pub vendor.pub]
6
+ * → generate a vendor keypair; prints the base64url public key to PIN.
7
+ *
8
+ * nucleus-license issue --key vendor.pem --sub a@b.com --tier pro \
9
+ * [--features dashboard,export] [--exp 2027-06-01] [--iat 2026-06-04]
10
+ * → prints a license token (deliver this string as the MoR "license key").
11
+ *
12
+ * nucleus-license verify <token> --pubkey <base64url> [--now 2026-06-04]
13
+ * → exit 0 if valid, 1 if not.
14
+ *
15
+ * No server, no DB: the Merchant of Record holds the customer; this issues and
16
+ * checks a signature. Keep the vendor private key secret.
17
+ */
18
+ import { readFileSync, writeFileSync } from "node:fs";
19
+ import { generateLicenseKeypair, exportLicensePublicKey, signLicense, verifyLicense, licensePrivateKeyFromPem, } from "./license.js";
20
+ function opt(flag) {
21
+ const i = process.argv.indexOf(flag);
22
+ return i >= 0 ? process.argv[i + 1] : undefined;
23
+ }
24
+ function keygen() {
25
+ const { publicKey, privateKey } = generateLicenseKeypair();
26
+ const pub = exportLicensePublicKey(publicKey);
27
+ const pem = privateKey.export({ type: "pkcs8", format: "pem" });
28
+ const outKey = opt("--out-key");
29
+ const outPub = opt("--out-pub");
30
+ if (outKey)
31
+ writeFileSync(outKey, pem, { mode: 0o600 });
32
+ if (outPub)
33
+ writeFileSync(outPub, pub + "\n");
34
+ process.stdout.write(`vendor public key (PIN this in the verifier):\n${pub}\n`);
35
+ if (!outKey) {
36
+ process.stdout.write("\nvendor PRIVATE key (keep SECRET — store in your MoR webhook secret):\n");
37
+ process.stdout.write(pem);
38
+ }
39
+ else {
40
+ process.stderr.write(`wrote private key → ${outKey} (chmod 600)\n`);
41
+ }
42
+ return 0;
43
+ }
44
+ function issue() {
45
+ const keyPath = opt("--key");
46
+ const sub = opt("--sub");
47
+ const tier = (opt("--tier") ?? "pro");
48
+ if (!keyPath || !sub) {
49
+ process.stderr.write("error: issue needs --key <vendor.pem> and --sub <id>\n");
50
+ return 2;
51
+ }
52
+ const features = (opt("--features") ?? "").split(",").map((s) => s.trim()).filter(Boolean);
53
+ const claims = {
54
+ sub,
55
+ tier,
56
+ features,
57
+ iat: opt("--iat") ?? new Date().toISOString().slice(0, 10),
58
+ ...(opt("--exp") ? { exp: opt("--exp") } : {}),
59
+ };
60
+ const key = licensePrivateKeyFromPem(readFileSync(keyPath, "utf8"));
61
+ process.stdout.write(signLicense(claims, key) + "\n");
62
+ return 0;
63
+ }
64
+ function verify() {
65
+ const token = process.argv[3];
66
+ const pubkey = opt("--pubkey");
67
+ if (!token || token.startsWith("--") || !pubkey) {
68
+ process.stderr.write("error: verify needs <token> and --pubkey <base64url>\n");
69
+ return 2;
70
+ }
71
+ const r = verifyLicense(token, pubkey, opt("--now"));
72
+ if (r.valid) {
73
+ process.stdout.write(`✓ valid — ${r.license.tier} for ${r.license.sub}` + (r.license.exp ? ` (exp ${r.license.exp})` : "") + `\n`);
74
+ return 0;
75
+ }
76
+ process.stderr.write(`✗ invalid — ${r.reason}\n`);
77
+ return 1;
78
+ }
79
+ function main() {
80
+ const cmd = process.argv[2];
81
+ switch (cmd) {
82
+ case "keygen":
83
+ return keygen();
84
+ case "issue":
85
+ return issue();
86
+ case "verify":
87
+ return verify();
88
+ default:
89
+ process.stdout.write("nucleus-license — offline Ed25519 license keys\n\n" +
90
+ " nucleus-license keygen [--out-key vendor.pem] [--out-pub vendor.pub]\n" +
91
+ " nucleus-license issue --key vendor.pem --sub a@b.com --tier pro [--features x,y] [--exp ISO]\n" +
92
+ " nucleus-license verify <token> --pubkey <base64url> [--now ISO]\n");
93
+ return cmd ? 2 : 0;
94
+ }
95
+ }
96
+ process.exit(main());
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Offline Ed25519 license keys — entitlement without a user database or a
3
+ * callback. The vendor signs a tiny claims payload with a private key; the CLI
4
+ * (or a Pro feature path) verifies it against a PINNED public key. No server,
5
+ * no license API, no phone-home: the Merchant of Record (Polar / Lemon Squeezy)
6
+ * holds the customer record and delivers the signed string as the "license
7
+ * key"; this module is the verifier.
8
+ *
9
+ * Token format (compact, copy-pasteable):
10
+ * nvlic1.<base64url(canonical-json payload)>.<base64url(ed25519 sig)>
11
+ * The signature is over the payload SEGMENT bytes (the base64url string), so
12
+ * verification never re-serializes — it checks the exact bytes that were signed.
13
+ *
14
+ * On-brand: a verification company gating its own Pro tier by a verifiable,
15
+ * offline-checkable signature rather than a trust-the-server license call.
16
+ */
17
+ import { type KeyObject } from "node:crypto";
18
+ export declare const LICENSE_PREFIX: "nvlic1";
19
+ /** The claims a license asserts. Keep it small — it's pasted by hand. */
20
+ export interface LicenseClaims {
21
+ /** Who the license is for (email / customer id from the MoR). */
22
+ sub: string;
23
+ /** Entitlement tier. */
24
+ tier: "pro" | "team" | "enterprise";
25
+ /** Unlocked features (free-form; checked by the Pro path). */
26
+ features: string[];
27
+ /** Issued-at (ISO date). Informational. */
28
+ iat: string;
29
+ /** Optional expiry (ISO date). Absent = perpetual. */
30
+ exp?: string;
31
+ }
32
+ export interface LicenseReport {
33
+ valid: boolean;
34
+ /** The verified claims, when valid. */
35
+ license: LicenseClaims | null;
36
+ /** First failing reason, or null when valid. */
37
+ reason: string | null;
38
+ }
39
+ /** Generate an Ed25519 vendor keypair. Keep the private key secret; ship the public. */
40
+ export declare function generateLicenseKeypair(): {
41
+ publicKey: KeyObject;
42
+ privateKey: KeyObject;
43
+ };
44
+ /** Export a public key to the base64url raw form the verifier pins. */
45
+ export declare function exportLicensePublicKey(publicKey: KeyObject): string;
46
+ /** Sign a license token with the vendor private key. */
47
+ export declare function signLicense(claims: LicenseClaims, privateKey: KeyObject): string;
48
+ /** Load an Ed25519 private key from PEM (PKCS#8). */
49
+ export declare function licensePrivateKeyFromPem(pem: string): KeyObject;
50
+ /**
51
+ * Verify a license token offline against the pinned vendor public key
52
+ * (base64url raw Ed25519). Checks format + signature + expiry. Never throws.
53
+ *
54
+ * @param now optional ISO timestamp for expiry checks (defaults to absent =
55
+ * skip expiry; pass an explicit time to enforce it deterministically).
56
+ */
57
+ export declare function verifyLicense(token: string, vendorPublicKeyB64url: string, now?: string): LicenseReport;
58
+ /** Convenience: does a verified license grant a given feature (or tier-implied)? */
59
+ export declare function licenseGrants(report: LicenseReport, feature: string): boolean;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Offline Ed25519 license keys — entitlement without a user database or a
3
+ * callback. The vendor signs a tiny claims payload with a private key; the CLI
4
+ * (or a Pro feature path) verifies it against a PINNED public key. No server,
5
+ * no license API, no phone-home: the Merchant of Record (Polar / Lemon Squeezy)
6
+ * holds the customer record and delivers the signed string as the "license
7
+ * key"; this module is the verifier.
8
+ *
9
+ * Token format (compact, copy-pasteable):
10
+ * nvlic1.<base64url(canonical-json payload)>.<base64url(ed25519 sig)>
11
+ * The signature is over the payload SEGMENT bytes (the base64url string), so
12
+ * verification never re-serializes — it checks the exact bytes that were signed.
13
+ *
14
+ * On-brand: a verification company gating its own Pro tier by a verifiable,
15
+ * offline-checkable signature rather than a trust-the-server license call.
16
+ */
17
+ import { createPublicKey, createPrivateKey, sign as edSign, verify as edVerify, generateKeyPairSync } from "node:crypto";
18
+ import { canonicalize } from "./attestation.js";
19
+ export const LICENSE_PREFIX = "nvlic1";
20
+ function b64url(buf) {
21
+ return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
22
+ }
23
+ function fromB64url(s) {
24
+ return Buffer.from(s.replace(/-/g, "+").replace(/_/g, "/"), "base64");
25
+ }
26
+ function importPublicKey(b64urlX) {
27
+ return createPublicKey({ key: { kty: "OKP", crv: "Ed25519", x: b64urlX }, format: "jwk" });
28
+ }
29
+ // ---------------------------------------------------------------------------
30
+ // Issuer side (run once per sale, or from a MoR webhook).
31
+ // ---------------------------------------------------------------------------
32
+ /** Generate an Ed25519 vendor keypair. Keep the private key secret; ship the public. */
33
+ export function generateLicenseKeypair() {
34
+ return generateKeyPairSync("ed25519");
35
+ }
36
+ /** Export a public key to the base64url raw form the verifier pins. */
37
+ export function exportLicensePublicKey(publicKey) {
38
+ const jwk = publicKey.export({ format: "jwk" });
39
+ if (!jwk.x)
40
+ throw new Error("not an Ed25519 public key");
41
+ return jwk.x;
42
+ }
43
+ /** Sign a license token with the vendor private key. */
44
+ export function signLicense(claims, privateKey) {
45
+ const payloadB64 = b64url(Buffer.from(canonicalize(claims), "utf8"));
46
+ const sig = edSign(null, Buffer.from(payloadB64, "utf8"), privateKey);
47
+ return `${LICENSE_PREFIX}.${payloadB64}.${b64url(sig)}`;
48
+ }
49
+ /** Load an Ed25519 private key from PEM (PKCS#8). */
50
+ export function licensePrivateKeyFromPem(pem) {
51
+ return createPrivateKey(pem);
52
+ }
53
+ // ---------------------------------------------------------------------------
54
+ // Verifier side (ships in the CLI / Pro path; pins the vendor public key).
55
+ // ---------------------------------------------------------------------------
56
+ /**
57
+ * Verify a license token offline against the pinned vendor public key
58
+ * (base64url raw Ed25519). Checks format + signature + expiry. Never throws.
59
+ *
60
+ * @param now optional ISO timestamp for expiry checks (defaults to absent =
61
+ * skip expiry; pass an explicit time to enforce it deterministically).
62
+ */
63
+ export function verifyLicense(token, vendorPublicKeyB64url, now) {
64
+ const parts = token.trim().split(".");
65
+ if (parts.length !== 3 || parts[0] !== LICENSE_PREFIX) {
66
+ return { valid: false, license: null, reason: "malformed license token" };
67
+ }
68
+ const [, payloadB64, sigB64] = parts;
69
+ let sigOk = false;
70
+ try {
71
+ const pub = importPublicKey(vendorPublicKeyB64url);
72
+ sigOk = edVerify(null, Buffer.from(payloadB64, "utf8"), pub, fromB64url(sigB64));
73
+ }
74
+ catch {
75
+ sigOk = false;
76
+ }
77
+ if (!sigOk)
78
+ return { valid: false, license: null, reason: "signature did not verify under the pinned key" };
79
+ let claims;
80
+ try {
81
+ claims = JSON.parse(fromB64url(payloadB64).toString("utf8"));
82
+ }
83
+ catch (e) {
84
+ return { valid: false, license: null, reason: `license payload not valid JSON: ${e.message}` };
85
+ }
86
+ if (claims.exp && now && now > claims.exp) {
87
+ return { valid: false, license: claims, reason: `license expired on ${claims.exp}` };
88
+ }
89
+ return { valid: true, license: claims, reason: null };
90
+ }
91
+ /** Convenience: does a verified license grant a given feature (or tier-implied)? */
92
+ export function licenseGrants(report, feature) {
93
+ return report.valid && !!report.license && report.license.features.includes(feature);
94
+ }
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `nucleus-attest` — producer-side CLI. Reads a nucleus-tool-proxy trace
4
+ * export (from the `emit_toolproxy_trace` example, or any equivalent
5
+ * instrumentation), signs an in-bounds attestation receipt, and writes it to
6
+ * stdout. The cross-check + signer public key go to stderr.
7
+ *
8
+ * Usage:
9
+ * nucleus-attest [export.json] [--key <pkcs8.pem>] [--signed-at <iso>]
10
+ * emit_toolproxy_trace ... | nucleus-attest > receipt.json
11
+ *
12
+ * With no --key, an EPHEMERAL Ed25519 keypair is generated in memory (no key
13
+ * custody) — fine for demos/CI dry-runs; the public key is printed to stderr
14
+ * so a verifier can pin it. Exit 0 on a produced receipt, 2 on error.
15
+ */
16
+ export {};
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `nucleus-attest` — producer-side CLI. Reads a nucleus-tool-proxy trace
4
+ * export (from the `emit_toolproxy_trace` example, or any equivalent
5
+ * instrumentation), signs an in-bounds attestation receipt, and writes it to
6
+ * stdout. The cross-check + signer public key go to stderr.
7
+ *
8
+ * Usage:
9
+ * nucleus-attest [export.json] [--key <pkcs8.pem>] [--signed-at <iso>]
10
+ * emit_toolproxy_trace ... | nucleus-attest > receipt.json
11
+ *
12
+ * With no --key, an EPHEMERAL Ed25519 keypair is generated in memory (no key
13
+ * custody) — fine for demos/CI dry-runs; the public key is printed to stderr
14
+ * so a verifier can pin it. Exit 0 on a produced receipt, 2 on error.
15
+ */
16
+ import { readFileSync } from "node:fs";
17
+ import { generateKeypair, privateKeyFromPem, exportPublicKey } from "./attestation.js";
18
+ import { createPublicKey } from "node:crypto";
19
+ import { parseExport, attestToolProxyRun } from "./toolproxy.js";
20
+ function arg(flag) {
21
+ const i = process.argv.indexOf(flag);
22
+ return i >= 0 ? process.argv[i + 1] : undefined;
23
+ }
24
+ function main() {
25
+ if (process.argv.includes("-h") || process.argv.includes("--help")) {
26
+ process.stdout.write("nucleus-attest — sign an in-bounds receipt from a tool-proxy trace\n\n" +
27
+ "Usage: nucleus-attest [export.json] [--key <pkcs8.pem>] [--signed-at <iso>]\n");
28
+ return 0;
29
+ }
30
+ // Positional input path = first non-flag arg after argv[1].
31
+ const positional = process.argv.slice(2).find((a) => !a.startsWith("--") && a !== arg("--key") && a !== arg("--signed-at"));
32
+ let exportJson;
33
+ try {
34
+ exportJson = positional ? readFileSync(positional, "utf8") : readFileSync(0, "utf8");
35
+ }
36
+ catch (e) {
37
+ process.stderr.write(`error: cannot read export: ${e.message}\n`);
38
+ return 2;
39
+ }
40
+ let exp;
41
+ try {
42
+ exp = parseExport(exportJson);
43
+ }
44
+ catch (e) {
45
+ process.stderr.write(`error: ${e.message}\n`);
46
+ return 2;
47
+ }
48
+ // Key: load from PEM, or generate ephemeral.
49
+ let privateKey;
50
+ let ephemeral = false;
51
+ const keyPath = arg("--key");
52
+ try {
53
+ if (keyPath) {
54
+ privateKey = privateKeyFromPem(readFileSync(keyPath, "utf8"));
55
+ }
56
+ else {
57
+ privateKey = generateKeypair().privateKey;
58
+ ephemeral = true;
59
+ }
60
+ }
61
+ catch (e) {
62
+ process.stderr.write(`error: cannot load key: ${e.message}\n`);
63
+ return 2;
64
+ }
65
+ const signedAt = arg("--signed-at");
66
+ const { receipt, crossCheck } = attestToolProxyRun(exp, privateKey, signedAt ? { signedAt } : {});
67
+ // Receipt → stdout (the artifact).
68
+ process.stdout.write(JSON.stringify(receipt, null, 2) + "\n");
69
+ // Diagnostics → stderr.
70
+ const pub = exportPublicKey(createPublicKey(privateKey));
71
+ process.stderr.write(`signer publicKey: ${pub}${ephemeral ? " (ephemeral)" : ""}\n` +
72
+ `recomputed inBounds: ${receipt.verdict.inBounds}\n`);
73
+ if (crossCheck.length > 0) {
74
+ process.stderr.write(`cross-check issues (${crossCheck.length}):\n`);
75
+ for (const c of crossCheck) {
76
+ process.stderr.write(` #${c.seq} ${c.operation}: proxy=${c.proxyVerdict} recomputed-inBounds=${c.recomputedInBounds} — ${c.note}\n`);
77
+ }
78
+ }
79
+ else {
80
+ process.stderr.write("cross-check: proxy decisions agree with recomputed verdict\n");
81
+ }
82
+ return 0;
83
+ }
84
+ process.exit(main());
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Producer-side instrumentation: turn a **nucleus-tool-proxy trace** into a
3
+ * signed capability-boundary in-bounds attestation receipt.
4
+ *
5
+ * The trace is the real output of the tool-proxy's authorization layer: a
6
+ * declared boundary (a SPIFFE principal + the task-shield allowlist, i.e.
7
+ * `TaskWitness`'s allow-set) plus one record per observed tool call mirroring
8
+ * the `verdict_sink` "tool_call" span fields (`actor`, `operation`,
9
+ * `verdict`, `subject`, `deny_reason`). See
10
+ * `crates/nucleus-policy/examples/emit_toolproxy_trace.rs` for the emitter.
11
+ *
12
+ * This module maps that export into the {@link Receipt} schema and signs it.
13
+ * The receipt's verdict is still recomputed independently by the verifier —
14
+ * the producer never gets to assert in-bounds; it only records what happened.
15
+ */
16
+ import { type KeyObject } from "node:crypto";
17
+ import { type CapabilityBoundary, type TraceEvent, type Receipt } from "./attestation.js";
18
+ /** A single observed tool call, mirroring the tool-proxy `verdict_sink` span. */
19
+ export interface ToolProxyVerdictRecord {
20
+ /** SPIFFE id of the actor that made the call (`nucleus.actor`). */
21
+ actor: string;
22
+ /** Operation/tool invoked (`nucleus.operation`), canonical serde name. */
23
+ operation: string;
24
+ /** The proxy's own decision (`nucleus.verdict`). */
25
+ verdict: "allow" | "deny" | "error";
26
+ /** The call's target/argument (`nucleus.subject`), if present. */
27
+ subject?: string;
28
+ /** Proxy deny reason (`nucleus.deny_reason`), if any. */
29
+ deny_reason?: string;
30
+ /** Human-readable label, if the runtime recorded one (mutable, untrusted). */
31
+ displayName?: string;
32
+ }
33
+ /** The full trace export the emitter produces. */
34
+ export interface ToolProxyExport {
35
+ boundary: {
36
+ principal: string;
37
+ allowedTools: string[];
38
+ };
39
+ records: ToolProxyVerdictRecord[];
40
+ /** Optional task-shield hash (provenance; not part of the receipt). */
41
+ taskHashHex?: string;
42
+ }
43
+ /** A disagreement between the proxy's decision and the recomputed verdict. */
44
+ export interface CrossCheckIssue {
45
+ seq: number;
46
+ operation: string;
47
+ proxyVerdict: string;
48
+ recomputedInBounds: boolean;
49
+ note: string;
50
+ }
51
+ export interface AttestResult {
52
+ receipt: Receipt;
53
+ /**
54
+ * Sanity cross-check: the proxy's per-call decision should agree with the
55
+ * receipt's recomputed in-bounds finding (a proxy `deny` of an out-of-policy
56
+ * op should land out-of-bounds, an `allow` of an in-policy op in-bounds). A
57
+ * non-empty list means the trace and the declared boundary disagree — worth
58
+ * surfacing, though the receipt's recomputed verdict remains authoritative.
59
+ */
60
+ crossCheck: CrossCheckIssue[];
61
+ }
62
+ /** Build the declared {@link CapabilityBoundary} from a trace export. */
63
+ export declare function boundaryFromExport(exp: ToolProxyExport): CapabilityBoundary;
64
+ /** Map the proxy's verdict records into receipt {@link TraceEvent}s. */
65
+ export declare function traceEventsFromRecords(records: ToolProxyVerdictRecord[]): TraceEvent[];
66
+ /**
67
+ * Attest a tool-proxy run: build the boundary + events from the export and
68
+ * sign the receipt. Also runs a cross-check that the proxy's own decisions
69
+ * agree with the recomputed in-bounds verdict.
70
+ */
71
+ export declare function attestToolProxyRun(exp: ToolProxyExport, privateKey: KeyObject, opts?: {
72
+ signedAt?: string;
73
+ }): AttestResult;
74
+ /** Parse a trace export from JSON text. Accepts the emitter's pretty output. */
75
+ export declare function parseExport(json: string): ToolProxyExport;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Producer-side instrumentation: turn a **nucleus-tool-proxy trace** into a
3
+ * signed capability-boundary in-bounds attestation receipt.
4
+ *
5
+ * The trace is the real output of the tool-proxy's authorization layer: a
6
+ * declared boundary (a SPIFFE principal + the task-shield allowlist, i.e.
7
+ * `TaskWitness`'s allow-set) plus one record per observed tool call mirroring
8
+ * the `verdict_sink` "tool_call" span fields (`actor`, `operation`,
9
+ * `verdict`, `subject`, `deny_reason`). See
10
+ * `crates/nucleus-policy/examples/emit_toolproxy_trace.rs` for the emitter.
11
+ *
12
+ * This module maps that export into the {@link Receipt} schema and signs it.
13
+ * The receipt's verdict is still recomputed independently by the verifier —
14
+ * the producer never gets to assert in-bounds; it only records what happened.
15
+ */
16
+ import { createHash } from "node:crypto";
17
+ import { makeBoundary, signReceipt, recomputeVerdict, } from "./attestation.js";
18
+ function sha256Hex(input) {
19
+ return createHash("sha256").update(input).digest("hex");
20
+ }
21
+ /** Build the declared {@link CapabilityBoundary} from a trace export. */
22
+ export function boundaryFromExport(exp) {
23
+ return makeBoundary(exp.boundary.principal, exp.boundary.allowedTools);
24
+ }
25
+ /** Map the proxy's verdict records into receipt {@link TraceEvent}s. */
26
+ export function traceEventsFromRecords(records) {
27
+ return records.map((r, i) => {
28
+ const ev = { seq: i, tool: r.operation, principal: r.actor };
29
+ if (r.displayName !== undefined)
30
+ ev.displayName = r.displayName;
31
+ // Bind the event to the call's target via a digest (subject is the
32
+ // tool-proxy's `nucleus.subject`); opaque to the in-bounds verdict.
33
+ if (r.subject !== undefined && r.subject !== "")
34
+ ev.argsDigest = sha256Hex(r.subject);
35
+ return ev;
36
+ });
37
+ }
38
+ /**
39
+ * Attest a tool-proxy run: build the boundary + events from the export and
40
+ * sign the receipt. Also runs a cross-check that the proxy's own decisions
41
+ * agree with the recomputed in-bounds verdict.
42
+ */
43
+ export function attestToolProxyRun(exp, privateKey, opts = {}) {
44
+ const boundary = boundaryFromExport(exp);
45
+ const events = traceEventsFromRecords(exp.records);
46
+ const receipt = signReceipt(boundary, events, privateKey, opts);
47
+ // Cross-check the proxy's decisions against an independent recompute.
48
+ const recomputed = recomputeVerdict(boundary, events);
49
+ const crossCheck = [];
50
+ recomputed.events.forEach((finding, i) => {
51
+ const proxyVerdict = exp.records[i]?.verdict ?? "error";
52
+ const proxyAllowed = proxyVerdict === "allow";
53
+ if (proxyAllowed !== finding.inBounds) {
54
+ crossCheck.push({
55
+ seq: finding.seq,
56
+ operation: finding.tool,
57
+ proxyVerdict,
58
+ recomputedInBounds: finding.inBounds,
59
+ note: proxyAllowed
60
+ ? "proxy allowed a call the declared boundary does not permit (boundary drift?)"
61
+ : "proxy denied a call the declared boundary permits (extra restriction outside the boundary)",
62
+ });
63
+ }
64
+ });
65
+ return { receipt, crossCheck };
66
+ }
67
+ /** Parse a trace export from JSON text. Accepts the emitter's pretty output. */
68
+ export function parseExport(json) {
69
+ const obj = JSON.parse(json);
70
+ if (!obj || typeof obj !== "object" || !obj.boundary || !Array.isArray(obj.records)) {
71
+ throw new Error("not a tool-proxy trace export (need { boundary, records })");
72
+ }
73
+ return obj;
74
+ }
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env bash
2
+ # converting-demo.sh — the 90-second "verify, don't trust" demo.
3
+ #
4
+ # Four beats: clean attests · OpenClaw bypass refused · FORGED verdict caught ·
5
+ # CI gate. The money beat is #3 — an independent recompute catches a forged
6
+ # claim that a *signed log* would wave through. That is the line between
7
+ # "we have receipts too" and verifiable evidence.
8
+ #
9
+ # Runs against the PUBLISHED package by default (the real install story):
10
+ # bash examples/converting-demo.sh
11
+ # Use the local build instead (no network):
12
+ # pnpm build && LOCAL=1 bash examples/converting-demo.sh
13
+ # Pacing for screen-recording (pause between beats):
14
+ # PAUSE=1 bash examples/converting-demo.sh
15
+ #
16
+ # Requires: node, and (default mode) npx with network. Trace fixtures are the
17
+ # real output of crates/nucleus-policy/examples/emit_toolproxy_trace.rs.
18
+
19
+ set -u
20
+ HERE="$(cd "$(dirname "$0")" && pwd)"
21
+ CLEAN_TRACE="$HERE/sample-toolproxy-trace.clean.json"
22
+ # Beat 2 uses the OpenClaw impersonation trace (principal mismatch under an
23
+ # allowlisted display name). sample-toolproxy-trace.bypass.json (a tool-not-
24
+ # allowed git_push) is the other refusal mode, gated by verify-gate.yml.
25
+ BYPASS_TRACE="$HERE/sample-toolproxy-trace.openclaw.json"
26
+ PRINCIPAL="spiffe://acme.example/agent/billing-assistant"
27
+ TMP="$(mktemp -d)"
28
+ trap 'rm -rf "$TMP"' EXIT
29
+
30
+ if [ "${LOCAL:-0}" = "1" ]; then
31
+ DIST="$HERE/../dist"
32
+ ATTEST=(node "$DIST/producer-cli.js")
33
+ VERIFY=(node "$DIST/cli.js")
34
+ SRC="local build ($DIST)"
35
+ else
36
+ PKG="@coproduct_inc/verify@${VERIFY_VERSION:-latest}"
37
+ ATTEST=(npx --yes -p "$PKG" nucleus-attest)
38
+ VERIFY=(npx --yes -p "$PKG" nucleus-verify)
39
+ SRC="published npm package $PKG"
40
+ fi
41
+
42
+ bold() { printf "\033[1m%s\033[0m\n" "$*"; }
43
+ dim() { printf "\033[2m%s\033[0m\n" "$*"; }
44
+ ok() { printf "\033[32m%s\033[0m\n" "$*"; }
45
+ bad() { printf "\033[31m%s\033[0m\n" "$*"; }
46
+ beat() { echo; bold "── $* ──"; [ "${PAUSE:-0}" = "1" ] && read -r -p " (enter)…" _ || true; }
47
+
48
+ echo
49
+ bold "@coproduct_inc/verify — verify, don't trust"
50
+ dim "source: $SRC"
51
+ dim "An agent run's tool-call trace + a declared capability boundary →"
52
+ dim "a signed, OFFLINE-verifiable in-bounds attestation. We don't trust the"
53
+ dim "issuer's verdict — we recompute it."
54
+
55
+ # ── Beat 1: a clean run attests ──────────────────────────────────────────────
56
+ beat "1/4 Clean run → ATTESTS (exit 0)"
57
+ dim "nucleus-attest < clean-trace → nucleus-verify"
58
+ "${ATTEST[@]}" "$CLEAN_TRACE" > "$TMP/clean.json" 2> "$TMP/clean.err"
59
+ if "${VERIFY[@]}" "$TMP/clean.json" --expect-principal "$PRINCIPAL"; then
60
+ ok " → exit 0. Auditor-grade evidence the agent stayed inside its boundary."
61
+ else
62
+ bad " unexpected: clean run did not verify"; exit 1
63
+ fi
64
+
65
+ # ── Beat 2: the OpenClaw bypass is refused ───────────────────────────────────
66
+ beat "2/4 OpenClaw bypass (display-name match, principal mismatch) → REFUSED (exit 1)"
67
+ dim "An injected call wears the allowlisted display name but a different SPIFFE id."
68
+ "${ATTEST[@]}" "$BYPASS_TRACE" > "$TMP/bypass.json" 2> "$TMP/bypass.err"
69
+ if "${VERIFY[@]}" "$TMP/bypass.json"; then
70
+ bad " unexpected: bypass verified (should refuse)"; exit 1
71
+ else
72
+ ok " → exit 1. CVE-2026-25253's failure class, ruled out by construction"
73
+ ok " (the boundary is keyed on the cryptographic principal, never the name)."
74
+ fi
75
+
76
+ # ── Beat 3: the money beat — a forged verdict is caught ───────────────────────
77
+ beat "3/4 FORGE the verdict (claim inBounds:true) → CAUGHT"
78
+ dim "A signed LOG would trust this. We recompute the verdict independently."
79
+ node -e '
80
+ const fs = require("fs");
81
+ const r = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
82
+ r.verdict.inBounds = true;
83
+ r.verdict.events = r.verdict.events.map(e => ({...e, inBounds:true, code:"ok", detail:"in bounds"}));
84
+ fs.writeFileSync(process.argv[2], JSON.stringify(r));
85
+ ' "$TMP/bypass.json" "$TMP/forged.json"
86
+ "${VERIFY[@]}" "$TMP/forged.json" --json > "$TMP/forged.report.json" 2>/dev/null || true
87
+ SIG=$(node -e 'console.log(require(process.argv[1]).signatureOk)' "$TMP/forged.report.json")
88
+ CONS=$(node -e 'console.log(require(process.argv[1]).verdictConsistentOk)' "$TMP/forged.report.json")
89
+ OKV=$(node -e 'console.log(require(process.argv[1]).ok)' "$TMP/forged.report.json")
90
+ echo " signatureOk=$SIG verdictConsistentOk=$CONS ok=$OKV"
91
+ if [ "$OKV" = "false" ] && [ "$CONS" = "false" ]; then
92
+ ok " → REJECTED two independent ways: the recompute disagrees with the claim,"
93
+ ok " AND the signature (taken over the honest verdict) no longer verifies."
94
+ ok " THIS is verifiable evidence, not a signed log you have to trust."
95
+ else
96
+ bad " unexpected: forged receipt was not caught"; exit 1
97
+ fi
98
+
99
+ # ── Beat 4: it's a CI merge gate, not a dashboard ────────────────────────────
100
+ beat "4/4 It's a merge gate, not a dashboard"
101
+ dim "Drop the Action in a PR; an out-of-bounds run fails the build:"
102
+ cat <<'YAML'
103
+ - uses: coproduct-private/spiffy/packages/nucleus-verify@main
104
+ with:
105
+ receipt: ./agent-run-receipt.json
106
+ expect-principal: spiffe://acme/agent/billing-assistant
107
+ YAML
108
+ ok " clean → check passes · out-of-bounds → check fails. Exit codes are the gate."
109
+
110
+ echo
111
+ bold "Summary"
112
+ echo " • Works above ANY runtime/guardrail (incl. Microsoft AGT) — neutral verifier."
113
+ echo " • Offline, zero runtime deps (node:crypto). 30-second npx drop-in."
114
+ echo " • The verdict logic is backed by machine-checked theorems (IFC noninterference)."
115
+ echo " • Honest scope: attests the DECLARED boundary over the OBSERVED trace —"
116
+ echo " completeness of trace capture is the runtime's job."
117
+ ok "DEMO OK ✓"
@@ -0,0 +1,34 @@
1
+ {
2
+ "boundary": {
3
+ "allowedTools": [
4
+ "read_files",
5
+ "glob_search",
6
+ "grep_search"
7
+ ],
8
+ "principal": "spiffe://acme.example/agent/billing-assistant"
9
+ },
10
+ "records": [
11
+ {
12
+ "actor": "spiffe://acme.example/agent/billing-assistant",
13
+ "deny_reason": "",
14
+ "operation": "glob_search",
15
+ "subject": "invoices/*.pdf",
16
+ "verdict": "allow"
17
+ },
18
+ {
19
+ "actor": "spiffe://acme.example/agent/billing-assistant",
20
+ "deny_reason": "",
21
+ "operation": "read_files",
22
+ "subject": "invoices/2026-06.pdf",
23
+ "verdict": "allow"
24
+ },
25
+ {
26
+ "actor": "spiffe://acme.example/agent/billing-assistant",
27
+ "deny_reason": "operation outside declared task allowlist",
28
+ "operation": "git_push",
29
+ "subject": "refs/heads/main",
30
+ "verdict": "deny"
31
+ }
32
+ ],
33
+ "taskHashHex": "8aa9199f47bc66272e3a40e87d8ef6daeec2e905e5a723252e615c45c4376888"
34
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "boundary": {
3
+ "allowedTools": [
4
+ "read_files",
5
+ "glob_search",
6
+ "grep_search"
7
+ ],
8
+ "principal": "spiffe://acme.example/agent/billing-assistant"
9
+ },
10
+ "records": [
11
+ {
12
+ "actor": "spiffe://acme.example/agent/billing-assistant",
13
+ "deny_reason": "",
14
+ "operation": "glob_search",
15
+ "subject": "invoices/*.pdf",
16
+ "verdict": "allow"
17
+ },
18
+ {
19
+ "actor": "spiffe://acme.example/agent/billing-assistant",
20
+ "deny_reason": "",
21
+ "operation": "read_files",
22
+ "subject": "invoices/2026-06.pdf",
23
+ "verdict": "allow"
24
+ },
25
+ {
26
+ "actor": "spiffe://acme.example/agent/billing-assistant",
27
+ "deny_reason": "",
28
+ "operation": "grep_search",
29
+ "subject": "TOTAL",
30
+ "verdict": "allow"
31
+ },
32
+ {
33
+ "actor": "spiffe://acme.example/agent/billing-assistant",
34
+ "deny_reason": "",
35
+ "operation": "read_files",
36
+ "subject": "invoices/2026-05.pdf",
37
+ "verdict": "allow"
38
+ }
39
+ ],
40
+ "taskHashHex": "8aa9199f47bc66272e3a40e87d8ef6daeec2e905e5a723252e615c45c4376888"
41
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "boundary": {
3
+ "principal": "spiffe://acme.example/agent/billing-assistant",
4
+ "allowedTools": ["read_files", "glob_search", "grep_search"]
5
+ },
6
+ "_note": "OpenClaw-class impersonation trace (CVE-2026-25253 shape): event #1 wears the allowlisted displayName 'Billing Assistant' but carries an ATTACKER SPIFFE id. Real export shape; the boundary is keyed on the cryptographic principal, so the receipt refuses to attest it.",
7
+ "records": [
8
+ {
9
+ "actor": "spiffe://acme.example/agent/billing-assistant",
10
+ "operation": "glob_search",
11
+ "verdict": "allow",
12
+ "subject": "invoices/*.pdf",
13
+ "deny_reason": ""
14
+ },
15
+ {
16
+ "actor": "spiffe://acme.example/agent/unknown-7f3a",
17
+ "operation": "read_files",
18
+ "verdict": "deny",
19
+ "subject": "invoices/2026-06.pdf",
20
+ "displayName": "Billing Assistant",
21
+ "deny_reason": "presented identity not bound to the declared principal"
22
+ }
23
+ ]
24
+ }
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@coproduct_inc/verify",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Offline, zero-trust verification of agent capability-boundary in-bounds attestation receipts: emit and verify an Ed25519-signed, hash-chained receipt that an agent run stayed within a declared boundary, keyed on a cryptographic principal (not a mutable display name) — the evidence layer above probabilistic guardrails. Also verifies Nucleus auction receipts by re-running the clearing locally. Zero runtime deps for the attestation core (node:crypto).",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
9
9
  "bin": {
10
- "nucleus-verify": "dist/cli.js"
10
+ "nucleus-verify": "dist/cli.js",
11
+ "nucleus-attest": "dist/producer-cli.js",
12
+ "nucleus-license": "dist/license-cli.js"
11
13
  },
12
14
  "exports": {
13
15
  ".": {
@@ -17,6 +19,14 @@
17
19
  "./attestation": {
18
20
  "types": "./dist/attestation.d.ts",
19
21
  "import": "./dist/attestation.js"
22
+ },
23
+ "./toolproxy": {
24
+ "types": "./dist/toolproxy.d.ts",
25
+ "import": "./dist/toolproxy.js"
26
+ },
27
+ "./license": {
28
+ "types": "./dist/license.d.ts",
29
+ "import": "./dist/license.js"
20
30
  }
21
31
  },
22
32
  "files": [
@@ -27,7 +37,13 @@
27
37
  "wasm/nodejs/nucleus_wasm_bg.wasm",
28
38
  "wasm/nodejs/nucleus_wasm_bg.wasm.d.ts",
29
39
  "examples/openclaw-replay.mjs",
30
- "README.md"
40
+ "examples/converting-demo.sh",
41
+ "examples/sample-toolproxy-trace.clean.json",
42
+ "examples/sample-toolproxy-trace.bypass.json",
43
+ "examples/sample-toolproxy-trace.openclaw.json",
44
+ "README.md",
45
+ "WHY-VERIFY.md",
46
+ "CHANGELOG.md"
31
47
  ],
32
48
  "scripts": {
33
49
  "build:wasm": "wasm-pack build ../../crates/nucleus-wasm --target nodejs --out-dir ../../packages/nucleus-verify/wasm/nodejs && rm -f wasm/nodejs/.gitignore wasm/nodejs/package.json",