Security
What Books can do, and what it cannot.
Ezra is the bookkeeper. Books is the software it works in, and this page is about the software: you are being asked to connect a bank account to something you have not used before, and the useful version of this page is not a list of adjectives. It is what is enforced, how it is checked, and where a control is weaker than it sounds.
It reads. That is the whole of its access.
The strongest security property Books has is a thing it was never built to do.
Books asks a bank connection for one product, transactions, and reads the balances that come with it. Nothing else is requested: not payment initiation, not account and routing numbers, not identity. It holds no payment credentials, no card numbers and no account numbers you have not shown it yourself on a statement, and there is no code path in the product that initiates a payment, a transfer or a transaction. If somebody took a Books account over, they could read your bookkeeping. They could not move a dollar, because the product has no way to.
A connection is also narrowed at the point you make it: only checking, savings, money market and credit card accounts are offered, because those are the ones Books can actually keep books from. You can disconnect an account at any time.
Between accounts
The interesting failure in a product like this is not somebody breaking in. It is one customer seeing another customer, quietly, because of an identifier that was trusted.
- Every row belongs to one account
- Not by convention. Every query is written through a scope that names the calling account’s id, and a query that forgets it raises rather than returning anything. Two accounts can hold the same books under the same name with identical transactions and never see each other.
- It was attacked, not assumed
- A test sets up two accounts with byte-identical books and drives every route in the API as one of them, substituting the other’s identifiers everywhere an identifier appears, while a trace watches the database for any statement that touches a scoped table without the caller’s account id. Nothing crossed.
- One deliberate exception, and it is narrow
- A single module reads across accounts, for running the service: who signed up, when, how far they got, and counts. It may not read a transaction, an amount, a balance, a statement, a document, a chat, a note, a write-off or a token, no query in it uses a wildcard, and a source scan fails the build if any other module grows a cross-account query. Every look is written to an audit log before the answer is computed, and a look that cannot be recorded is refused rather than served.
Where your data sits
And what shape it is in when it sits there.
- Bank tokens
- Encrypted at rest with a key derived from the service secret, one row per account. They are never sent to the browser and never appear in a log.
- Sessions
- The cookie is httpOnly and same-site, and what is stored on the server is a hash of it, never the value. Sign-in is rate limited, and the limiter keys on a digest rather than on the address itself.
- Your statements
- Kept under a directory belonging to your account alone, named by the hash of their contents. Nothing writes outside that directory, and a books name can never walk up out of it.
- Backups
- One encrypted archive holding the database and every uploaded statement together, because a ledger restored beside missing files is a backup of half the product. AES-256-GCM in framed chunks under a key unique to that archive, so a truncated, reordered or spliced archive fails to decrypt rather than restoring quietly wrong data. The database is a consistent snapshot, not a file copy. The restore has been run.
- What reaches the model
- When you ask Ezra a question, the slice of your ledger that question needs is sent, as compact data. The ledger itself stays where it is. The standing instructions name your sets of books without their account digits.
In the browser
Checked against real responses from a real build, not against the strings somebody typed into a config file.
Every response carries HSTS, a referrer policy, no-sniff, and a frame denial. Books is never embedded anywhere, so framing is refused outright. The permissions policy denies every browser capability Books does not use. The content policy is enforcing and nonce based: an inline script runs only if it carries the nonce minted for that one response, an inline event handler attribute is refused outright, images and fonts are permitted only from this origin, and network connections only to this origin and to the bank connection provider. The one external script Books loads is the bank connection widget, from that provider’s own content network, and it is named in the policy rather than covered by a wildcard.
A script walks every page of a real build, twice, and fails if any page carries the wrong header value, more than one content policy, an inline script without the nonce the header names, or a page that a shared cache would be allowed to keep.
What is deliberately not written down
The leak in a product like this is usually not a breach. It is a log.
An authorization code never reaches the access log, because the logger redacts by parameter name rather than by pattern. An unhandled error answers one sentence rather than the exception. A failure at the bank connection provider or the model provider answers in Books’s own words rather than passing along the vendor’s response. The health endpoint reports booleans and counts, never a path.
What this page is not claiming
The parts a careful reader would go looking for.
Books holds no security certification and has had no third-party audit. Everything above is a control in the codebase with a test or a verification script behind it, which is a different and lesser thing than an external attestation, and it is described that way on purpose.
You can evaluate it without connecting anything.
Create an account and upload a single statement. No bank connection, no card, and you will have seen what Ezra does with a real document before deciding whether to give it a live one.
Statement PDFs are read from American Express, Apple Card, Citi and Wells Fargo. From any other bank, the CSV or Numbers activity export works, and a PDF Books cannot read is kept and says so rather than being booked.