Authentication V3
How Sibyl Memory Plugin knows it's really you. Two paths, both designed to avoid the things that go wrong with classic API keys and passwords: no key to leak, no password to phish, no token sitting in a server log somewhere.
Two paths, picked once
Both auth paths converge on the same credentials file.
When you run sibyl init, the CLI creates a one-time session, opens a browser to the activation page,
and waits. In the browser you sign in one of two ways. The server records the bind, issues you a credentials file,
and the CLI writes it to your machine at ~/.sibyl-memory/credentials.json with file permissions
0600. From that point on, every memory operation runs locally against your own SQLite database. The
credentials only come into play when the SDK needs to check your tier with the server.
Sibyl Memory Plugin doesn't issue API keys. The credentials file is bound to a specific machine session and the identity you proved (wallet or email). You can't accidentally paste it into a public repo and lose your account, because there's no shared secret in it that would unlock anyone else's account.
Path 1 · Sign in with a wallet (SIWE)
Best path if you have any Ethereum-compatible wallet. Works with MetaMask, Rabby, Coinbase Wallet, Frame, or
any other wallet that supports the EIP-4361 standard
(which is essentially all of them now). Required if you want to subscribe in USDC or qualify for the staker tier
by holding $SIBYL.
The signature commits to a specific session.
What you'll see
- The activation page asks for a one-time signature.
- Your wallet pops up showing a plain-English message: who's asking, what session it's for, when it was issued. No transaction. No gas.
- You click sign.
- The page reports success and the terminal picks up automatically.
Example SIWE message (click to expand)
sibyllabs.org wants you to sign in with your Ethereum account:
0x742d35Cc6634C0532925a3b844Bc454e4438f44e
Sign in to Sibyl Memory Plugin, session 0244be7f-6517-4cdd-9546-c4e3242f13ca
URI: https://sibyllabs.org/plugin/activate
Version: 1
Chain ID: 8453
Nonce: 87Bk3p2N4qR
Issued At: 2026-05-16T22:47:13.482Z
Resources:
- https://api.sibyllabs.org/api/plugin/bind
The chain ID is 8453 (Base). The nonce is server-issued and bound to that specific session. The Resources line tells your wallet exactly which endpoint will receive the signature. Chain ID and session-binding are the two tightenings Sibyl Memory Plugin adds on top of standard EIP-4361 — see replay protection.
Path 2 · Sign in with email (terminal pairing code)
Best path if you don't have a wallet yet, or you want to try the free tier without committing to one. Here's the flow:
- You run
sibyl init. The CLI generates a fresh 6-digit pairing code locally on your machine (via Python'ssecrets.randbelow), and prints it in the terminal. - The CLI sends only the SHA-256 hash of the code (with your session ID mixed in) to the server. The actual code never leaves your machine until you type it into the browser yourself.
- The browser opens to the activation page. You type your email and the 6-digit code from your terminal.
- The server hashes what you typed, compares to the hash it has on file, and binds the email to your session.
The code never leaves your machine in plaintext.
sha256("492071:0244be7f-...c4e3242f13ca"). The 6 digits stay on your machine until you type them into the browser yourself.The point of the design: you never have to wait for an email to arrive (no provider, no spam filter, no DNS hops), and no third party ever sees your code. The code in your terminal and the code in the browser come from the same machine, mediated by you.
Magic-link email flows depend on a transactional email provider. Providers go down, mail gets flagged as spam, DNS misconfigurations break delivery, and your sign-in flow becomes coupled to a vendor's reputation. The pairing-code design has no email provider in the loop at all. The signal travels from your terminal to your browser via the most reliable wire on earth, which is your own eyes and fingers.
Brute-force resistance
A 6-digit code is one of a million possibilities. The server enforces three guards:
After 5 wrong codes, the session is locked and you have to start over with a fresh sibyl init. Codes
expire 15 minutes after issue. The per-IP rate limit on /session-init and per-email cap on
/email-bind together defeat the brute-force-via-many-sessions attack against the 106
code space.
Tier-jacking protection
If an email already has a wallet bound to it, the email-bind path refuses with a clear error and points to the wallet path. The reasoning: a free-tier email-only account has nothing worth jacking, but a paid user (subscribed or staked) always has a wallet bound, and giving wallet-bound accounts exclusive authority over upgrade decisions means an attacker who learned your email can't downgrade or sideload your tier.
Your credentials file
Either path writes ~/.sibyl-memory/credentials.json. The shape:
Identity, tier hint, and tamper-evidence in one file.
The fields, briefly:
account_id. Stable identifier for your account. Used by every server-side call.tenant_id. Used to isolate your memory data on the server side (if you ever sync) and locally when one machine has multiple identities. Equals your account_id unless you're on an Enterprise plan with multi-tenant access.tier.free,sync,stake,lifetime, orenterprise. Authoritative version always comes from the server; this is a cached hint.emailandwallet. Whichever you used to sign in. Both may be populated if you've upgraded or added a wallet.session_token. Long-lived bearer token, sent on cap-check and access-check requests. The full token is never logged or printed to your terminal output;sibyl statusshows a short slice.signatureandsigned_at. HMAC-SHA256 over the canonical credential fields, signed by the server. See tamper-evident credentials.
Tamper-evident credentials (HMAC)
The signature field is an HMAC over your credential fields, produced server-side with a secret only
the server holds. Every time the SDK calls the server (for cap checks or tier verification), it echoes the
signature back. The server verifies that the signature matches the credentials it issued. If you (or someone
with file access to your laptop) edited the credentials file to change tier from free
to sync in an attempt to bypass the cap, the signature wouldn't match and the server would log a
credentials_tamper_suspected event.
Local edits get caught at the server boundary.
The cap decision itself never depends on what your credentials file says. It comes from the server's database via the authoritative tier lookup. HMAC is tamper evidence, not access control. Even if you wanted to cheat the local file, the server has the last word.
Most of your reads and writes never call the server. The cap check only fires when a free-tier user is about to push past 2 MB, or when the local tier cache (7-day TTL) has expired. Under-cap writes for free users and all writes for paid users go straight to your local SQLite. Authentication doesn't change that.
Replay protection (the details)
A wallet signature is just bytes; if an attacker captured one, in principle they could reuse it. Sibyl Memory Plugin has three layers of defense:
- Session-bound nonces. The server issues a fresh random nonce for each activation request and stores it alongside the session UUID. The bind step requires both that the nonce exists and that its stored session matches the session you're trying to bind. A signature from session A cannot bind session B.
- Session UUID in the signed statement. The text you sign explicitly names your session ID. If an attacker recorded a signature and tried to replay it on their own session, the statement would mismatch.
- One-time nonces. Each nonce can only be consumed once. After a successful bind, the nonce is deleted.
Chain ID + RPC verification
When the server verifies an on-chain transaction (for staker checks or USDC subscribes), it asserts the chain ID is 8453 (Base) before accepting any state. This blocks the cross-chain replay attack where the same transaction hash exists on Polygon, Arbitrum, or Mainnet for an unrelated transfer.
Refreshing or rotating credentials
If your credentials file gets lost or you want to rotate the session token:
$ sibyl init --reset
This generates a new session, walks you through activation again, revokes the old session_token, and writes a fresh credentials file. Your account, tier, history, and memory data are unaffected; only the local credentials are replaced.
What to back up
Your memory data lives in ~/.sibyl-memory/memory.db. That's the file to back up. The
credentials file is regeneratable any time you can prove identity to the activation page (wallet sign or email
pairing-code). The tier cache regenerates itself automatically on the next cap-check.
What to do if you suspect a key compromise
- Run
sibyl init --resetimmediately. Old session tokens are revoked server-side. - If your wallet is potentially compromised (not just your laptop), do not bind that wallet again. Use a fresh wallet for the new bind.
- Email [email protected] if you saw a charge or activity you didn't initiate. We can manually freeze the account while you reset.
FAQ
Do you ever see my wallet's private key?
No. Wallet signing happens entirely in your wallet. The server only sees the signature, which is one-way.
Do you ever see the pairing code?
No. Only the SHA-256 hash of code + ":" + session. The code itself stays on your machine.
Can I have multiple accounts on one machine?
Yes. Pass --credentials-dir <path> to sibyl init and the credentials + memory
database land in the directory you specify. Useful for work-vs-personal splits, or for testing.
What if Sibyl Labs disappears?
Your memory.db is plain SQLite and stays on your machine. You can open it with any SQLite tool. The
tier check stops resolving, so the SDK falls back to a strict local 2 MB cap. Free tier becomes the default
forever. The self-hosting path (Enterprise tier) removes the dependency on us entirely; ask if that matters to
you.