References, encryption, and spill
Large, durable, and confidential are different payload problems. Cheetah provides three mechanisms because each changes a different boundary:
| Mechanism | Changes | Does not provide |
|---|---|---|
| signed payload reference | how supported command data crosses the WebSocket | encryption of the stored value |
| encrypted envelope | which components can read selected application plaintext | transport-size reduction or key management |
| Redis history spill | where a retained top-level payload is stored | a client-fetch protocol or command confidentiality |
Use ordinary inline JSON when it fits. Each additional mechanism introduces configuration, storage, and failure paths that the product must operate.
Reserved protocol keys
Keys beginning with __cheetah_ belong to the framework. The Python command boundary normalizes Unicode keys with NFKC and searches nested objects and arrays before accepting caller-supplied parameters, parser options, or inline definitions.
Applications must not manufacture payload markers to bypass validation. Use the helpers that own signing, storage, retrieval, encryption, and processing order.
Signed command references
The wire marker is exact:
{
"__cheetah_ref": true,
"token": "signed-nonempty-token"
}
With SendOptions.auto_offload enabled, which is the default, AppNode can offload supported values only when the serialized command exceeds its configured WebSocket limit and a token signer is present. Current automatic candidates are:
- the complete
command.paramsobject; - individual
capture.parser_definitions[].definitionbodies.
Candidates are tried from largest to smallest until the command fits. The original value is stored as an offloaded_payload record in the authenticated principal's history. The App node then signs a token bound to that principal, canonical stream, message identity, read scope, and expiry. store_and_sign() defaults the token lifetime to five minutes.
The host exposes RestNode.retrieve_payload() through its own authenticated HTTP route, commonly /api/payload. Retrieval verifies signature and expiry, principal equality, read scope, stream ownership, and the exact retained message. It returns 200 with the payload, 403 for invalid capability or principal binding, 404 when the record is absent, and 500 when no token signer is configured.
On the TypeScript side, FetchPayloadResolver defaults to /api/payload, a 10-second timeout, and strict transport security. The negotiated handshake can supply its base URL. Browser runtime composition enables automatic reference resolution by default; core and other custom compositions require a resolver and explicit enablement.
Resolution occurs before argument validation and local authorization. It supports markers in nested objects and arrays, although server-generated automatic command offload uses the whole- params position and explicit parser-definition position. Independent retrievals run in batches; maxConcurrency defaults to six and now rejects zero, negative, fractional, NaN, or infinite values before any resolver call.
errorPolicy defaults to best-effort, which leaves a failed marker in place and logs a warning. strict turns the failure into command-normalization failure. Resolved values are not walked again for a second layer of references.
Signing protects the integrity and scope of the retrieval capability. It does not hide the stored plaintext from authorized history or spill infrastructure.
Encrypted envelopes
The client-side envelope is:
{
"__cheetah_encrypted": true,
"keyName": "account-data",
"ciphertext": "Base64...",
"iv": "Base64...",
"metadata": {
"kdf": "pbkdf2",
"salt": "Base64...",
"iterations": 100000,
"hash": "SHA-256"
}
}
IPayloadEncryptor owns named keys and the cryptographic implementation. The supplied DefaultPayloadEncryptor uses AES-256-GCM through Web Crypto. String passphrases use PBKDF2 with a fresh 16-byte salt, 100,000 iterations, and SHA-256; raw key bytes are imported directly. Every encryption uses a fresh 12-byte IV.
encryptPayload() protects one JSON-like value. encryptPayloadFields() uses non-overlapping JSON Pointer selections over a cloned object. The selections must exist and cannot contain one another.
Automatic decryption is opt-in and requires an application-supplied encryptor. The dispatcher resolves signed references first, decrypts whole-params or nested envelopes second, then validates and authorizes the resulting parameters. A whole envelope that decrypts to a primitive is wrapped under __decrypted so command parameters remain object-shaped.
Decryption also defaults to best effort and does not re-walk newly decrypted content. Use strict mode and an argument schema that rejects opaque leftovers when a handler must never run without plaintext.
Cheetah does not distribute or rotate keys, encrypt on the server, or create a universal result-encryption policy. Encrypt before offloading when infrastructure must not see the offloaded value.
Redis history spill
History spill changes backend storage after an accepted returned message. When explicitly enabled, RedisHistoryStore compares the serialized top-level payload with the active retention policy's spill threshold. A large payload is written to the configured spill store; the Redis Stream keeps backend, key, size, and absolute-expiry metadata. Ordinary history reads rehydrate the normal inline message, so the internal payload_ref is not a client wire format.
Every newly managed spill receives one immutable expiry shared by its reference and external object. The static policy defaults that lifetime to 86,400 seconds. Expired spilled messages are omitted from normal and command-correlation reads.
The built-in file store records expiry beside each payload, rejects path traversal, hides and deletes expired values when read, performs throttled cleanup during later managed writes, and exposes cleanup_expired_payloads() for scheduled or decommission cleanup. It does not promise that an idle directory reaches zero files without that hook. Multi-node use requires storage visible to every reader and reasonably synchronized wall clocks.
Spill composition fails closed unless the store supports managed expiry or the host explicitly chooses allow_unmanaged_spill_retention=True. A spill write failure is fail-open for message availability: Redis history logs the failure and stores the payload inline. Hydration failures remain read failures. Explicit history deletion attempts to delete the paired external object.
This managed lifecycle is separate from browser-capture artifacts, whose current local store does not have coordinated retention.