The short version: your messages are unreadable to us, your paper library is not, and the difference is deliberate. Where you run it changes who “us” means, and that is named below rather than left vague.
porcupineResearch cannot read your messages or your LaTeX manuscripts. It can read your paper library, your highlights, and your extracted data, which are encrypted at rest and access-controlled. Documents you write in Google Docs live in your Google Drive under Google’s terms — we can read those, and so can Google. porcupineResearch always knows who is in which project and when they acted.
Encrypting everything sounds better and works worse: a server that cannot read a screening status cannot count one, and a review lives on counting. So the line is drawn once, explicitly, and each kind of content sits on a named side of it.
Project messages and direct messages, LaTeX sources and their update history, comments on them, and compiled PDFs.
XChaCha20-Poly1305 under a per-project key wrapped to each member. Sealed and opened in your browser; the server stores bytes it cannot interpret.
Membership and roles, screening status and decisions, annotations and their anchors, extracted values, protocol questions, and the activity log.
Row-level security and encryption at rest. The server can read this, and it has to — sorting, filtering, counting and the PRISMA diagram are all server-side work on exactly this data.
Anything you write in a connected Google Doc, and exported Sheets.
Google's encryption and access controls, under Google's terms. Outside this application's boundary entirely.
Scholarly metadata — paper titles, DOIs, authors, venues — along with project names and member display names.
None needed. This is bibliographic data that is public by construction.
There is no password to sign in with — authentication is an emailed six-digit code — so there is nothing you know that a key could be derived from. Instead the app generates a recovery passphrase of 30 characters and shows it exactly once, when you enrol. Argon2id turns it into a key-encryption key, that unwraps a master key, and the master key unwraps your identity keypair. Project keys are sealed to each member’s public key, per project and per epoch.
Two consequences, said here rather than in a help article:
Every table carries PostgreSQL row-level security, and the application connects as a role those policies apply to. A permission bug in a page therefore shows someone an empty list; it does not show them another project’s papers. The rules that matter — an exclusion needs a reason in a systematic review, a reconciler cannot be one of the two people who extracted the paper — are database constraints and triggers, so an import or a future API cannot route around them.
The test suite asserts these by trying to break them. Every check that expects to find nothing is paired with the same query run with the policy off, so a rule that has quietly stopped being enforced fails the build rather than passing it.
Email dhrubojyoti.saha@g.bracu.ac.bd with enough detail to reproduce it. Please do not open a public issue for a vulnerability. There is no bounty; there is an acknowledgement in the changelog if you want one.