/** * `@coproduct_inc/verify` — capability-boundary **in-bounds attestation**. * * The evidence layer ABOVE probabilistic guardrails: given an agent run's * observed tool-call trace and a DECLARED capability boundary, produce and * offline-verify an Ed25519-signed, SHA-256-hash-chained receipt that the * agent stayed within the boundary. * * The boundary is keyed on a **cryptographic principal** (a SPIFFE ID) bound * to a **SHA-256 permission fingerprint** — exactly the binding * `nucleus-tool-proxy::identity_fusion` makes between "who you are" (SPIFFE * ID) and "what you can do" (the permission fingerprint embedded in the * X.509 cert). Trust is NEVER keyed on a mutable display name. That single * discipline is what rules out the OpenClaw-class trust-boundary bypass * (CVE-2026-25253): a call whose human-readable `displayName` matches an * allowlisted agent but whose cryptographic `principal` differs is * out-of-bounds by construction, and the receipt refuses to attest it. * * ## Honest scope * * This attests that the OBSERVED trace stayed within the DECLARED boundary — * it is integrity-axis, model-level evidence (the bar-2 IFC noninterference * guarantee), NOT an end-to-end proof of arbitrary agent behaviour, and it * does not vouch that the trace is a complete record of what the agent did. * It is the verifiable evidence layer; completeness of capture is the * producer's (instrumented runtime's) responsibility. * * Zero runtime dependencies: Ed25519 + SHA-256 come from `node:crypto`. */ import { type KeyObject } from "node:crypto"; /** Wire identifiers — bumped on any breaking schema change. */ export declare const RECEIPT_KIND: "nucleus.capability-boundary.attestation"; export declare const RECEIPT_VERSION: 1; /** * A declared capability boundary: WHICH tools a cryptographic principal is * permitted to invoke. * * `permissionFingerprint` is the SHA-256 (hex) over the canonical, sorted, * de-duplicated `allowedTools` — mirroring the X.509 permission-fingerprint * extension in `nucleus-identity::attestation`. It binds the boundary's * identity to its exact permission set: change the tools, change the * fingerprint. */ export interface CapabilityBoundary { /** Cryptographic principal identity (SPIFFE ID convention). NOT a name. */ principal: string; /** Tools/capabilities this principal is permitted to invoke. */ allowedTools: string[]; /** SHA-256 hex over the canonical sorted unique `allowedTools`. */ permissionFingerprint: string; } /** One observed tool-call event from an agent run. */ export interface TraceEvent { /** Monotonic position in the run (0-based). */ seq: number; /** Tool / capability actually invoked. */ tool: string; /** Cryptographic identity PRESENTED for this call (SPIFFE ID). */ principal: string; /** * Human-readable label the call presented (mutable). This is the OpenClaw * attack surface — a naive gate keys on this; we record it but NEVER trust * it for the in-bounds decision. */ displayName?: string; /** Optional SHA-256 hex of the call arguments (opaque to the verdict). */ argsDigest?: string; } /** Why a single event is in / out of bounds. */ export type FindingCode = "ok" | "tool_not_allowed" | "principal_mismatch" | "fingerprint_mismatch"; export interface EventFinding { seq: number; tool: string; inBounds: boolean; code: FindingCode; detail: string; } /** The deterministic verdict recomputed from a boundary + events. */ export interface Verdict { /** AND over `boundaryFingerprintOk` and every event finding. */ inBounds: boolean; /** The declared `permissionFingerprint` matches the recomputed one. */ boundaryFingerprintOk: boolean; events: EventFinding[]; } /** A signed in-bounds attestation receipt (the wire form). */ export interface Receipt { version: typeof RECEIPT_VERSION; kind: typeof RECEIPT_KIND; boundary: CapabilityBoundary; events: TraceEvent[]; /** The attestor's claimed verdict (re-derived and re-checked on verify). */ verdict: Verdict; /** SHA-256 hex hash-chain over the canonical events. */ rootHash: string; /** Ed25519 public key (base64url raw 32-byte, JWK `x` form). */ publicKey: string; /** Ed25519 signature (base64) over the canonical signing body. */ signature: string; /** Informational only — NOT trusted by the verifier. */ signedAt?: string; } /** Deterministic JSON: object keys sorted, no incidental whitespace. */ export declare function canonicalize(value: unknown): string; /** SHA-256 hex over the canonical sorted, de-duplicated tool set. */ export declare function permissionFingerprint(allowedTools: string[]): string; /** * SHA-256 hash-chain over the events, in order: * h₀ = SHA256(genesis); hᵢ = SHA256(hᵢ₋₁ ‖ canonical(eventᵢ)). * Order-sensitive: reordering or inserting an event changes the root. */ export declare function rootHash(events: TraceEvent[]): string; /** * Recompute the in-bounds verdict. Pure and deterministic — no crypto, no * I/O. This is the soundness floor: a forged `inBounds: true` claim cannot * survive a re-run here. * * An event is in-bounds iff: * 1. its `principal` equals the boundary's `principal` (cryptographic * identity match — the OpenClaw guard), AND * 2. its `tool` is in the boundary's `allowedTools`. * `displayName` is deliberately never consulted. */ export declare function recomputeVerdict(boundary: CapabilityBoundary, events: TraceEvent[]): Verdict; /** Build a well-formed boundary, computing its permission fingerprint. */ export declare function makeBoundary(principal: string, allowedTools: string[]): CapabilityBoundary; /** Generate an ephemeral Ed25519 keypair (e.g. for tests/demos). */ export declare function generateKeypair(): { publicKey: KeyObject; privateKey: KeyObject; }; /** Export a public KeyObject to the receipt's base64url raw form (JWK `x`). */ export declare function exportPublicKey(publicKey: KeyObject): string; /** * Attest a trace against a boundary, signing the receipt with `privateKey`. * * The attestor signs the TRUE recomputed verdict — it never fabricates an * `inBounds: true`. For an out-of-bounds trace the receipt faithfully * records `verdict.inBounds === false`; the verifier (and CLI) then treat * that as a refusal-to-attest. "Refuses to attest" = does not issue a * passing receipt, NOT silently signs a green one. */ export declare function signReceipt(boundary: CapabilityBoundary, events: TraceEvent[], privateKey: KeyObject, opts?: { signedAt?: string; }): Receipt; /** Structured verification result. `ok` is the single boolean to gate on. */ export interface VerifyReport { /** `signatureOk && rootHashOk && verdictConsistentOk && inBounds`. */ ok: boolean; /** Ed25519 signature verified under the receipt's public key. */ signatureOk: boolean; /** Hash-chain over `events` reproduces `receipt.rootHash`. */ rootHashOk: boolean; /** Independently recomputed verdict deep-equals the claimed one. */ verdictConsistentOk: boolean; /** The recomputed verdict says every event is in bounds. */ inBounds: boolean; /** Pinned-key check (only meaningful when `opts.expectPublicKey` given). */ publicKeyOk: boolean; /** Pinned-principal check (only meaningful when `opts.expectPrincipal` given). */ principalOk: boolean; /** The verifier's own recomputation (authoritative over the claim). */ recomputed: Verdict; /** First failing reason, or null when `ok`. */ reason: string | null; } export interface VerifyOptions { /** Pin the expected signer public key (base64url raw form). */ expectPublicKey?: string; /** Pin the expected boundary principal (SPIFFE ID). */ expectPrincipal?: string; } /** * Verify an in-bounds attestation receipt fully and offline: signature + * hash-chain + an INDEPENDENT recomputation of the verdict. Never throws on * a malformed receipt — failures surface as `ok: false` with a `reason`. */ export declare function verifyReceipt(receipt: Receipt, opts?: VerifyOptions): VerifyReport; /** Parse a receipt from JSON text, then verify. Convenience for the CLI. */ export declare function verifyReceiptJson(receiptJson: string, opts?: VerifyOptions): VerifyReport; /** Load an Ed25519 private key from PEM text (PKCS#8). */ export declare function privateKeyFromPem(pem: string): KeyObject;