SIBYL LABSdocs

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.

One signature, one pairing code, or nothing. No passwords. No long-lived API keys. No leak surface.

Two paths, picked once

overview · the activation handshake

Both auth paths converge on the same credentials file.

terminal sibyl init opens browser wallet email path 1 · siwe sign nonce in wallet EIP-4361 · session-bound path 2 · pairing 6-digit code from terminal sha256-hashed before send credentials.json ~/.sibyl-memory/ HMAC-signed · 0600 activation completes in <60s on either path

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.

No API keys

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.

flow · siwe sequence

The signature commits to a specific session.

TERMINAL BROWSER WALLET api.sibyllabs.org open(activate?session=...) GET /nonce?session=... nonce (session-bound, 1-use) sign(msg + session-id) signature POST /bind { sig, msg } credentials.json (HMAC-signed) terminal poll picks up · activation complete

What you'll see

  1. The activation page asks for a one-time signature.
  2. 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.
  3. You click sign.
  4. 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:

  1. You run sibyl init. The CLI generates a fresh 6-digit pairing code locally on your machine (via Python's secrets.randbelow), and prints it in the terminal.
  2. 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.
  3. The browser opens to the activation page. You type your email and the 6-digit code from your terminal.
  4. The server hashes what you typed, compares to the hash it has on file, and binds the email to your session.
visual · what you see in your terminal

The code never leaves your machine in plaintext.

~/projects/atlas-agent — sibyl init
$ sibyl init Sibyl Memory Plugin · activation Session: 0244be7f...c4e3242f13ca Opening: https://sibyllabs.org/plugin/activate?session=0244be7f-... Email path · pairing code (type into the browser):
492071
Wallet path · just sign in your wallet, no code needed. waiting for browser activation … 9:42 left
The hash sent to the server is 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.

Why this instead of a magic link?

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:

5attempts / session
15mincode TTL
30/hr/session-init per IP
50/day/email-bind per email

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:

anatomy · credentials.json schema v2

Identity, tier hint, and tamper-evidence in one file.

~/.sibyl-memory/credentials.json · mode 0600 "account_id": "3ac673c8-cdbb-4af2-..." "tenant_id": "3ac673c8-cdbb-4af2-..." stable account identity "email": "[email protected]" "wallet": "0x742d35Cc...4438f44e" whichever you bound "tier": "free" cached hint · server is authoritative "session_token": "st_a8f3...7b21" bearer · short slice on print "signature": "e3b0c4...2b855" "signed_at": "2026-05-16T22:47Z" "issued_at": "2026-05-16T22:47Z" HMAC-SHA256 over canonical fields "schema_version": 2 tamper-evidence edit → server detects mismatch

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, or enterprise. Authoritative version always comes from the server; this is a cached hint.
  • email and wallet. 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 status shows a short slice.
  • signature and signed_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.

defense · hmac tamper-evidence

Local edits get caught at the server boundary.

local credentials tier: "sync" attacker edited from "free" signature: e3b0c4... echo sig + claim to /check-write api.sibyllabs.org HMAC verify(sig, fields) → mismatch detected log credentials_tamper_suspected use DB tier (free) instead cap-decision is server-authoritative cap_exceeded upgrade URL returned HMAC is tamper evidence, not access control · the DB always has the last word

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.

Local-first promise

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

  1. Run sibyl init --reset immediately. Old session tokens are revoked server-side.
  2. If your wallet is potentially compromised (not just your laptop), do not bind that wallet again. Use a fresh wallet for the new bind.
  3. 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.