Skip to main content
CyborgDB supports per-user access control (RBAC) on an encrypted index. Instead of sharing the single root index key with every caller, the holder of the root index KEK can mint per-user keys that grant scoped read and/or write access. Each user then loads the index with their own per-user key and is restricted to the permissions they were granted.
Key model. Each index is encrypted under a 32-byte Key-Encryption-Key (KEK) — the index_key. The root KEK holder mints per-user wrapped keys; the set of wraps that exist is the permission set — there is no separate permission flag. A read-only user simply has no write wrap. Managing users (create / delete / list) is always gated on the root KEK.

Minting User Keys

The root holder calls create_user_keys (Python) / CreateUserKeys (C++), supplying the new user’s 16-byte identifier, their 32-byte per-user KEK, the permissions to grant, and the root index KEK as the admin gate.
import cyborgdb_core as cyborgdb
import secrets

api_key = "your_api_key_here"  # Replace with your CyborgDB API key

client = cyborgdb.Client(api_key, cyborgdb.StorageConfig.memory())

# Root index KEK — the admin gate for user management
index_key = secrets.token_bytes(32)
index = client.create_index("shared_index", index_key)

# Mint a read-only user and a read/write user
reader_id = secrets.token_bytes(16)
reader_kek = secrets.token_bytes(32)
index.create_user_keys(reader_id, reader_kek, permissions=["read"], index_key=index_key)

writer_id = secrets.token_bytes(16)
writer_kek = secrets.token_bytes(32)
index.create_user_keys(writer_id, writer_kek, permissions=["read", "write"], index_key=index_key)
You must securely deliver each user’s user_id and user_kek to that user out-of-band — together they are that user’s credentials for the index.

Loading the Index as a User

A user loads the index with their own KEK as the index_key and passes their user_id. From then on, every operation is gated to the permissions they hold — a read-only user’s upsert/delete is rejected, raising RuntimeError in Python (std::runtime_error in C++).
# The read-only user loads the index with their own key
user_index = client.load_index("shared_index", reader_kek, user_id=reader_id)

# Reads are allowed
results = user_index.query(query_vectors=[0.1, 0.2, 0.3, 0.4], top_k=5)

# Writes are rejected for a read-only user
# user_index.upsert([...])  # raises RuntimeError — no write wrap
In Python, you can also override the key per operation with index_key= and user_id= keyword arguments — required in stateless/service deployments that reload the index per request. In C++, every key-bearing method takes a trailing KeyContext; construct it as cyborg::KeyContext{user_kek, user_id} for an RBAC user, or pass a bare KEK for the root holder.

Listing and Revoking Users

User management is always gated on the root KEK. list_user_keys returns each user’s id and whether they have read/write; delete_user_keys revokes a user (idempotent).
# List all users (root-gated)
for u in index.list_user_keys(index_key=index_key):
    print(u["user_id"], u["has_read"], u["has_write"])

# Revoke a user (idempotent, root-gated)
index.delete_user_keys(reader_id, index_key=index_key)

API Reference

For full signatures and exceptions, refer to the API Reference:

Python API Reference

API reference for create_user_keys / list_user_keys / delete_user_keys in Python

C++ API Reference

API reference for CreateUserKeys / ListUserKeys / DeleteUserKeys in C++