Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Trust assumptions and threat model

The Economics chapter traces where every lamport goes; this page traces where trust goes: what each participant must assume about the others, which of those assumptions are enforced on-chain, and which live off-chain in an operator’s configuration or a service the network operator runs. Nothing here is a hidden flaw — each item is a deliberate design boundary — but anyone holding real value in the system should know where the boundaries are.

Every on-chain guarantee below is a guarantee of the currently deployed bytecode. Who can replace that bytecode — the program upgrade authority — is a trust boundary that sits above all of them; it has its own page, the program upgrade authority policy.

For the plain-language version of what an ordinary user exposes versus keeps private — on-chain and off — start with What’s public and private; its field reference is the exact per-account inventory.

The domain authority is fully trusted for relayed mail

On-chain, SendMail accepts a message when the signer is the sender named in the email or the active authority of the recipient’s domain — the MX operator’s wallet, for mail relayed in from traditional SMTP. The chain cannot verify that the from address on relayed mail is genuine; it trusts the authority’s signature entirely. Verifying the sender is the operator’s job, off-chain, via SPF/DKIM/DMARC (the sender_auth setting in sithbitd’s configuration).

The consequence of a lax or compromised MX is worse than ordinary spam: because fromboxes are keyed by the from string, a relay that accepts a forged from lets the forger consume a trusted correspondent’s prepaid stamps and arrive at that correspondent’s discounted price. Spoofing through a careless operator is simultaneously stamp theft from the impersonated sender and a bypass of the recipient’s stranger pricing.

What this means in practice:

  • Operators: run sender_auth at spf or stricter on any internet-facing MX. An authority that relays forgeries is spending its own users’ stamps. The strictest setting, sender_auth = "dmarc", now enforces the sender domain’s full published DMARC policy: unaligned mail under p=quarantine is filed into the recipient’s Junk folder and p=reject bounces — a direct mitigation against forged-from relaying, and an all-or-nothing one, since RFC 9989 retired the pct= sampling tag. A "dmarc" MX can additionally emit DMARC aggregate (rua) reports back to the domains it evaluates, giving those senders visibility into forgeries attempted through this relay, and per-failure forensic (ruf) reports for finer-grained visibility — see the forensic-reporting privacy note before enabling it.
  • Recipients: your spam-pricing guarantee is only as strong as your domain operator’s inbound authentication. A mailbox on a well-run domain inherits its rigor; a bare-pubkey mailbox with no domain accepts only sender-signed (wallet-to-wallet) mail, which needs no such trust.
  • The protocol: today there is no on-chain accountability for authorities beyond the delegate’s ability to deactivate a domain. Per-authority accountability (reputation or stake) is a recognized open design question, not current behavior — see Per-authority accountability is a known gap below for how the design handles the gap in the meantime.

Everything above is about the from claim and stamp economics; the same operator is, by default, also trusted with your plaintext. Its IMAP/POP storage holds an unsealed copy of every message in every mailbox on its domain — that’s what lets it answer IMAP/POP requests at all. The one carve-out is Lockbox, client-side end-to-end encryption that keeps a plaintext body from ever reaching that storage — but it ships in only two of the four GUI clients and needs both correspondents running one; see Lockbox narrows the domain-operator boundary below for the exact scope. Nothing about the from-claim trust or the envelope/header visibility above changes when lockbox is in play — it seals the body, not the delivery decision the operator makes around it.

The domain authority’s reach stops at the mail it relays. It has no say over identity resolution: there is one alias namespace, global and domain-blind, and only an alias’s holder can repoint it. A domain cannot mint or capture alice@its-domain — the suffix is parsed off and discarded, so that address resolves to whoever holds the global alice, and to nobody if no one does.

This is what keeps lockbox end-to-end. Sealing follows resolution client-side, so an authority that could redefine which wallet an address names would thereby choose which key a sender seals to — the one operator power that would reach inside a sealed body. It does not have that power. The same holds for incoming mail: a SithBit MX resolves an inbound RCPT through that same global namespace, so an authority decides neither which key a sender seals to nor which wallet receives mail for a local part.

A domain-scoped alias namespace, in which an authority could map any local part under its own suffix, was implemented and then removed for exactly these reasons. It also collapsed the verified-sender mark’s independence: the same party would have defined the local part and, through its own DNS, vouched for it. Organizations issue addresses to staff by reserving global aliases in bulk and transferring them — which leaves the recipient holding the name.

Per-authority accountability is a known gap, mitigated by policy

The protocol bullet above states the shape of the gap plainly; this is the honest accounting of it. A domain authority is trusted entirely for the mail it relays: a malicious or compromised authority can spoof a from address or burn a correspondent’s prepaid stamps for any mailbox under its domain, and today the chain records no per-authority evidence a wronged recipient could use to prove which authority mishandled a given message. The SendMail signature names the authority to the chain, but nothing binds that authority to a verifiable claim a third party could later adjudicate — there is no on-chain reputation, stake, or cryptographic receipt that would let a recipient hold a specific authority to account after the fact.

