Secrets Management
Store and retrieve secrets under envelope encryption
Lux KMS stores secrets under envelope encryption and serves them over HTTP and the native ZAP wire. A secret is addressed by four values — org, path, name and env — and holds exactly one value per address.
Addressing
| Part | Meaning |
|---|---|
org | Tenant scope. Also the scope of the token that reads it. |
path | A grouping string you choose, e.g. ci, myservice/local. |
name | The secret's name, e.g. DATABASE_URL. |
env | dev, test, main (or default). |
There is nothing to create first — no project, no workspace, no folder object. Writing to a path brings it into being.
env is a field, not a hostname. One KMS per org serves every environment;
there are no kms.dev.* / kms.test.* hosts.
Storing and reading
# create or replace — one upsert, not two endpoints
curl -X POST https://kms.lux.cloud/v1/kms/orgs/lux/secrets \
-H "Authorization: Bearer $TOKEN" \
-d '{"path": "myapp", "name": "db-password", "env": "main", "value": "s3cureP@ss"}'
# read
curl -H "Authorization: Bearer $TOKEN" \
"https://kms.lux.cloud/v1/kms/orgs/lux/secrets/myapp/db-password?env=main"
# list names under a path
curl -H "Authorization: Bearer $TOKEN" \
"https://kms.lux.cloud/v1/kms/orgs/lux/secrets?path=myapp&env=main"In the URL, path and name are one segment list split at the last slash:
.../secrets/a/b/C is path a/b, name C. Escape each segment on its own —
escaping the joined string encodes the separators and the server reads one long
name.
Encryption at rest
Read this before choosing a transport — the two planes do not store the same bytes.
Over ZAP, each secret gets its own random 256-bit DEK; the value is sealed with AES-256-GCM under that DEK, and the DEK is wrapped under the deployment's Root Encryption Key. The AAD binds path, name and env, so a sealed value cannot be replayed at a different address.
Over HTTP, the value is stored as given and returned as given. The per-secret
envelope is not applied on this path. What protects it is whole-database
encryption at rest (KMS_ENCRYPTION_KEY_B64, applied by the storage engine over
the entire keyspace) plus org-scoped IAM JWT auth and TLS in transit. That is
meaningfully weaker than the envelope: there is no per-secret key and no
address binding.
Two consequences that will bite if you mix transports against one store:
- A secret written over ZAP and read over HTTP returns the raw sealed bytes, not the value. HTTP does not unseal.
- A secret written over HTTP and read over ZAP fails to unseal — there is no wrapped DEK to unwrap.
Pick one transport per secret and stay on it. The Go client uses ZAP exclusively.
| Scheme | Use |
|---|---|
aead+mlkem | AES-256-GCM with an ML-KEM-encapsulated DEK (ZAP path). |
tfhe | Threshold reveal. |
ckks | Encrypted ML compute. |
The Root Encryption Key can be sourced from a luxfi/mpc threshold cluster
(MPC_REK_ENDPOINT) rather than a static K8s Secret, in which case the unwrapped
REK exists only in the process heap between boot and shutdown. A deployment
configured that way fails closed — if the REK cannot be fetched, it refuses
to start rather than falling back. A deployment with no REK at all still serves
the HTTP secrets plane, since that plane never needs one.
Transports
The same store is served two ways, with the same question set:
| HTTP | ZAP op | |
|---|---|---|
GET .../secrets/{path}/{name} | OpSecretGet 0x0040 | read |
POST .../secrets | OpSecretPut 0x0041 | create or replace |
GET .../secrets?path=&env= | OpSecretList 0x0042 | list names |
DELETE .../secrets/{path}/{name} | OpSecretDelete 0x0043 | delete |
ZAP is the in-cluster path (binary, over luxfi/zap); the Go client uses it
exclusively and has no HTTP fallback.
What this service does not do
The following are not implemented — if you need them, they are your application's job, not the KMS's:
- Versioning. One value per
(org, path, name, env). A write replaces the previous value; there is no history and no rollback, and no?version=selector. - Automatic rotation. There are no rotation providers or intervals. Rotating a credential means writing the new value.
- Point-in-time recovery. Durability comes from encrypted replication of the whole store to S3, not from per-secret history.
- Tags and folders as objects.
pathis a plain string, not a folder record you create or list.
Earlier revisions of this page described all four as features. They were inherited from a different product this service replaced, and never shipped here.
Durability
The embedded store replicates to S3 continuously — incremental every second, full snapshot hourly — with every replicated object encrypted under age before it leaves the process.
s3://<bucket>/kms/{node-id}/
├── snap/{timestamp}.zap.age
└── inc/{version}.zap.age