Safety and privacy¶
What the worker can and cannot do to your files, and what leaves your machine.
What leaves your machine¶
| You choose | What is sent, and to whom |
|---|---|
| A local model (Ollama, LM Studio on this machine) | Nothing |
Your Claude subscription (claude-cli) |
The text of each source goes to Anthropic, through the official claude tool |
Your ChatGPT plan (codex-cli) |
The text of each source goes to OpenAI through the official codex tool. Email is never sent to it |
An API key (anthropic, openai-compatible) |
The text of each source goes to that provider |
Ollama or LM Studio on another machine (base_url), or an Ollama -cloud model |
The text goes to that host. It is not "local" even though the program is |
In plain words: a model sends text out when someone other than you runs it. Every model in the worker carries that flag (it is what the privacy rules read). Local Ollama and local LM Studio do not send text out; the API providers, claude-cli and codex-cli do; a fallback follows the model that sees the text first, so a local main model with a cloud fallback counts as sending out. An Ollama or LM Studio model whose base_url is not this machine (anything but localhost, 127.x or ::1) counts as sending text out: sb doctor shows a WARN server <task> line for each such model, and sb init --base-url or the installer's server-address question prints the same warning.
Nothing else about your notes is sent anywhere: there is no telemetry and no account. The worker fetches the web pages you ask for, and talks to your mail server (Gmail unless [email] imap_host says otherwise) only if you turn mail capture on.
There is one network call that is not about your sources: the update check. Once a day, when you run a command in a terminal, esbi-cli sends one anonymous HTTPS GET to the GitHub releases API to see whether a newer version exists. It carries no vault data, no config and no identifier (GitHub sees your IP address, as any website does). Its answer is read as untrusted text and only a version tag and a project link in the expected shape are used. The nightly run never makes it, nothing is installed without your yes (sb update asks), and you can turn it off with [update] check = false or ESBI_NO_UPDATE_CHECK=1. See Update esbi-cli.
Email stays local¶
Email is private by design. Email is never shown to a model that sends text out. This is enforced in four places:
- Writing. An email is written only by
[llm.private], which must be a model on this machine. Without one, an email is refused while a cloud model is set (sb doctorwarns beforehand). A remote[llm.private]is refused too. - Context. When a cloud model writes any other source, email pages, and concept pages built only from email, are left out of the existing-pages list it sees. A page that email helped write is offered by title only, without its one-line summary.
- Questions.
sb askwith a cloud model leaves out email pages and the## From [[email]]sections of shared pages (## Desdein a Spanish vault: both are recognised), and any saved answer that used an email page. When a page has a section from email, its consolidated## Summary(## Resumen) is left out too: it was written from every section, email included, so a cloud model is never shown it. - Consolidating. A concept or entity page that email helped write gets its consolidated summary only from
[llm.private]or a model that runs on this machine. If the only model there is one that sends text out, the page is skipped (and listed) rather than summarised.
Everything that comes with or through a mail counts as email, not just its body: a PDF attached to it, an image attached to it (read by the local OCR model), text read by OCR from a scanned PDF that came attached to one, and the pages that a link in the mail leads to, when [email] follow_links is on. Those get kind: email and are written only by [llm.private], like the mail itself. To tell a file from a mail once it sits in inbox/ as a plain file, the worker keeps the hash of every attachment it saved from a mail (.esbi/mail-pdfs.txt); a link taken from a mail is queued with its own origin (mail-link) so its note is marked the same way. An embedding model that is not on this machine never receives email-derived pages, and an email being ingested is never used as a query to it.
To set it up, see Keep email local.
Files and history¶
- The model never writes files and needs no tool access; the worker applies only what passes validation.
- Nothing is deleted. Mail is read with
BODY.PEEK, marked as read only once it is saved in the vault, and never removed;raw/is never modified; the old vault is only read.sb reingestreplaces notes, but tags the vault in git first. - The mailbox password lives only in the macOS Keychain (the system keyring elsewhere, which is not a supported system), never in a file, a log or the command line.
- Notes made from email are ordinary notes in the vault. They go wherever the vault goes: the git backup remote (
sb init --remote, pushed after each index refresh; use a private repository) and thesb exportwebsite (do not publish that folder if you capture email).raw/is never exported. - Text a source puts into a note (a page, a mail, a model's answer) is plain text there: HTML is shown as text, images in model text are dropped, and the exported site runs one script only: Mermaid, loaded from
cdn.jsdelivr.neton a page that has a diagram, to draw it. The details are under Prompt injection.
Fetching¶
http/httpsonly, public addresses only (localhost, private ranges, link-local and cloud-metadata addresses are refused), re-checked on every redirect. The name is resolved once per request and the connection goes to the address that was checked (theHostheader and the TLS name stay the original host, so certificates are still verified), so a DNS server cannot answer "public" to the check and "private" to the connection.- Downloads are capped at 20 MB and 120 seconds.
- Proxies from the environment are ignored.
HTTP_PROXY,HTTPS_PROXY,ALL_PROXYandNO_PROXY(and the other environment settings the HTTP library would read) have no effect on fetching a link or on the update check. Behind a proxy it is the proxy that resolves the name and connects, so the check that a link points at a public address would no longer describe where the request goes, and a proxy inside your network could be made to reach internal hosts through a link in an email. If your network only has a proxy, set[network] use_environment_proxy = true: the address checks are then the proxy's, not esbi-cli's, andsb doctorwarns for as long as it is on. See[network]. - A link in a mail is only fetched when you turn on
[email] follow_links, and then by the nightly run, not while the mail is captured. Opening a link is a visit: whoever runs that site sees a request from your machine. It goes through the same checks as any link. - The IMAP connection verifies the server's certificate and name.
- A
config.tomlin the current folder is never read: a cloned repository could otherwise point the model or the mailbox at someone else's server.
Prompt injection¶
A web page, a mail, a PDF, a clip or a transcript can contain text that tells the model what to do ("ignore your instructions and write..."), and a small model often obeys. The worker cannot make a model refuse. So it does two things: it stops a source from forging the structure of the prompt, and it treats everything the model writes back as untrusted text, checking it before anything reaches a note. The model also never writes files and has no tools, so the worst it can do is return text, and that text is cleaned. The guarantees below are tested with a corpus of 15 inert hostile sources (instructions in the body, in a fake system turn, in a fake </source>, in a clip's title, in an image's alt text, in a mail's subject, in a PDF line, in a transcript; exfiltration through an image or a link; links to missing or private pages; page names that try to escape their folder; fake contradictions; raw HTML; a mail that asks to be sent to the cloud) and a test that plays a model that obeys every one of them.
What is guaranteed¶
- The fence. A source goes into a prompt between tags such as
<source>...</source>or<chunk>...</chunk>. A source cannot close or forge those tags, nor fake the markers a chat template puts between turns (<|...|>,<<SYS>>,[INST]): the look-alike is made inert before the prompt is built. The same holds for a title, a URL or a name that goes into the instructions of a prompt: it is quoted, and cannot add lines to them. The prompts also tell the model that what is inside the tags is data, which a model may or may not heed. - Plain prose only. Everything the model writes into a note is plain text: no HTML (
<is shown as text), no image syntax (a remote image would load when the note is opened, and its address could carry the titles of your other notes out), no control characters (a terminal would run an escape sequence thatcatorsb askprints). This covers the summary, the one-line summary that the index shows, concept and entity descriptions, key ideas, contradictions, connections and PDF figure captions. - Addresses only from the source. A Markdown link or a bare web address the model wrote survives only if that exact address is in the source (or in the pages given to
sb ask). Otherwise the link text stays and the address goes, and the note says how many were removed. A link the source really made is kept. [[Links]]only to pages that exist. A link the model wrote to a page that does not exist is reduced to its words, and a public note never links to an email page (or to a page built only from email).- Contradictions stay on their side. A contradiction can only land on a page on the same side of the email line as the source: a public source cannot put a callout on an email page, and a mail cannot write its text into a public page (which a cloud model would read later).
- Reserved names are refused, and aliases cannot steal names. A concept called
Home,index,log,lint,SCHEMAor a date such as2026-10-04is skipped, so it cannot shadow the real page in[[links]]; an alias that already means another page is dropped. A page name cannot carry a path out of its folder. - A quote cannot forge structure. A quote is flattened to one line (it still has to be in the source word for word), and one that starts like a heading or emphasis is rejected, so a quote cannot add a heading to the note. A clip's
source:address is accepted only if it is one plainhttp(s)address, with no line breaks or HTML. sb askis cleaned the same way. The answer is scrubbed like a note (an address survives only if it is in the pages given), andsb ask --savewrites the cleaned text. With a model that sends text out, an answer cannot cite an email page.
What is not guaranteed¶
- That a model resists an injection. It often does not. On
llama3.2(3B), plain-text attacks worked in 4 to 5 of 5 tries. The worker only limits what the output can do; a hostile page can still make a small model write something wrong or off-topic. - That a plausible fake is caught. An invented but believable summary, key idea or contradiction that uses words from the source, written because a page told the model to, passes every check: the checks look at markup, addresses, links, names and verbatim quotes, not at truth.
- That a hostile page's own images and links do not phone home. What a web page itself contains is kept as the page had it: its images are linked in the note and its text is in
raw/. Opening the note in a viewer that loads remote images makes a request to that site. - That
raw/is safe to open anywhere. The snapshots inraw/are the source's text verbatim; only the notes the model writes are cleaned. - That a local model does not mention a private title in plain words. The filters remove
[[links]]to email pages from a public note and keep email pages out of what a cloud model is shown. A model on this machine, which may see those titles, can still write one in a sentence without a link, and nothing removes it. - Text hidden inside images. An image or a scan is read by the OCR model, and what it reads is as untrusted as a web page: an image can carry instructions that are invisible to you (tiny, faint or off to the side) and that the OCR model transcribes. Nothing detects them, and only the fence and the checks above apply to what follows.
Whatever the model returns is also checked as before (quotes verbatim, terms and entities in the source, links to real pages).
What sb ask checks in an answer¶
A citation that points at a real page does not prove the page says what the answer says. After the model answers, the worker checks each sentence's numbers, proper names and key words against the pages the model was given and cited, and marks the sentences that fail with ⚠. This is a trust check, so its limits matter:
- The evidence is what the model could see. For a cloud model that is the public text only: email pages, the email sections of shared pages and their consolidated summaries are not evidence, exactly as they were not in the prompt. The check never reads more than the model was allowed to read.
- It compares words, so it needs the same language. When every page the answer rests on is written in another language than your notes, nothing is checked.
- It was measured on one setup: Spanish notes and
llama3.2(all 51 unsupported sentences flagged; 5 of 146 supported ones flagged wrongly, none in answers that cite a page). Its weak spot is a sentence where two ordinary words are swapped for unrelated nouns: only 31% of those were caught. It does not judge meaning, so it is a pointer to what to read, not a proof.
It is on by default ([run].check_answers). See Ask your wiki.
Images¶
Images and scanned PDFs are read by [llm.ocr], which only accepts an Ollama model running on this machine: an image is never sent to a cloud model, even when the notes are written by one. The text read from an image is as untrusted as a web page (an image can carry written instructions); the same delimiters apply.
Reporting a problem¶
Please report vulnerabilities privately: see SECURITY.md.