The near-term answer is documentation and operator policy, not a protocol change. Operating a domain authority is a trusted role by construction, the same way running an organization’s mail server is: a domain owner authorizes an authority precisely because they trust it, and should authorize only authorities they are willing to trust with their users’ relayed mail. The postmaster authorization model — the delegate gates which wallets may ever become an active authority — is the accountability boundary the design leans on today: onboarding is permissioned, so a domain’s authority is a party the postmaster delegate chose to admit, not an anonymous one.

This is a known, accepted trust assumption pre-launch, stated here rather than papered over. Per-authority cryptographic accountability — a mechanism that would let a recipient prove authority misbehavior on-chain (reputation, bonded stake, or signed delivery receipts) — remains a recognized open design question and deferred future work, not current behavior.

MX-to-MX transport: opportunistic TLS is downgradeable, MTA-STS closes it

Between the sending relay and the recipient’s MX, relayed mail crosses the open internet as SMTP. The default posture on that hop is opportunistic TLS (RFC 7435): encrypt when the far end offers STARTTLS, accept whatever certificate it presents, fall back to plaintext when it offers nothing. That defeats a passive tap but not an active man-in-the-middle, who can strip the STARTTLS capability from the greeting or present his own certificate — opportunistic TLS authenticates nobody, so the relay cannot tell the attacker from the MX. (The sealed message body stays sealed regardless; what the transport does or doesn’t protect is the SMTP envelope, the headers, and any plaintext a traditional correspondent sent.)

A recipient domain closes this by publishing an MTA-STS policy (RFC 8461), which the relay honors by default: an enforce-mode policy commits the sender to verified TLS with a policy-matching MX, and any failure defers the mail rather than downgrading — the stripping attack now delays delivery instead of exposing it. Two residuals remain. First contact is trust-on-first-use: an attacker present at the first resolution can suppress policy discovery itself (strip the DNS answer, block the HTTPS fetch), and the relay — having never seen a policy — delivers opportunistically. And the protection a cached policy gives against DNS stripping lasts only as long as the cache entry — the policy’s max_age, and only in the relay process that fetched it — so an attacker who can outlast the cache, or who strips during a relay restart, is back at first contact. Both are inherent to MTA-STS’s DNS-plus-HTTPS trust base rather than implementation gaps.

DANE (RFC 7672, implemented and on by default) has neither residual: a recipient domain that signs its zone with DNSSEC and publishes TLSA records for its MX hosts gets its certificate pins validated on every delivery — there is no first-contact window to poison and no cache to outlast, because the trust statement rides the (validated) DNS answer itself. The relay prefers DANE over MTA-STS wherever both apply, and a bogus or stripped-to-unsigned TLSA answer defers the mail rather than downgrading. The remaining scope limit is the recipient’s: only domains that deploy DNSSEC and publish TLSA records get this protection; everyone else falls back to MTA-STS or opportunistic TLS as above. Either way, TLS-RPT (RFC 8460, implemented behind [spooler.tlsrpt]) gives the targeted domain’s operator visibility into these attacks: a domain publishing a TLSRPT record receives senders’ aggregated TLS outcomes, so a STARTTLS-stripping or certificate-substitution campaign shows up in its reports as failures instead of merely delaying mail in silence.

Forensic (ruf) reporting exposes message content

DMARC failure/forensic reporting is a materially different privacy surface from aggregate reporting, and it is off by default for that reason. An aggregate (rua) report is a statistical roll-up — counts of pass/fail by source IP, no message content. A forensic (ruf) report is a copy of the offending message itself, sent to whoever the sender domain names in its ruf= DMARC tag — a third party you do not control. Three properties bound that exposure:

  • Headers-only by default. SithBit follows the RFC 9991 §7.1 content-minimization guidance: a report attaches only the offending message’s headers (text/rfc822-headers), not its body. Envelope and header metadata still leave your server, but the message content does not.
  • include_body = true is an explicit operator opt-in that leaks the full message. It attaches the complete message/rfc822 — subject, body, and all — to every forensic report. Enable it only when you need full-body forensics and trust every domain whose mail you evaluate, because you are handing that domain’s ruf= operator the entire failing message.
  • The external-destination gate limits who can receive reports. Before sending to any ruf= address outside the policy domain, the server enforces the RFC 9991 §5 authorization check (the target must publish a <policy-domain>._report._dmarc.<target> record). An attacker cannot point a victim domain’s ruf= at their own collector to harvest that domain’s inbound mail unless the target domain has itself opted in.

The net guidance: leaving [spooler.dmarc_ruf] off sends no forensic reports at all; enabling it with the headers-only default is a modest, metadata-level exposure to authorized report destinations; turning on include_body is a deliberate decision to share full message content with third parties and should be made with that squarely in view.

The marketplace sells protocol authority; DNS remains separately owned

A MailDomain’s binding to its real-world DNS name is checked once, at mint, whichever of the three authorization routes minted it: the delegate signing domain create by hand, the domain-sithbit service verifying the _solana.authority.<domain> TXT record before co-signing that same instruction, or the permissionless domain authorize path, which walks a DNSSEC proof on-chain. From that moment on no instruction ever consults DNS again: domain transfer and the open marketplace repoint the authority field freely, as a free-floating on-chain asset.

