Expand description
Credential custody protocol (DR-0053).
whip holds handles; a custodian in a separate security principal holds
material and performs operations. This crate is the seam between them: the
handle/sentinel types, the operation vocabulary, and the transport trait.
It contains no backend and no cryptography — those live in
whipplescript-custodian.
The vocabulary is deliberately closed and there is no get(handle) at
any layer (DR-0053 §2): the only power the custodian offers is
substitute / sign / verify / derive / wrap / unwrap / mint under policy.
One caller with a legitimate reason to fetch plaintext would re-establish
extractability for all, so the operation does not exist to be called.
models/maude/credential-no-eliminator.maude proves the language side of
this; the -REVEAL sibling carries the rejected get design.
The custodian stays semantically dumb (§3): whip constructs entire
requests with a typed Sentinel exactly where material belongs, and the
custodian’s whole power is substitution at the marked slot if policy
permits. It does not parse payloads, choose endpoints, or know what an API
is.
Modules§
- canon
- Built-in canonicalizers (DR-0053 §7).
- client
- whip’s client transport to a custodian daemon on this box: one
newline-delimited JSON call per connection over a Unix domain socket
(the server half lives in
whipplescript-custodian::serve). Unix-only, so the protocol crate stays wasm-clean; a wasm host supplies its ownCustodyTransport.
Structs§
- Credential
Name - A credential’s stable name. Resource identity is
credential:<name>— deliberately not backend-qualified (vault:,kms:), so identity survives backend migration and grants keep working (DR-0053 §5). - Custody
Call - One call over the transport.
- Custody
Reply - The custodian’s reply. Every reply — refusals included — carries the use
id it was recorded under and the rung the credential’s evidence derives,
with
degradedset when the resolution is a compatibility shim (legacyenv:refs resolve at r0 degraded; DR-0053 Migration). - Egress
Request - An outbound HTTP request whip constructed in full, with sentinels at the marked slots. The custodian substitutes and egresses; it never chooses any part of this.
- Egress
Response - Envelope
- A label-carrying envelope (DR-0053 §13). The envelope records the caller’s
IFC label and
unwraprestores it, so the wrap → store → unwrap roundtrip cannot launder; AEAD associated data binds the ciphertext to (credential, context, label) so envelopes are not swappable between contexts even with every label intact (models/maude/credential-wrap-carriage.maude,-UNBOUNDsibling). - Mint
Extraction - How the custodian extracts the minted material from an exchange response
(DR-0053 Open, OAuth response capture): whip declares the path, the
custodian applies it — a dumb instruction, not protocol semantics. Only
simple dotted paths (
access_token,data.token) are supported; extraction is not a query language. - Sentinel
- A typed placeholder marking exactly where material belongs in a request
whip constructs. The custodian substitutes at marked slots and nowhere
else. The textual form is
{{whipplescript-credential:<name>:<form>}}. - UseAttribution
- Who is using the credential, recorded with every use — §1 claims every
use is attributable, and
UsesAreRecordedinCredentialCustody.tlaexists so the rung floor cannot be satisfied by an unrecorded use.
Enums§
- Credential
Kind - The declared kind of a credential.
kindexists so the checker can statically rejectsign … with stripe_api(DR-0053 §5); the custodian’s registered kind is authoritative and mismatch is a check error. - Credential
Ref - One parsed operator-config credential reference (DR-0053 Migration).
- Custody
Error - Typed refusals.
Refusedreasons are closed so callers can route them; backend faults carry a message. - Custody
Ok - Success payloads, one per operation. None yields sealed material:
DerivedandMintedreturn handles;Unwrappedreturns application data that was whip’s to begin with. - Custody
Op - One custody operation. Externally tagged by
op, and the vocabulary is closed: a message with"op": "get"— or any name outside this list — fails to deserialize. - Operation
- The closed operation vocabulary (DR-0053 §2). There is no
Getvariant and never will be; a wire message naming an unknown operation fails to deserialize. - Presentation
Form - Presentation form for material at a marked slot (DR-0053 §5): usable in any string position, lowering the handle to a sentinel.
- Rung
- Sealing rung, ordered (DR-0053 §4). Derived from evidence by the
custodian, never asserted in configuration —
require credential <rung>in the signed policy compares against what the custodian derived (models/maude/credential-rung-evidence.maude: configuration is not evidence). - Signature
Alg - Signature algorithm for
sign/verify. Distinct fromCredentialKind: the kind is what the credential is, the alg is what this call asks it to do, and the custodian refuses mismatches. - Transport
Error - Transport faults, distinct from custody refusals: a refusal is the custodian speaking; a transport error means it never did.
Constants§
- CUSTODIAN_
SOCKET_ ENV - The conventional environment variable naming the custodian socket.
- CUSTODY_
PROTOCOL - Wire protocol identifier, carried on every call so a custodian can refuse a caller from a different protocol generation.
Traits§
- Custody
Transport - The transport seam (DR-0053 §2, tracker slice 1). r0 may run in-process,
but it still speaks
CustodyCall/CustodyReplythrough this trait — wiring r0 as direct function calls would leave the principal-separation seam unexercised and make r1+ a rewrite rather than a backend swap.