ctx.cat Security Model
ctx.cat private shares are zero-knowledge by default: content and metadata are encrypted before upload, and decrypt keys remain in URL fragments.
Signing
Hosted ctx.cat does not sign plaintext user content. CTX_CAT_ENABLE_REMOTE_SIGNING defaults to disabled, so /signing/sign is only for local, self-hosted, or explicitly trusted deployments.
Author signatures should be created by local clients such as ctxcat or ctxcat-mcp. Server provenance receipts cover hashes only:
- signature envelope version
- content hash
- metadata hash
- public key fingerprint
- GitHub username claim, if supplied
- author signature timestamp
- backend receipt timestamp
Verification States
not published for that user at verification time.
allowed org.
failed.
match the encrypted metadata.
unsigned: no author signature.signed: content and metadata match the public-key signature.github-key-verified: signing key appears athttps://github.com/<user>.keys.github-claimed-unverified: the share claims a GitHub user, but the key wasorg-verified: GitHub key verified and the user belongs to a configuredinvalid: content, metadata, domain, purpose, or signature verificationtrace complete: decrypted agent transcript validates and per-record hashes
Ownership
Private owner URLs use #key=<decryptKey>&owner=<ownerCapability>. Legacy reader URLs using #<decryptKey> still load. The owner capability authorizes encrypted edits, deletion, expiration changes, and owner-marked encrypted comments.
Public and unlisted pages use #owner=<ownerCapability> for edits, deletes, and owner-marked comments. Their content is plaintext by design.
Client-authoritative create capabilities
The v1 create route separates allocation from authorization. A client chooses a random 128-bit record ID and a random 256-bit owner capability. Create sends only a domain-separated SHA-256 commitment over the decoded owner-capability bytes. The raw owner capability is rejected on create and is sent only for an authenticated reconciliation, edit, or delete. Private reader keys never leave the client.
Commitments are authorization verifiers, not replay identifiers. The service does not classify requests, store a replay fingerprint, or expose a commitment in responses. Logs and errors must contain only the record ID, status, and a generic message; capability and commitment headers must be redacted.
The commitment does not make a weak capability safe. Clients must generate the full 32 random bytes, encode them as canonical unpadded base64url, and compare reconciliation content locally before treating an ambiguous create as success. Private reconciliation streams ciphertext rather than placing base64 in JSON. Clients should hash that stream incrementally, enforce the declared size and a deadline, and compare it with the already prepared ciphertext and encrypted metadata digests before accepting an ambiguous create.