A marketplace sale therefore conveys exactly the on-chain half of a domain — the SendMail injection right for its mailboxes and the 10% operator share of DeleteMail settlement — while the DNS zone, the MX records, the hosting, and the DKIM keys stay with whoever controls DNS. What buying a domain does — and does not — buy spells that out for buyers; this section covers what can go wrong once the two halves sit in different hands.

An existing domain locks out the new DNS owner

AuthorizeDomainByProof only mints: it requires the target domain account to be uninitialized, so once a domain exists on-chain — by any route, active or deactivated — a fresh, entirely valid DNSSEC proof for the same name is refused. Buy the DNS name at the registrar after the on-chain domain was minted and you cannot prove your way in; the recorded authority — possibly a marketplace buyer several sales removed from any DNS check — keeps the injection right and the settlement share.

Every way out today runs through the delegate: domain transfer repoints the authority instantly, and domain close frees the account so the new zone owner can re-prove — but if what the new owner needs first is to stop the wrong key injecting mail, deactivation is bound by the 7-day timelock below. The divergence persists until the delegate acts. The asymmetry is real: the mint path is permissionless, the re-mint path is not.

A captured proof replays within its RRSIG window

The DNSSEC witness is public by construction — it is staged on-chain in chunks, so anyone can copy it out of transaction history and re-stage it under their own payer (the staging buffer is keyed on the payer and the domain, not on any privileged key). The verifier checks that each signature’s [inception, expiration] window contains the cluster clock and nothing more; the program imposes no freshness ceiling of its own. A captured proof therefore stays replayable until the earliest RRSIG expiration in its chain — a bound set by each zone’s re-signing policy, commonly days to a couple of weeks, with no revocation before it.

The replay bites exactly where the lockout above does not: when the domain account is free (never minted, or just closed by the delegate to resolve a lockout) at a moment the DNS name has just changed hands. The departing zone owner’s still-valid proof can re-mint the domain to the old TXT authority even though the live zone now publishes a different key — and the lockout above then makes the wrong binding stick.

Sold authority drifts from where mail actually routes

Nothing obliges a marketplace buyer to operate the domain, and the chain cannot see whether they do. While protocol authority and DNS point at different parties:

  • Recipients on the domain stop receiving relayed (SMTP-in) mail: internet mail still follows the DNS MX to the old operator’s server, whose key can no longer sign SendMail. What they can receive is relayed mail injected by the new authority — which, per the first section above, is trusted entirely for the from claim, frombox stamps and discounted correspondent pricing included. Wallet-to-wallet mail between bare pubkeys is untouched, as always.
  • The displaced operator still holds the zone, the MX, and the DKIM keys — and receives internet mail it can no longer deliver on-chain.
  • The new authority collects the 10% operator share of every settlement on the domain’s mailboxes: income with no operating duty attached.

An honest sale coordinates the DNS name and the hosting hand-over off-chain around the on-chain purchase; the chain neither requires nor checks that this happened. domain get shows the on-chain state — only the DNS zone shows who controls the name.

What the marketplace guards close — and what stays open

The marketplace’s guards close the state races inside the market, for domains and their alias twins alike: a buy re-checks that the listing’s recorded seller is still the name’s current authority (a stale listing that predates a transfer can never sell the new owner’s name at the old owner’s price); domain buy is refused while a deactivation timelock is in flight; and transfer and close are refused while a listing is open, so no stale holder is left positioned to collect sale proceeds. See the listing error table for the exact refusals.

What those guards deliberately do not close is the divergence itself. Protocol authority and DNS ownership are two different assets, and the marketplace sells only one of them — an economic and social fact of the design, not a bug, demanding the same off-chain diligence any domain-name purchase always has.

The recorded direction — a design decision, not current behavior — is a reclaim-by-proof path: a fresh DNSSEC proof by the current zone owner, submitted against an existing domain account, opening a timelocked, contestable reclaim of the on-chain authority. That would make DNS the root of trust for a domain’s whole life rather than only at mint: marketplace-bought authority becomes revocable by whoever actually controls the zone, the lockout gains a permissionless resolution, and the replay window is defused by contestability — a fresher proof beats a replayed one. The postmaster-delegation rework it was sequenced behind has landed; reclaim-by-proof itself has not — until it does, everything above is the operative behavior.

Alias auctions: escrow custody and sniping

