braket.gg BETA
EN

Trust and data

Braket holds a studio's most sensitive assets: your Steam publisher key, your normal Web API key, SMTP credentials, and all of your tournament data. This chapter documents how those are protected: the post-quantum credential vault that wraps your secrets, the signed and verifiable data export that proves how your data is stored, the hard per-tenant isolation, and the audit log. It is accurate to src/platform/vault.ts, src/platform/export.ts, and the platform schema.

The credential vault

Your Steam keys never sit in the database as plaintext. They are sealed in a post-quantum credential vault using an envelope scheme that is quantum-resistant by hybrid construction. The algorithm identifier stored with every secret is:

AES256GCM+HPKE(X25519+ML-KEM-768)

The envelope scheme

Each secret is protected in two layers:

  1. Data layer. The secret is encrypted with AES-256-GCM under a fresh 256-bit data encryption key (DEK) generated per secret. The ciphertext is stored as iv|ct|tag (hex).
  2. Key-wrapping layer. That DEK is itself encrypted with AES-256-GCM under a key-encryption key (KEK) derived by HKDF-SHA256 over the concatenation of two shared secrets: an X25519 ECDH exchange and an ML-KEM-768 (FIPS 203) encapsulation, both to the platform vault keypairs. The wrapped DEK is stored as ephPub|kemCt|iv|ct|tag (hex).

Because the KEK mixes a classical (X25519) and a post-quantum (ML-KEM-768) shared secret, an attacker must break both to recover a KEK. This is what defends against harvest-now-decrypt-later: even an adversary who records ciphertext today and gains a quantum computer later still faces the classical half, and vice versa.

The GCM additional authenticated data binds each secret to its tenant and kind (<tenantId>|<kind>|v1), so ciphertext cannot be transplanted between tenants or secret kinds.

Vault keys and the master key

The platform holds one active keypair per kind (vault_x25519, vault_mlkem768, and mldsa65_signing for signing). The public halves are stored in the clear; the private halves are stored AES-256-GCM encrypted under a master key. The master key is VAULT_MASTER_KEY (64 hex chars = 32 bytes), held in the environment and KMS-backed in production. In real (non-mock) mode the master key is required: the vault refuses to fall back to a session-secret-derived key. Only in mock/dev does it derive a deterministic key from the session secret so hermetic tests work.

What a secret's lifecycle looks like

The secret kinds stored are web_api_key, publisher_api_key, and smtp_pass.

The signed data export and the Trust page

Every arena can produce a complete, signed data export, the "show me exactly how my data is stored" feature exposed on the Trust page. The export format is braket-export.v1 and it contains everything the tenant owns:

How the signature works

The whole payload is serialized as canonical JSON (RFC-8785-style: object keys sorted recursively, so any third party reproduces the exact signed bytes regardless of key order), hashed with SHA-256, and the hash is signed with the platform's ML-DSA-65 (FIPS 204) key. The bundle carries integrity.sha256, and signature with alg: "ML-DSA-65", the signer's publicKeyHex, and the signatureHex.

Verifying an export offline

Studios can verify an export offline:

npx tsx src/dev/verifyExport.ts <export.json>

Verification is strict about the signer key. It recomputes the canonical JSON hash and checks it against integrity.sha256, then verifies the ML-DSA-65 signature, but only against a platform public key you supply out of band (from the Trust page or a published constant). The key embedded in the bundle is never trusted on its own, because an attacker could sign a forged bundle with their own keypair and embed their own public key. If the signer key does not match the trusted platform key, verification fails with signer key does not match the trusted platform key; a modified payload fails with payload hash mismatch; an invalid signature fails with ML-DSA-65 signature invalid.

The same ML-DSA-65 signing key is also the foundation for signing other attestations (for example event-chain heads) as those features land.

Per-tenant data isolation

Isolation is physical, not just logical: one SQLite database file per tenant. The platform database is only a tenant registry; a tenant's actual data lives in its own file (<slug>.db), with no shared tables between tenants. The export's storage description states this directly: "One SQLite database per tenant (<slug>.db), no shared tables with other tenants." Combined with the vault's per-tenant AAD binding, there is no path by which one arena's request can read another arena's data or secrets.

Backups are continuous Litestream replication to S3 with 30-day retention. Deleting a tenant removes its live secret ciphertext immediately; backup copies age out within the retention window.

Audit logging

Sensitive actions are recorded in a per-tenant audit log (audit_log): the tenant id, the acting admin's SteamID64, the action name, and metadata. It captures things like secret changes (secret.set), API-key creation and revocation (api_key.created, api_key.revoked), and result overrides. The audit log is included in the signed data export, so the record of who did what is itself covered by the export's integrity signature.

Summary of the guarantees

Concern Mechanism
Steam keys at rest AES-256-GCM ciphertext, DEK wrapped by hybrid X25519 + ML-KEM-768 to the vault keys
Harvest-now-decrypt-later Post-quantum ML-KEM-768 in the hybrid KEK; breaking one primitive is not enough
Master key handling VAULT_MASTER_KEY required in real mode, KMS-backed; private vault keys sealed under it
Export integrity Canonical-JSON SHA-256 signed with ML-DSA-65; verify offline against a pinned platform key
Secret exposure in exports Secrets appear only as ciphertext metadata, never plaintext
Tenant isolation One SQLite database file per tenant; per-tenant AAD binding on secrets
Accountability Per-tenant audit log, included in the signed export