Threat model: what contains the agent
Written to the same standard as the privacy page: a security claim is only honest if its edges are named. This page says what MyRA defends against, what it does not, and which sentences here are checked by a test rather than merely believed.
What MyRA is
One process tree. An Electron main process, one window, and the child processes MyRA starts — pandoc, pdftotext, tar, the Lemonade daemon, and whatever inference engines that daemon starts in turn.
There is no VM, no container, and no OS-level sandbox around the main process. That is a decision rather than an omission. MyRA needs the system keyring, native directory pickers, Zotero’s data directory and ~/Documents; a sandbox policy permissive enough to allow those four is permissive enough to allow everything that matters. The honest answer was to make the in-process boundary airtight and say plainly where it ends, which is this page.
The AppArmor profile in the .deb is electron-builder’s boilerplate and reads flags=(unconfined). It exists to grant user namespaces back on Ubuntu 24.04 and later, so that Chromium’s own sandbox works. It confines nothing.
The boundary: the tool registry
The model cannot do anything that is not a tool. There is no bash, so “run a command” is not expressible in the protocol — the model can only emit a call matching a schema in registry.ts, and this is the complete list.
| Tool | Risk class |
|---|---|
web_search | safe |
fetch_page | safe |
deep_research | safe |
academic_research | safe |
write_document | write |
read_document | safe |
convert_document | write |
draft_document | write |
search_library | safe |
create_task | write |
list_tasks | safe |
complete_task | write |
create_diagram | safe |
create_table | safe |
create_prisma_diagram | safe |
create_chart | safe |
remember | write |
project_papers | safe |
read_paper | safe |
Machine-checked. test/threatModel.test.ts parses this table and compares it to the registry, so a tool added without a row here fails npm test.
Two rules hold the boundary up and neither may be relaxed:
- A tool builds its own argv from a fixed template. pandoc’s
--lua-filterexecutes arbitrary code, so a passthrough argument would be a shell with extra steps. Pinned bytest/jail.test.ts. - Every path is resolved and jailed on every call, with
realpath, after normalisation. Pinned bytest/jail.test.tsandtest/docs.test.ts.
Note what the risk classes mean in practice for this tool set: nothing is classified above write, and write auto-approves in the default mode. So the approval prompt never fires during ordinary use. The jail is what is actually containing the agent, not the permission dialog. That is why the jail’s tests are the ones that matter.
What the agent can reach
<workspaceRoot>/documents, throughresolveInJail— read, write, convert. Nothing above it, and no path outside it, on any of the three platforms MyRA ships to.- The research root, for runs it creates. The directory name is a slug of the question, reduced to
[a-z0-9-]. - MyRA’s own task store, a flat directory of JSON files in MyRA’s own folder.
- A research project’s notes, append-only, through
remember— and only a note whose quote is something the user typed (or the user’s own reply agreeing to a suggestion), checked after every untrusted block is stripped. A fetched page can ask the model to remember something; it cannot supply the quote the note has to rest on. Pinned bytest/rememberTool.test.ts. - The network, only through
fetch_pageand the four scholarly APIs indatabases.ts.fetch_pagerefuses loopback, private, link-local and CGNAT addresses after resolving them, which covers the cloud metadata endpoint and DNS rebinding at resolve time. Pinned bytest/guard.test.ts. - The Zotero library, read-only, and never the live SQLite file.
- The papers of the conversation’s own project, read-only, through
project_papersandread_paper: the ones uploaded to it (undersourcesRoot, addressed by an asserted id that must be one of the project’s members) and the PDFs of the items in its linked Zotero collections. The model names an item key, never a path. A file in Zotero’s storage is read throughresolveInJailagainst<data dir>/storage; a linked file is read only at the path Zotero’s database records for an item in scope, and only if it resolves to a regular.pdf— an allowlist rather than a jail, decided explicitly because linked attachments (ZotMoov, a synced “Zotero Attachments” folder) are how many libraries keep their PDFs. Every line of paper text reaches the model inside an UNTRUSTED CONTENT block, indented so a paper’s own reference list cannot pass for a source. Pinned bytest/zoteroPdfs.test.tsandtest/paperTools.test.ts.
What the agent cannot reach
- Command execution. No tool takes a command, a shell string, or an argument that reaches a command line.
test/research.test.tspins thatbashandwrite_fileare unknown tools. - An arbitrary URL. See above.
- The keyring.
secrets.tsis main-only and no tool calls it. - Settings. No tool writes configuration, so the model cannot move its own jail.
- Another local account’s files. Directories MyRA creates are
0700and files0600, asserted under a0000umask bytest/privacy.test.ts.
What is trusted
The main process, completely. It holds the keyring, the filesystem and all network egress. Nothing in MyRA defends against a compromised main process.
Every binary MyRA downloads, once installed. pandoc, the Lemonade daemon and the inference engines run as you, with no confinement. What MyRA does promise is that it will not install one it cannot verify — see the supply chain below.
The renderer is trusted but checked. It runs with sandbox: true, contextIsolation: true and nodeIntegration: false; it makes no network requests at all, because onBeforeRequest denies everything but the dev server; and nothing in it builds HTML from a string — Markdown is rendered from marked’s lexer into React elements, pinned by test/markdown-safety.test.ts — so model output cannot become script.
Its IPC arguments are nonetheless validated in main, and the reason is worth stating rather than assuming: a future innerHTML, a renderer escape, or the local API server growing a route would each turn “the window would never send that” into a false premise. Every id that lands in a path is asserted, every configurable folder is rebuilt rather than spread, and every reveal resolves before it acts.
What MyRA does not defend against
- A compromised main process, as above.
- Another process running as you. The
0700directories defend against a different local account, which is the case a shared lab or university laptop actually has. They do not defend against something already running under your own user. - A hostile pandoc or lemond after installation. The checksum is checked once, at install.
- A residual race on document writes.
write_documentcreates its file under an unguessable name withO_EXCLand renames it into place, so a symlink planted at the target between resolution and use is replaced rather than followed. What remains is the parent directory: swapping an intermediate directory for a symlink in the same window would still land the file elsewhere. Closing that needsopenat/O_DIRECTORYwalking, which Node does not expose. The precondition is write access inside a directory created0700— i.e. already you — or a workspace you deliberately pointed at a shared or synced folder. - A model that lies. Tool output is wrapped in untrusted markers and citations are checked, but a fabrication inside an answer is a quality problem, not a containment one.
Prompt injection, specifically
A fetched web page, a search snippet and somebody else’s manuscript are all text written by strangers, and all three reach the model.
Injection cannot widen what the agent can do. It can only misuse what the agent may already do. There is no instruction in a web page that adds a tool, removes the jail, or reaches a shell, because none of those is expressible. What injected text can do is ask for a document to be written, a page to be fetched, or a search to be run — the same things you can ask for.
What holds: the jail, the URL guard, the permission mode, and the untrusted markers that tell the model where the stranger’s text begins. What does not: nothing stops a model acting on injected text within its allowed capabilities.
Supply chain
Everything MyRA downloads at runtime is verified before it is used:
- The Lemonade daemon — pinned by sha256 in
src/core/runtime/lemonade.ts, recorded when the version was chosen rather than read from the same host that serves the bytes. A version bumped without its hashes failsnpm test. - pandoc — verified against the digest GitHub publishes for the release asset. No digest means MyRA declines to install it and falls back to whatever is on your
PATH. - The bundled C runtime — pinned by its OCI digest.
- Model weights — verified against the hashes Hugging Face publishes.
Archives are extracted with --no-same-owner --no-same-permissions, so a tarball cannot restore a setuid bit or widen a directory MyRA created 0700.
Reporting a vulnerability
See SECURITY.md in the repository: use GitHub’s private vulnerability reporting rather than a public issue.