An alias auction moves real value through the program between mutually distrustful parties — a seller, a shifting set of bidders, and whoever eventually cranks settlement — so it is worth being explicit about where custody sits and what each party can and cannot do to the others.

  • The program holds the escrow, not any counterparty. A bid’s lamports live in the auction’s on-chain escrow account, owned by the alias program, from the moment the bid lands until it is refunded (on outbid) or split (at settlement). No seller, bidder, operator, or cranker can withdraw or redirect them; every movement is computed on-chain from the recorded high bid — the refund is the exact outbid amount, and the settlement split is the postoffice’s recorded operator_share_bps (default OPERATOR_SHARE_BPS = 90/10, delegate-tunable only within its on-chain 20% cap). A bidder trusts the deployed bytecode, not the seller.
  • Anti-snipe blunts last-second bid timing. A bid inside the final window pushes the deadline out (see the auction timing rules), so winning by landing an unbeatable bid one block before close no longer works — any late bid re-opens a full window for others to respond. It does not stop a determined bidder from bidding, only removes the timing advantage; and the created_at + 7-day hard cap bounds how long the extensions can run, so the mechanism cannot be turned into an indefinite-lock griefing vector.
  • Settlement is crankable by anyone, and deterministic. After the deadline, settle-auction can be signed by the seller, the winner, or an unrelated third party, and the outcome is identical whoever cranks it: the alias repoints at the recorded high bidder and the escrow splits 90/10. A hostile or simply absent cranker cannot alter the result or capture funds — the worst they can do is not crank, which delays settlement until someone else does (both the winner, who reclaims the escrow rent, and the seller, who collects 90%, are motivated to). There is no trusted sequencer or auctioneer in the loop.
  • Griefing surface is priced, not eliminated. Spam bidding is fenced by three costs stacked together: a bid must clear the reserve, then clear the standing high bid by the minimum increment (max(5%, 1,000,000 lamports)), and the first bidder additionally fronts the escrow account’s rent — which, per the escrow-rent flow, is reclaimed by the eventual winner, so a first-bidder-then-loser forfeits it. Those costs make throwaway bids expensive without making legitimate ones onerous. The residual, accepted surfaces: a bidder’s own funds are locked in escrow until they are outbid or the auction settles (their choice to bid, their capital at stake, refunded in full if outbid); and the seller is bound once the first bid lands — the strict no-cancel commitment that protects bidders is, symmetrically, a commitment the seller cannot escape. A bidless auction locks nothing and costs only the seller’s own listing rent.

A re-pointed alias cannot redirect a mail login

Settlement being crankable by anyone has a second edge: an alias can change hands at a moment of someone else’s choosing — including the moment you are signing in to collect your mail with it. A transfer or a marketplace sale has the same shape. Nothing in the protocol makes a re-point wait for a quiet time, and nothing should: a name whose new holder must ask permission to take possession is not really theirs.

Mail logins are fenced against it instead. A login name is resolved to a wallet exactly once per session, on the way to the stored password, and the IMAP or POP session that follows opens the account that one resolution named. There is no second lookup between “this password is correct” and “here is the mailbox” for a re-point to land in, so:

  • A re-point landing mid-login changes nothing about that login. The password checked and the mailbox opened belong to the same account, whichever way the name moved in between: the old holder’s password does not become a key to the new holder’s mail, and the new holder’s does not reach back to the old holder’s.
  • A session does not follow the name. It stays on the account it authenticated, so losing the alias mid-session does not cut the session off, and gaining one does not extend it to the new mail. The next login resolves the name afresh — by then, the new holder’s account, whose password the old holder does not have.
  • An unresolvable name is refused exactly as a wrong password is, with no distinguishing reply, so the login prompt is not an oracle for which aliases exist.

The wallet-signature and client-certificate logins sit outside all of this: their username is the wallet address and their credential is a proof over it, so nothing is resolved and there is nothing to re-point.

The postoffice admin keys: a hot delegate, a hidden owner

The postoffice’s admin surface used to be a singleton — one postmaster key holding every power. Since the delegation cutover it is two trust boundaries, deliberately unequal:

  • The standing delegate — a wallet recorded on the postoffice, holding the operational powers: authorize and deactivate domains, tune the capped fees, publish the root KSK, waive bulk-alias fees. It is a hot key by design (the domain-sithbit self-service flow signs with it online).
  • The postmaster (owner) — not a pubkey on-chain at all, but a Merkle commitment root over a hidden key set produced in an offline key ceremony. Only the ownership operations — sweeping postoffice revenue, rotating the delegate, installing a successor commitment — spend one of those hidden keys, and each use rotates the whole set.

Domain ownership itself is still proven off-chain: domain-sithbit checks a DNS TXT record and signs with the delegate key — so the verifying agent and the operational key remain, today, single points of trust for onboarding.

The on-chain design bounds the worst outcomes: the fees are capped (MAX_POSTOFFICE_STAMP_FEE_LAMPORTS and kin), postage settles directly to recipients and operators without passing through the postoffice, and rent always returns to whoever paid it. What a compromised or coerced delegate can do is concrete but operational-only:

  • Deactivate any domain — halting relayed (SMTP-in) mail for every mailbox under it until the domain is reactivated. Deactivation is deliberately a reversible toggle rather than a close, so the damage is an outage, not a loss. It is also rate-limited by a two-step, 7-day timelock (see Deactivate a domain): the key can request a deactivation but cannot complete it for a week, and the request is cancelable in the meantime — so a compromised key can no longer take the relayed network down at once, only start a delayed, vetoable countdown. Reactivation stays instant, keeping the recovery direction fast.
  • Refuse to authorize new domains, freezing onboarding.
  • Retune the protocol fees — up to their hardcoded caps, no further.

