Documentation

Privacy Controls

IPFS was designed for open sharing, and its addressing model quietly assumes it: a CID is derived from the content, so anyone who learns the CID can ask the network for the bytes. For a public dataset that's the whole appeal. For a pre-publication genome it's a problem, and "just don't share the CID" is not a privacy model.

Ekayana layers three real controls on top, in increasing order of strength:

  • Gateway access control - session tokens, UCAN capabilities, IP allowlisting, geolocation restrictions. This stops the platform's own gateway from serving unauthorized reads.
  • Permission management - every grant is a signed UCAN naming specific actions (read, write, delete, share, admin), time-bound, attenuable, and revocable.
  • Content encryption - payloads are sealed before they reach IPFS, with a fresh ML-KEM-1024-wrapped key per object; client side encryption is available for zero-knowledge workflows.

The first two depend on the platform enforcing rules. The third doesn't - and that distinction is the backbone of the rest of this page.

Encrypt then store

The strongest privacy control is the one that does not depend on an access-control list being enforced correctly. Private payloads are encrypted before upload, so the CID addresses ciphertext:

Fresh 32-byte content keyrandom, one per payloadSEALS THE DATASEALS THE KEYAES-256-GCMpayload + associated dataSealed payloadciphertextIPFSthis is what the CID addressesML-KEM-1024encapsulation → shared secretHKDF-SHA256 → AES-256-GCMderives a wrapping key, sealsSealed content keyopens only with the decap keyA leaked CID hands over the left column only - bytes with no key anywhere near them.
Envelope encryption: a fresh 32-byte content key per payload, used on one branch to seal the payload with AES-256-GCM before it goes to IPFS, and on the other sealed itself by ML-KEM-1024 encapsulation through HKDF-SHA256 into AES-256-GCM

Consequences worth being precise about:

  • A leaked CID is not a leaked dataset. Any node replicating the block holds bytes that are useless without the ML-KEM decapsulation key.
  • The AEAD binds the payload to its owner. Associated data ties each sealed payload to the identity it was stored for, so a ciphertext lifted into another account will not open.
  • Tampering is detected, not silently served. AES-256-GCM is authenticated; a modified block fails to decrypt rather than returning corrupted data.
  • Legacy content still works. Sealed payloads carry a version marker, so pre-existing plaintext content is served unchanged during migration.
On the "right to be forgotten". IPFS content is immutable and may be replicated beyond your control, so deletion cannot be guaranteed by unpinning alone. Encrypting at rest makes erasure effective rather than merely best-effort: destroying the key renders every surviving replica permanently unreadable. This is why nothing personal is ever anchored on-chain - only public keys, opaque fragments, and URIs.

See Post-Quantum Security for the algorithms and key management, and UCAN Authorization for how a content key is delegated to a recipient inside the capability token itself.

Implementing Privacy Controls

Encryption is a property of the write path

There is no "encrypt: true" flag to remember. Payloads written through the encrypted path are sealed automatically: a fresh AES-256-GCM content key per object, itself wrapped to the server's ML-KEM-1024 key. Reads decrypt transparently, and an object stored as plaintext before the encryption layer existed is detected and passed through unchanged - so both coexist without a migration.

The associated data binds each object to its owner, so a sealed payload cannot be replayed into another account's context even by someone holding the ciphertext.

Access control is capability based

Access is granted by minting a scoped, revocable UCAN capability - not by editing an ACL:

bash
curl -X POST https://api.ekayana.com/api/share/file \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "cid": "QmXyz...",
    "recipient_did": "did:bio:user:7",
    "capabilities": ["read"],
    "expiration_days": 7
  }'

Revoke with DELETE /api/share/revoke/{token_hash}. Revocation cascades: the proof chain is walked on every validation, so withdrawing a grant also invalidates everything delegated beneath it.

What this gives you under GDPR

RequirementMechanism
Data minimisationOnly public keys, fragments, and URIs are ever anchored on-chain
Purpose limitationCapabilities name one resource and one action set
Storage limitationEvery grant carries a signed expiry
Right to erasureDelete unpins the platform copy; destroying the key renders surviving replicas unreadable
Integrity & confidentialityAES-256-GCM at rest, post-quantum key wrapping
Honest limit: IPFS is a public network. Once a plaintext object has been fetched by a third party, no API can recall it. That is precisely why personal and pre-publication data is encrypted before it reaches IPFS - erasure then depends on key destruction, which you control, rather than on cooperation from every node that ever cached the content.