Skip to main content

How your secrets stay secret

01 · In your browser

Your passphrase never leaves

You type it client-side. ACP never receives it over the wire. Not on sign-up, not ever.

What we can and cannot see

Even someone with full database access cannot read your server secrets.

We never see
Your passphrase
The key is built in your browser. We receive the vault key, never the passphrase.
Bridge bearer secret
Locked under your key before storage. Shown as plain text only inside an unlocked session.
Discord bot token
Same AES-256-GCM lockbox. Only the scrambled text is stored in our database.
ed25519 signing private key
Locked under your key. The matching public key lives in your acp-query config.
We store
Argon2id salt
Needed to rebuild the key from your passphrase at next unlock.
Locked vault key
The passphrase-locked vault key. Useless without your passphrase to rebuild the key.
Secret ciphertexts
AES-256-GCM output. Cannot be read without the vault key.
Public keys
X25519 per-member public keys used to re-lock the vault key for teammates.

What actually lands on disk

The real shape of a stored row and a signed bridge request. No readable secrets, anywhere.

A stored credentials row
organization_credentials
├─ org_id            "org_3kf9..."            ← plain text
├─ argon2_salt       "9a1c4f…e02b"  (16 B)    ← public, per-org
├─ wrapped_dek       "AY8kQ2…Lf9w=="          ← passphrase-locked, unreadable
├─ bridge_secret     "AbZ3rT…0pQ=="           ← AES-256-GCM(vault key)
├─ bot_token         "Ac91mN…7xV=="           ← AES-256-GCM(vault key)
├─ signing_privkey   "Ad77kP…2sR=="           ← AES-256-GCM(vault key)
└─ cipher_version    1                        ← plain text

Pull the database and you get this. Every secret column is AES-256-GCM scrambled text.

A signed bridge request
POST /acp-bridge/players HTTP/1.1
Authorization: Bearer acp_q_b6f1…             ← rotatable, useless alone
X-ACP-Timestamp: 1717372800                   ← ±30 s window
X-ACP-Nonce: 7f3a9c12-…                        ← 5-min memory, single-use
X-ACP-Signature: ed25519:9c4f…a17e            ← signs ts+nonce+method+path+sha256(body)

# Replay the same bytes 31 s later →  401 stale_timestamp
# Replay within window, reused nonce →  401 nonce_seen
# Change one body byte →  401 bad_signature

Stealing the bearer token alone gets an attacker a 401 on every replay variation.

ed25519 signing input (per request)
timestamp±30 s window
nonce5-min single-use
method + pathcanonicalized
body hashSHA-256
ed25519 signature → verified server-side
acp-query signing

Leaked tokens cannot be replayed.

acp-query requests are signed per-request with an ed25519 key locked under your vault key. You paste the matching public key into your server's config. ACP never holds it unlocked.

The signing input includes a timestamp (±30s window) and a one-time nonce kept for 5 minutes. Even if a token is intercepted, replaying it without a fresh signature is rejected.

Threat model

An honest account of what this scheme protects and what it does not.

Protected against
Database dump or backup leak
Scrambled text is useless without the passphrase. Argon2id m=64 MB makes offline guessing expensive.
Rogue operator reading rows
Encrypted columns are opaque. Even direct database access cannot reveal credentials.
Stolen acp-query bearer token
ed25519 per-request signing + 30s timestamp window + 5-min nonce. Replay-resistant.
Session hijack without the key entry
Stealing the session cookie without the matching server-side key entry yields nothing useful.
Not protected against
Vault key in server memory during an active session
The key reaches the server for the session lifetime. A breach during that window could expose it.
Compromise of your browser during a session
Anyone with access to your browser while you're unlocked can read the key from memory.
Malicious operator changing server code
This is a data-at-rest defense. A code-level attacker could capture the passphrase at login. Runtime integrity is out of scope.

Technical specifications

Primitives and parameters, no hand-waving.

Cryptographic primitives

Security FAQ

What if I forget my passphrase?+
If you configured a recovery code during onboarding, enter it via the "Forgot passphrase?" link on the unlock dialog. It unlocks your vault and lets you set a new passphrase. If a privileged team member still has an active session, they can re-grant access without resetting. Without either, encrypted data is unrecoverable and the organization must be reset: new passphrase, regenerated credentials, updated FiveM config.
Can ACP staff read my data?+
No. A database dump only reveals scrambled text, salts, and locked key material. None of that opens without your passphrase, which never reaches our servers. The only brief exposure is the vault key in memory during an active session, but that is tied to your session and purged on logout.
What happens if your database is leaked?+
An attacker obtains scrambled text, salts, and locked vault keys, but no readable credentials. To recover anything they need your passphrase, and Argon2id at m=64 MB makes offline guessing expensive: each guess uses 64 MB of memory. Unique salts per org block precomputed attacks. Rotate passphrases across affected organizations as a precaution; each org rotates independently.
Is this zero-knowledge?+
Close, but not absolute, and we won't claim it is. The passphrase never leaves your browser, which is the strongest property. During an active session the vault key is held briefly in server memory to decrypt credentials for API calls. A breach during that window, or a malicious change to our server code, could expose the key. The threat model section above is the honest summary.

Ready to take control?

Set up your admin panel in minutes. No credit card required.

Start Free Trial