What it can never do: move a lamport out of the postoffice, change the commitment root, or make itself unremovable — the sweep and rotation powers answer only to a ceremony-key proof, and a single delegate by the owner revokes a stolen delegate entirely. The delegate’s blast radius is an outage and a fee tweak, not a theft.

Wallet-to-wallet mail between bare pubkeys needs no domain and keeps working regardless, so even a delegate compromise never touches the wallet-to-wallet substrate — only the relayed-mail onboarding and outage layers.

Custody now has two distinct jobs: keep the ceremony seeds offline and split across vaults (they are the ownership), and treat the delegate as a rotate-on-schedule service credential. Both are covered in the postmaster key custody runbook.

The deactivation timelock

Domain deactivation is intentionally slow. The delegate issues a request that starts a 7-day clock and leaves the domain active; only after the clock elapses can a finalize actually deactivate it, and a cancel aborts the request at any point before then. The delay is a notice-and-veto window against a compromised or coerced delegate: it converts an instant, network-wide mail halt into a delayed, cancelable one. See Deactivate a domain for the flow and the CLI commands.

The mailbox close timelock prices identity-cycling

Mailbox closure is timelocked for a different reason than domain deactivation above: not a compromised authority, but a spammer’s unit economics. When CloseMailbox refunded rent in a single instruction, a sender who had burned one wallet’s reputation could close, reclaim, and recreate at effectively zero cost — the identity was disposable, which is exactly the property a postage-priced system must deny.

Closing now takes a request that starts a 7-day clock and refunds nothing, and a finalize after it elapses that returns both the mailbox’s rent and the transient pending-close account’s; a cancel aborts the request meanwhile. The one-step instruction is refused on-chain with error 94, InstantCloseDisabled. The effect on an attacker is capital stuck for a week per burned identity, plus a week-long window in which operators can see a mailbox announce its own exit. The effect on an honest owner is a delay on an action they take approximately never — the rent is returned in full, so the cost is time, not money. See Close a mailbox.

The deliberate asymmetry: CloseKey was left instant. It is the revocation path for a compromised delegated encryption key, and a timelocked revocation would leave MX servers sealing new mail to a key the attacker holds for another seven days. Timelocking a close helps the defender; timelocking a revocation helps the attacker.

Message metadata is hashed, not hidden

No SithBit instruction or account carries an address string. A message account stores the sender wallet, a blake3 hash of the normalized from address (the frombox seed), the timestamp, and the IPFS CID; the recipient appears only as the wallet the account’s PDA seeds on, and the frombox instructions likewise carry the hash. The human-readable From:/To: headers exist solely inside the sealed body, readable only by the recipient’s key.

What a chain observer still learns — and should be treated as public:

  • wallet-level flow: which wallet received mail, when, and which sender wallet paid for it (accounts, signatures, and timestamps are inherent to the chain, and history outlives DeleteMail);
  • hash linkage: the same from address always hashes to the same value, so an observer can correlate “this sender identity again” and can confirm a guess of a known address by hashing it — the hash hides the string, it is not resistant to a dictionary of candidate addresses;
  • the CID of the sealed body (fetching it yields ciphertext).

What the observer no longer gets is the address book itself: reading who-mails-whom as alice@corp.com → bob@example.org now requires already knowing both strings.

The discovery keyserver is a public enumeration surface

The account API’s cert keyserver (GET /v1/chain/cert?email=…) is intentionally public and unauthenticated, on the same reasoning as a PGP keyserver or WKD: every field it returns — the resolved wallet and its published encryption key — is already readable on-chain by anyone. It leaks nothing a chain observer could not already fetch.

What it does add is convenience, and convenience cuts both ways. An unauthenticated HTTP endpoint that turns an address into “does this recipient exist, and what key seals to them” makes bulk enumeration cheap: an attacker can probe a dictionary of candidate local-parts against a domain — one GET per guess — to learn which addresses are live, without an RPC node or any on-chain trace. This is the same order of exposure as the hash-linkage dictionary attack above — the address strings were never secret — but the keyserver lowers the effort from “scan and correlate the chain” to a plain web request. An operator who considers inbox-existence itself sensitive should rate-limit or otherwise front the route; the protocol treats the underlying data as public by design.

Frombox custody favors the recipient

Two behaviors follow from the frombox being the recipient’s account (see Closing accounts):

  • CloseFrombox returns the entire balance — rent and any unused prepaid stamps — to the recipient, not to whoever funded them. This is the recipient’s remediation against a sender who stockpiled cheap stamps before a price hike (raising the price never revalues stamps already bought; closing the frombox seizes them).
  • A sender who prepaid against their own wallet address can withdraw the unspent remainder with ReclaimFromboxStamps, which zeroes the stamp count and returns the balance above rent to that sender. The frombox PDA derives from the hash of the signer’s address bytes, so reproducing the derivation is the authorization: no stranger can reach a victim’s frombox, and no separate authority field exists to get wrong. This narrows — but does not close — the custody gap above. Two limits are deliberate. A frombox keyed on an email string hashes text no wallet key can reproduce, so it has no sender-side withdrawal and stays recipient-managed. And the recipient’s CloseFrombox still sweeps any residual left behind, so reclaiming is a race the sender can enter, not a claim that outranks the owner. Funding someone else’s frombox on their behalf remains a gift with no refund path — the derivation names the payer only when the payer is the sender. Fund a frombox only as generously as you trust its owner.
  • Anyone can transfer extra lamports into a frombox PDA directly. The per-send value moved onto a message is (balance − rent) / stamps, so a topped-up frombox inflates each remaining stamp’s settlement value — at the topper’s expense, to the recipient’s (and operator’s) benefit. Not an attack on anyone else’s funds; just don’t send lamports to a frombox except through AddStamps.

