/** * Client-side receipt→envelope BINDING (P0 fix: receipt-substitution). * * A valid receipt authenticates a decision the SERVER signed — it does NOT authenticate whatever * envelope an attacker wraps around it. Before executing with enforced:true, the guard must bind the * receipt to THIS envelope by LOCALLY RECOMPUTING the canonical decision body hash and comparing it * to the receipt's SIGNED `bh` (never trusting the envelope's own claimed `decision_body_hash`, which * an attacker rewrites). This mirrors the server's §106 isCurrentlyAuthorized binding, applied in the * client — the guard is the component that authorizes execution, so the binding must live here too. * * The canonicalization is ported VERBATIM from the server's canonical-json.js + decision-result.js * computeBodyHash so the recomputed hash is byte-identical to the server's decision_body_hash. */ /** * RFC 8785 (JCS) canonical JSON for the envelope domain (ASCII keys, JSON-representable scalars). * Ported verbatim from the server canonicalizer so client + server hash identically. */ export declare function canonicalJson(value: unknown): string; /** * decision_body_hash = 'sha256:' + sha256(canonicalJson(envelope MINUS receipt MINUS * decision_body_hash)). Recomputed LOCALLY from the presented envelope so a rewritten field can * never pass — identical algorithm to the server's decision-result.js computeBodyHash. */ export declare function computeBodyHash(envelope: Record): string; /** Minimal shape of the SDK verifyReceipt result the binding consumes. */ export interface VerifyReceiptResultLike { valid?: boolean; status?: string; reason?: string | null; payload?: Record; } /** Host context the receipt scope must match (from GuardConfig). */ export interface BindContext { operation?: string; environment?: string; audience?: string; } export type BindCause = 'RECEIPT_UNVERIFIED' | 'RECEIPT_ENVELOPE_MISMATCH'; export interface BindResult { ok: boolean; cause?: BindCause; detail?: string; } /** * Bind a verified receipt to THIS envelope. Fail-closed on ANY mismatch. Ordered checks: * 1. valid === true (bad signature → RECEIPT_UNVERIFIED) * 2. status === 'VERIFIED_CURRENT' (expired / superseded / wrong-audience / wrong-env * / scope-mismatch → RECEIPT_ENVELOPE_MISMATCH) * 3. signed bh === LOCALLY-recomputed body hash (the anti-substitution anchor — binds the ENTIRE * decision body incl. decision/execution_action/fp) * 4. signed fp === envelope.fingerprint (explicit fingerprint binding) * 5. operation / environment / audience scope (when the AUTHENTICATED envelope carries them — * bh already binds their presence/value) * * @param envelope the presented decision envelope (attacker-influenced; NOT trusted for bh/fp) * @param vr the SDK verifyReceipt result ({ valid, status, payload:{ fp, bh, … } }) * @param ctx host operation/environment/audience the receipt must match */ export declare function bindReceiptToEnvelope(envelope: Record | null | undefined, vr: VerifyReceiptResultLike | null | undefined, ctx?: BindContext): BindResult; //# sourceMappingURL=receipt-binding.d.ts.map