# Security ## Reporting a vulnerability Please do not open a public issue for a security problem. Report it privately through GitHub instead: **Security, Report a vulnerability** on this repository (private vulnerability reporting). Say what you found, how to reproduce it, and which version you ran. You get an answer within a week. A fix goes out as a new release; the release notes name the problem once the fix is available, and you are thanked there unless you would rather not be. ## Supported versions Only the newest release gets security fixes. Updating is `docker compose pull && docker compose up -d`; see [Updating](README.md#updating). ## Reaching nexbooks from outside nexbooks is best used in your own network, and from on the way through a VPN. If you open it to the internet anyway, the README has a checklist: [Reaching nexbooks from outside](README.md#reaching-nexbooks-from-outside). **A request that cannot be anybody's costs next to nothing (under `/api/`).** One that brings neither a session cookie nor a token to a route that needs a sign-in is answered `401` before the framework, a thread or the database is asked. A sender that guessed wrong 30 times within a minute (a session cookie, device token or API key the routes turned away as wrong, the same one once; a key at a route for devices, keys that are switched off or a key sent by a web page are mistakes, not guesses, and do not count) is answered `429` until the minute is over, but **only for credentials the database does not have**: the door looks the credential up (one question about three hashes, on a connection of its own) and never turns away a session, a device token or an API key that exists, not after a restart and not behind a shared address (a household, an office, a proxy). Where the database cannot answer, the request goes on to the route. The sender is told apart as for the sign-in brake: without `NEXBOOKS_TRUSTED_PROXIES` a made-up `X-Forwarded-For` opens no new count, and the proxy as a whole is the sender; `X-Forwarded-For` counts as the sender's address only from a trusted proxy. Measured on one machine at a steady 400 requests a second from outside: a person's shelf took 60 to 64 ms (54 ms alone) instead of 74 to 245 ms. The doors for reading apps (`/opds`, `/kosync`, `/kobo`) are not behind this door: each looks its secret up in the database (one or two small questions) and counts wrong ones against the sender on its own, five and then a quarter of an hour of `429` (a secret that exists is never turned away by that). Measured in one process, a request of that kind that belongs to nobody took about 11 to 13 ms against 2.5 ms for the door. A flood of eight senders on those doors slowed a person's shelf no more than a flood on the door did (206 to 214 ms against 277 ms, in one process, which measures the interpreter lock more than the doors). They stay out of the door because they have three more ways to bring a secret (a Basic header, two headers of KOReader, a path) and answer `403` while the operator has e-readers switched off, which needs the database too. **The container.** It runs with a read-only root, a small `/tmp` in memory, no new privileges, every capability dropped but the four the start needs to set the owner of `/data` and to drop from root, and as the user PUID:PGID (never root). ## What the operator can reach The operator manages accounts, the settings, the libraries and the backups. A backup holds the server's secret (`secret.key`) together with the database: with both, the mail credentials, the API keys of the indexers and the passwords of the download programs can be read. Keep backups as protected as the data folder. A device token never reaches the operator's side, not even the operator's own. ## Books are only read (unless the operator turns tidying on, books are fetched or uploaded, or an edition is moved) nexbooks reads the folders of its libraries and never writes into them: it does not delete a book, and it does not change the metadata inside a file. The exceptions are filing what was fetched or uploaded (below, "Checking and filing") and "Tidying" (Settings, Tidying), **off from the start** and the operator's alone, and only in a library the operator is a member of with "manage" (a preview names the books and their files, and those are for the members of a library, the operator included): it renames and moves files by a scheme, after a preview and after the operator's confirmation, in a folder nexbooks may write in (the libraries are mounted `:ro` from the start, and then nothing can happen). It never overwrites a file, never follows a link, never leaves the library folder (a name from a book's metadata can neither climb with `..` nor add a `/`), moves a book whole or not at all, writes every move into a journal, and can be undone. The third is **moving an edition into another library**, for a member with "manage" in both (the operator included only as such a member), after a question: every file of the edition goes to its goal by the scheme of filing, never over anything (a goal that is taken gives another name), never through a link; on the same disk as a second name, else as a copy whose size is compared; the files at the source go only after every file lies at its goal and the edition belongs to the target, each only while its twin there is whole. A move that fails before that takes back what it put in the target; one that fails after it leaves the rest at the source, which no scan takes for a new book. A path in the database is relative to its library and is checked before every use: nothing outside the library folder is ever read, whatever the database says (links and `..` included). **One more thing is done by itself, and only where nexbooks may write.** After a scan, an edition that holds one file twice, the same to the byte, keeps one copy and puts the others into the trash of the library (they come back from there). That happens only in a library nexbooks has been given to write in: the operator switched tidying on, or the library is where fetched books go (Settings, Formats and places). In every other library the copies stay where they are and are only named under "Braucht dich", even when the folder is mounted so that nexbooks could write. ## Searching and downloading - **The search finds only what the caller may see, and takes no time for what they may not.** Every word in the search index carries the library of its book in front, and a search asks for the words of the caller's libraries only, so the index never reads what lies in another library. The statement that finds the books carries the rights a second time (a library of the caller, a file on the disk), and nothing is filtered afterwards. What is typed never becomes syntax of the index or of SQL: it is cut into words of letters and digits, each quoted. A search that runs longer than five seconds is stopped, and a person may search 240 times a minute. - **A download names the file by its number.** The place on the disk comes from the database and is checked against the library folder again at every request (no `..`, no absolute path, no link, a plain file inside the folder); a file of a library the caller is not in, a file beside a book and a file that is gone answer `404` exactly like a number nobody had. - **The catalogue search is for a signed-in browser**, never for an API key: it costs the sources a request. It sends the typed words and nothing else, only to the sources the operator switched on, at their pace, and a person may ask it 20 times a minute. ## Reading in the browser A book is HTML, CSS and pictures made by somebody else, opened in the browser of a person who is signed in to nexbooks. That is the place where a book could do harm, and so the reader is built around one rule: **no script runs in a page of a book**, and what a book does not need to reach is out of its reach. - **The frame of the book** (`/readview.html`) is served with a policy of its own: `connect-src 'none'` (it cannot call nexbooks, nor anybody else), scripts only from nexbooks's own files, pictures, styles and fonts only out of the book, no windows, no forms, no navigation of the page that holds it. It fetches nothing itself: the page fetches the file with the person's session and hands the bytes over (a PDF has a frame of its own, with no origin at all). Messages count only from the frame the page holds. The frame has the origin of nexbooks, because the reader has to reach into the pages it turns (a browser allows that only inside one origin; a frame without an origin cannot be reached into, which was tried and measured); the protection is therefore that no script runs. - **The walls are tried before every book.** The two walls that belong to the browser (a page's frame has no `allow-scripts`, and the page inherits the policy of the frame) are settings, and a setting can fail without a sound. So the reader makes a page the way it makes a page of a book (a `blob:` address, in a frame with the same `sandbox`), puts a script of its own in it and looks whether it ran. If it did, no book is opened in that browser and the person is told so in plain words; the file can still be downloaded. (The PDF frame has no origin and is not tried this way.) - **Every page of a book is cleaned before it is shown**, whatever the manifest calls its media type (parameters and capitals are ignored): `application/xhtml+xml`, `text/html`, `image/svg+xml` and also `application/xml` and `text/xml`, which a browser would draw as a page. A page of the spine of any other type is replaced by an empty page, and a part that is nothing the reader knows (`text/plain`, `application/octet-stream`, other `+xml` types, a missing type) gets the type `application/octet-stream`, so that no browser draws it or guesses a page from its bytes. An XML document that is no XHTML page is an empty page. An SVG page cannot carry a policy of its own (SVG has no place for one): it is cleaned the same way, and the policy and the sandbox of the frame are its walls. What is taken out: `