Lockbox narrows the domain-operator boundary, but only for two of the four GUI clients

Lockbox is the one mechanism anywhere in this document that lets a message escape the domain-operator boundary above for confidentiality. Before treating “I run a SithBit client” as “my mail is end-to-end encrypted,” three scope limits are worth being explicit about:

  • Only the Thunderbird extension and the Outlook add-in carry it. All four GUI clients run the same wasm-signed shared core for wallet and account operations, but the webmail app and Chrome extension do not seal a message client-side before it leaves your device — mail sent or read through them stays inside the full plaintext-operator boundary, identical to the CLI’s own IMAP/POP/SMTP path. See GUI clients for the comparison.
  • Both correspondents need it, on every message. Lockbox is all-or-nothing per recipient — if either side lacks the plugin, or the recipient can’t be resolved to a wallet, the message goes as ordinary plaintext rather than a broken partial seal. An operator whose users run a mix of clients still holds plaintext for every conversation that touches a non-lockbox side.
  • Metadata stays visible. Even between two lockbox-capable clients, the SMTP envelope and headers — To, From, Subject, routing — travel unsealed, visible to every relay in the path, including the terminating domain operator, no matter what the sealed payload carries. Lockbox closes the content-reading gap, not the delivery-metadata gap; it does not make the operator’s role in delivery invisible.

A related but distinct capability is easy to conflate with lockbox: the webmail app and Chrome extension can unseal a message’s on-chain sealed body client-side, in wasm. That is not lockbox, and it does not close the operator’s plaintext copy. The on-chain ciphertext is computed server-side, at delivery time, from the same plaintext the operator already holds for IMAP/POP — see why lockbox matters. Wasm-side decryption protects the public IPFS copy from the rest of the internet; it says nothing about the copy sitting on the operator’s own disk. Only lockbox prevents that plaintext copy from existing in the first place.

Lockbox mail: the recoverable reading key

Lockbox mail can derive its X25519 reading key from the wallet — the wallet signs one fixed, domain-separated message and a KDF turns that deterministic signature into the reading key. This is what makes the key recoverable and multi-device (any device with the wallet reproduces it, with nothing to back up), but it moves the trust boundary onto that one signature: anyone who can induce the wallet to sign this exact message can reconstruct the reading key and read all mail sealed to it. A malicious dApp that shows a lookalike signing prompt is the realistic attack.

Mitigations and their limits:

  • Domain separation. The signed message carries a versioned SithBit prefix, so the signature can’t be harvested from an unrelated signing request that happens to reuse the same bytes. It does not stop a prompt that deliberately signs the SithBit message.
  • Approve only in the plugin. The reading-key signature should be requested only by the SithBit client; treat any other prompt asking to sign a “SithBit reading key” message as hostile.
  • Forward secrecy is opt-out, not default. A user who values forward secrecy over recoverability keeps a randomly generated delegated key instead (sithbit mailbox key), which no signature can reconstruct — at the cost of the lost-key / multi-device pain the derived key removes.

Rotation works the same as any published key: publish a new one and re-seal future mail; already-sent ciphertext sealed to the old key stays readable only by the old key.

A deployment that turns on large-attachment IPFS offload delivers oversized attachments as a link instead of as bytes: the part is encrypted under its own freshly generated symmetric key, the ciphertext is pinned to IPFS, and the message carries …/ipfs/<cid>#<key> in its place. The key is in the URL’s #fragment.

Whoever holds that whole URL can read the attachment. There is no wallet binding, no per-recipient wrapping, no expiry, and no revocation — possession of the link is the authorization, the same way a “secret link” file share works. This is weaker than the sealed-box path the message body takes, and the difference is not a detail: a sealed body can only be opened by the recipient’s wallet key (or their published reading key), so the pinned ciphertext is useless to everyone else on earth, including the operator who pinned it. An offloaded attachment is protected only by a string that travels in an email. Turning offload on moves large attachments from wallet-bound confidentiality to bearer confidentiality; it is off by default precisely because that is an operator’s decision to make, not a default to inherit.

content_id widens which parts make that move, and is a second decision of the same kind. On the default, inline parts — the ones an HTML body renders as cid:… — never leave the message at all, whatever their size. Setting "orphaned" admits the ones nothing references, which are ordinary attachments their sending client happened to label. Setting "all" admits genuinely inline parts too, and those are body content: an image a sender pasted into the message rather than clipped to it, often exactly the thing they would not have thought of as an attachment. Under "all" such an image becomes a bearer link like any other offloaded file, and the recipient sees a link where the picture used to be. Weigh it as its own choice; arming a size rule does not arm it.

aggregate_bytes widens how many parts make that move, and changes no posture. Every part it selects becomes the same bearer link, under the same content_id rules — a part held inline stays held, budget or not. What it changes is the arithmetic: a message can now turn several ordinary, individually-modest attachments into links at once, so the count of live bearer credentials a single message carries rises with the budget’s tightness. Nothing about any one link is weaker.

Where such a link leaks in practice:

  • Forwarding. Forwarding a message is the ordinary way an attachment travels, and here it hands the recipient’s forwardee — and everyone downstream of them — permanent read access. The forward does not carry a copy of the file that could be stripped; it carries the credential.
  • The message’s own headers. The placeholder part repeats the link in X-SithBit-Offload-Url, so it is written down twice in every relay hop, spam filter, journal archive, and backup the message passes through. Anything that keeps mail keeps the link, for as long as it keeps it.
  • Browsers. Clicking through puts the URL — fragment included — in history, in autocomplete, in a synced profile, and potentially in a bookmark. Fragments are not sent in Referer, so an onward navigation does not leak the key that way, but the local copies are real.
  • Clients and middleboxes that touch URLs. Link previewers, “safe link” rewriters, archivers, and anything that prefetches URLs found in mail will visit the gateway; a rewriter that rebuilds the URL may also copy the fragment into a system you did not choose.
  • Paste. The link is short, printable, and looks like a normal download URL. It gets pasted into chats and tickets like one.

What the design does buy, and it is worth being exact about the limits:

  • The key never reaches the gateway. A #fragment is the one part of a URL a client keeps to itself, so the fetch that retrieves the ciphertext carries no key — no gateway, proxy, or CDN access log on that path can contain one. Decryption happens in the recipient’s client.
  • The gateway cannot decrypt. It serves opaque SBa-envelope bytes and has no decrypt path at all, so an operator’s gateway logs and an attacker who dumps the gateway’s storage both learn ciphertext.
  • Each attachment gets its own key, generated fresh and never reused across parts or messages, so a disclosed link discloses exactly one attachment — never a second one, never the message body, and never anything belonging to another recipient.
  • What is pinned is ciphertext, so an IPFS peer that fetches or replicates the blocks learns nothing from them. That is strictly better than pinning a large attachment in the clear, which is the alternative this feature replaces — but it is not equivalent to sealing to a wallet.

Expunge reclaims a local-only attachment, and never a relayed one. Settlement and expunge release the message’s pin, by a name the store can reconstruct. An offload pin is reclaimed differently, and the difference bounds what deleting mail actually buys you:

  • It is released only by the last reference. One attachment is pinned once per submission and referenced by every copy that carries its link — each local recipient, the sender’s Sent copy, every IMAP COPY. Each expunge drops one reference; the last one standing unpins. So a recipient deleting a message does not make the attachment unfetchable for the others, and should not be told that it does.
  • A relayed submission’s pins are never released at all. If any recipient was remote, the pins are flagged at delivery and no local expunge ever touches them — not even the last local copy’s. The link is already on servers this deployment cannot see, and unpinning would break it there. For those attachments there is no automatic cleanup: they stay pinned, and stay readable by anyone holding the link, until someone removes them out-of-band at the pinning provider, where every offloaded object is named under the offload/ prefix. That is a deliberate trade for the relayed case, not an unfinished worker.

Neither release path reaches a link that has already been forwarded, logged, or pasted. Unpinning removes the deployment’s copy of the ciphertext; it revokes nothing, and it cannot help if another pinning peer replicated the blocks. Treat an offloaded attachment as published for as long as anyone holds its link — and a relayed one as published for good.

The wallet mail password is a bearer credential

The wallet-signature login is deliberately a static password: a signature over a fixed 61-byte challenge — a constant prefix, your wallet’s public key, and your account’s auth epoch — which is what lets a stock mail client save one value and reuse it forever. Everything below follows from that shape rather than from any defect in it.

  • It replays. The signature is bound to your wallet and your epoch, so nobody can present it as a different wallet — but anyone who captures it can present it as you, for as long as the epoch stands. There is no nonce and no expiry, because SASL PLAIN carries no challenge to put one in. Rotating the epoch (POST /v1/account/auth-epoch) is the revocation lever, and TLS is what keeps the value off the wire in the first place.
  • It works across SMTP, IMAP, and POP alike. One operator runs all three, so this is by design rather than an escalation: a credential that opens the mailbox opens the mailbox.
  • Any site can ask your wallet to mint one. The challenge is a well-known constant, so a dapp that gets you to sign an innocuous-looking message can obtain a working mail password without ever touching this deployment. The bytes are domain-separated from Solana transactions, so such a signature moves no funds — but it does read and send your mail, which is enough to run a convincing phishing campaign from your own address. Treat a signature request you did not initiate the way you would treat a transaction you did not initiate.
  • A signature verifies at epoch 0 for a wallet this deployment has never seen, since there is no account row to read an epoch from. Whether such a login may create storage is gated separately (see the chain-gated session-open switch); the credential check itself makes no account-existence claim.

The trade this buys is compatibility with every mail client ever written, and it is the reason the reading key exists to keep bodies sealed even from a login that succeeds. Rotate the epoch whenever a password has been pasted somewhere you would not paste it again.

A stored mail password keeps your mail readable to the operator

Wallet-signature login proves a key per connection and stores nothing; with a reading key published, delivered mail is sealed at rest and the operator holds ciphertext. A stored mail password — the credential that makes CRAM-MD5 and APOP work — reverses both halves:

  • The secret is plaintext-recoverable by the server. It is sealed under the deployment’s credential key rather than hashed, because the challenge-response mechanisms need the original bytes to compute their digest. Anyone who reaches the store and that key reads the password.
  • Every message delivered to an account holding one is stored unsealed. This is not an oversight: a CRAM-MD5 or APOP session proves possession of a shared secret and never carries a key that could unwrap a sealed body, so a sealed copy would simply be unreadable to the client that asked for it. The per-account rule — stored password ⇒ plaintext — is the only gate, and it is deliberate.

So the exposure of a store, blob bucket, or credential-key compromise is not uniform across accounts: wallet-signature accounts lose metadata, stored-password accounts lose message bodies. Mail bodies are a high-value target here specifically — they carry the recovery and confirmation traffic a wallet-phishing campaign wants.

Three levers, in order of preference:

  • Do not set one. Wallet-signature auth is the default state, and clearing an existing password (DELETE /v1/account/password) returns the account to it — though mail already delivered stays as it was stored.
  • enable_stored_passwords = false retires the mode deployment-wide: account-api refuses to store new passwords, and the SMTP and POP listeners stop advertising CRAM-MD5. Existing stored secrets keep verifying until each is cleared, so this closes the door on new exposure rather than undoing old.
  • [account_keys] seals these accounts after all, under a key derived per account from one operator-held root. It narrows the second bullet of the exposure above — the store and blob bucket stop holding plaintext bodies — but not the first: the root sits in the daemon’s memory, so it moves the line from anyone who steals the storage to anyone who compromises the running server, which is the boundary the closing paragraph below describes. Off by default; existing plaintext bodies are not migrated.

What it does not do is protect the bodies from the operator in the first instance. Sealing happens at spool time on a chain-connected deployment; a hostile or compromised operator sees plaintext as it arrives, whatever the account’s credential. That boundary is Lockbox’s subject, not this one.

The gateway’s fee payer is what every chain write spends

The stamp economics price the sender: a stamp is burned from the sender’s frombox on every send. The transaction fee is not the sender’smail-grpc signs each SendMail with the operator’s own fee-paying wallet. So every accepted local message costs the operator a small amount of SOL, whatever the recipient priced their postage at, and a recipient who sets free or near-free postage moves that cost entirely onto the operator.

Three things bound the exposure, none of which is a per-sender budget:

  • Postage is checked before acceptance. A recipient with no stamps refuses the recipient at RCPT time, so the common flood never reaches the chain queue at all.
  • fee_payer_floor_lamports stops the spend at a floor rather than at zero, and turns a drained wallet into a /readyz alert.
  • sithbit.chain.sendmail meters every submission by outcome, so the volume is visible before the balance moves.

Two gaps stay open deliberately:

  • postmaster_wallet exempts postmaster@<local domain> from the postage check (RFC 5321 requires the address to be reachable), yet those messages still enqueue a chain job. An unauthenticated sender can therefore drive fee-payer spend at whatever rate the connection limits and per-connection message cap allow. Keep the exemption pointed at a wallet you watch, and treat a sithbit.chain.sendmail rate that outpaces real mail as the signal it is.
  • Bound it with [spooler.chain_budget], which is off by default. max_per_window caps on-chain publications per window across every sender and is what actually bounds spend on this path; over-budget mail is still accepted and readable, with only its publication paced. Set it whenever postmaster_wallet is set — an unbudgeted exemption is the wallet-drain scenario above, unchanged.
  • A per-sender budget alone does not bound spend, by construction. The envelope sender on the exempted path is unauthenticated and free to vary, so max_per_sender_per_window is evaded by changing MAIL FROM. It is fairness between senders; the shared budget is the control.
  • The budget paces, it does not shed. A sustained flood grows the chain job queue rather than being refused, which is the deliberate trade: mail is never destroyed to protect the wallet. Queue depth is the signal (sithbit.queue.depth).
  • The window is fixed, and the counter fails open. A burst straddling a window boundary can reach up to twice the budget, and a store that cannot answer a charge publishes unpaced rather than stalling mail. The budget bounds sustained spend; it is not a precise meter.

What is enforced on-chain

For contrast, the guarantees that need no trust in any operator: PDA ownership and derivation checks gate every lamport move; only the recipient can reprice a frombox; only the sender or recipient can settle a message; stamp arithmetic is overflow-checked (a u64::MAX price is an effective per-sender block); the stamp fee is capped; and every account class has a close path that returns rent to its recorded payer.