Paperless-ngx
Scan a letter or drop in a PDF, and Paperless-ngx reads the text on the page, works out who sent it, and files it under the right type and tags. Months later you find it again by typing a word you happen to remember - a name from the letterhead, an amount, a policy number. Nothing is sent anywhere to make that work: the text recognition runs on your own server, and the original scan is kept exactly as it arrived.
Install it from Suite Manager → Apps — once running it lives at
paperless.<your-domain> and on your Home dashboard.
What you can do
- Search the words inside your paper — Every page that arrives is read once, so a scanned letter can be found later by any word printed on it - a reference number, an amount, or the name of the company that sent it.
- Files new documents for you — Paperless learns from the documents you have already sorted, then suggests the sender, the document type and the tags for each new arrival.
- Keeps the original exactly as it came in — The scan you fed in is stored untouched next to the searchable version, so you can always download the document you actually received.
- Reads English and Dutch out of the box — Text recognition runs on your own machine, with English, Dutch, German, French, Italian and Spanish already installed - no account and no upload to anyone else's service.
A self-hosted Evernote alternative
Paperless-ngx does the job people use Evernote, Adobe Scan, Microsoft Lens, Genius Scan, DEVONthink, SwiftScan, and DocuWare for, on a server you own: the same everyday task, without a subscription and without an account at a company that also keeps your data. If you arrived comparing one of these, this is the app My Own Suite installs in its place — its privacy posture below says exactly what it does and does not send anywhere.
- Evernote alternative
- Adobe Scan alternative
- Microsoft Lens alternative
- Genius Scan alternative
- DEVONthink alternative
- SwiftScan alternative
- DocuWare alternative
Good to know
Paperless-ngx
Private by default Privacy grade A (A is best, D is worst)Runs entirely on your machine. Nothing leaves your server unless you share it.
- Resource needs: Medium resources — Runs Paperless plus a Valkey broker, and text recognition is CPU-heavy while documents are being processed.
- Package-provided privacy notes (not independently verified by MOS): Paperless-ngx stores and processes your documents on your own server. Text recognition runs locally in the container, and the optional AI and cloud-OCR features are off unless you configure them yourself.
- Only the Paperless web service is reachable through Caddy; the Valkey broker stays internal to the package network.
- Optional remote OCR through Azure AI and optional AI suggestions through an OpenAI-compatible service or Ollama are disabled by default, and MOS does not configure them.
- MOS turns off Paperless' own update check, which would contact GitHub, because MOS handles app updates itself.
- Documents, thumbnails and the search index live in package-owned MOS volumes; back them up before relying on this package for records you cannot scan again.
Privacy assessment
This is the assessment behind Paperless-ngx's posture grade, exactly as it ships inside the package. It describes package version 0.1.1 and nothing else — install a different version and its own assessment travels with it. Reviewed 11 August 2026, due for reassessment by 11 February 2027.
What was reviewed
- Package — version 0.1.1, content digest
e3e0e043b36d…at commit3fa1119973ba - Paperless-ngx — version 3.0.5 from
ghcr.io/paperless-ngx/paperless-ngxpinned at65a4cabf0169… - Valkey — version 9.1.1 from
docker.io/valkey/valkeypinned atee91f7a174ac…
Deliberately outside this assessment:
- Third-party Paperless-ngx mobile and desktop clients, none of which are published by the project
- Third-party Paperless-ngx API clients
- Network scanners or multifunction printers configured by the owner to deposit documents into the consume folder
- IMAP mail servers reached by mail accounts the owner adds after installation
Evidence
Observed Watched on the wire, in a running instance of this package.
-
Runtime network capture of this package. Both containers were run from the MOS runtime projection, rendered by Suite Manager's own projection code, with a logging resolver as their only upstream resolver and a packet capture inside each container's own network namespace, so DNS to Docker's embedded resolver on loopback was recorded alongside everything crossing the package boundary. Across a 7 minute 25 second session covering first boot, superuser creation, document upload, OCR, search, opening documents and every settings page, the only DNS name either container resolved, apart from the injected control host, was the internal service alias 'broker' (300 times from the paperless container and never from the broker container), and the only TLS server name either presented was that same control host. Control requests were injected into both containers and appear in both the DNS and the packet record, so the silence is an observation rather than a failed capture.
Source: MOS runtime projection of apps/paperless-ngx 0.1.0 (Paperless-ngx 3.0.5), observed 2026-08-11 21:36:43 to 21:44:08 UTC · checked 2026-08-11
-
No connection bypassed name resolution. Every IP conversation recorded on either container was internal to the package network, on loopback, or to one of the two addresses the control host resolved to; there was no unexplained external address, which is the check that catches a hardcoded endpoint reached without a DNS lookup. Both external conversations began 3.8 and 4.1 seconds into the capture and lasted under a twentieth of a second each, matching the control injection window, and no external conversation occurred during the remaining seven minutes of interactive use. There was no IPv6 egress at all: the only IPv6 traffic was link-local neighbour discovery and ::1 loopback, and although AAAA records for the control host were requested, no IPv6 connection was established.
Source: MOS runtime projection of apps/paperless-ngx 0.1.0, tshark conversation and protocol analysis of the per-container captures, observed 2026-08-11 · checked 2026-08-11
-
Document content was not observed leaving the package. The paperless container received 2027 kB from the browser over the published loopback port and returned 543 kB; its total outbound traffic to the internet across the whole session was 2893 bytes and 1767 bytes, both to the control host, which is consistent with two TLS handshakes and orders of magnitude too small to carry the uploaded documents. Text recognition ran while the container contacted nothing. TLS payloads were not decrypted, so this bounds behaviour by destination and volume rather than asserting request contents.
Source: MOS runtime projection of apps/paperless-ngx 0.1.0, per-container conversation byte counts, observed 2026-08-11 · checked 2026-08-11
-
The scheduled task that reaches outward was observed doing nothing. Of the six tasks Paperless ships enabled, the ten-minute 'Check all e-mail accounts' job is the only one that would contact an external server, and its 23:40 local tick fell inside the capture window. It produced no DNS query and no connection, confirming that it is a true no-op when no mail account is configured, which MOS does not create. The observation is scoped to that task: at the wall-clock times covered, the hourly classifier training, hourly workflow check, nightly index optimisation, daily trash emptying and weekly sanity check did not tick, though all five are local by inspection.
Source: MOS runtime projection of apps/paperless-ngx 0.1.0 observed 2026-08-11 23:36 to 23:44 Europe/Amsterdam, and src/paperless/settings/custom.py parse_beat_schedule at v3.0.5 · checked 2026-08-11
-
The browser client was observed contacting nothing outside the operator's own server. The human reviewer drove the full session in Firefox with the network panel open and preserve-log enabled, covering sign-in, uploads, search, opening documents, viewing recognised text and every settings page, and reported that every request went to the local server with no outbound destination. This agrees independently with the server-side capture and with static analysis of the compiled bundle. It is recorded as the reviewer's direct observation: no HAR file was retained, so unlike the server-side finding it cannot be re-analysed later, and no claim is made here about request headers, cookies or bodies.
Source: Firefox network panel observation of the MOS runtime projection of apps/paperless-ngx 0.1.0, by the human reviewer, 2026-08-11 · checked 2026-08-11
Configured Read from the configuration MOS actually ships.
-
MOS installs the Dutch Tesseract language pack at image build time rather than through PAPERLESS_OCR_LANGUAGES, which installs packages over the network at every container start. The motivation was reproducibility from a pinned image, and keeping the OCR languages the MOS install form offers working without a runtime package fetch is a direct consequence: every language the form accepts is present in the image before it runs.
Source: apps/paperless-ngx/Dockerfile and apps/paperless-ngx/manifest.json
-
Only the paperless service is published through the generated app route; the Valkey broker has no route and stays internal to the package network. The first administrator account is created from the MOS install form directly inside the container through PAPERLESS_ADMIN_USER and PAPERLESS_ADMIN_PASSWORD, with no upstream Paperless-ngx account, no registration and no licence check. Documents, the originals as received, thumbnails, the search index and the SQLite database persist to package-owned MOS volumes.
Source: apps/paperless-ngx/manifest.json and apps/paperless-ngx/README.md
Documented Taken from upstream documentation, policies or source.
-
The pinned image contains no analytics, crash-reporting or usage-telemetry library. A search of the entire Python source tree for sentry, posthog, analytics, matomo, plausible and google-analytics returned no file. There is no telemetry subsystem to disable, which is a different and stronger statement than a telemetry feature that ships switched off.
Source: grep of /usr/src/paperless/src in the pinned image (sha256:65a4cabf0169ea7fbd90ab7bb28ba3f8b5909613635acda1a03ad606f34b456b) · checked 2026-08-11
-
The built web client contacts no external host. Extracting every http(s) URL from the compiled Angular bundle yields only http://www.w3.org (SVG and XML namespace identifiers, which are never fetched), https://docs.paperless-ngx.com and https://github.com as documentation links a user may click, plus placeholder strings from bundled libraries. There is no CDN, font service, script origin, map provider, error reporter or analytics endpoint in the client surface.
Source: grep of /usr/src/paperless/src/documents/static/frontend/en-US/*.js in the pinned image · checked 2026-08-11
-
The server has exactly one hardcoded outbound endpoint: https://api.github.com/repos/paperless-ngx/paperless-ngx/releases/latest, issued by RemoteVersionView when the web client requests /api/remote_version/ and cached for 15 minutes. Nothing else in the server or the client reaches a network host that the owner has not configured themselves. MOS switches the check off (see telemetryControls). Upstream would leave it off anyway at v3.0.5, because the client setting that triggers it defaults to false, so the MOS setting pins a default rather than correcting an enabled one. The runtime capture confirms this: api.github.com was never resolved and never contacted, including at first boot and on first sign-in, when a client with no stored preference falls back to its defaults.
Source: src/documents/views.py RemoteVersionView, src/paperless/settings/__init__.py ENABLE_UPDATE_CHECK, and src-ui/src/app/data/ui-settings.ts at v3.0.5 · checked 2026-08-11
-
Text recognition and language processing run entirely on the operator's own server from assets baked into the image. The image ships 22 MB of NLTK corpora, stemmers and tokenizers at /usr/share/nltk_data, so the punkt and stopwords archives the source documents are never fetched at runtime, and Tesseract language data is installed at build time. The document classifier that suggests correspondents, types and tags is trained hourly from the operator's own documents by a local scheduled task; no model is downloaded and no inference leaves the container.
Source: filesystem inspection of the pinned image, src/paperless/settings/__init__.py _get_nltk_language_setting, and documents.tasks.train_classifier · checked 2026-08-11
-
The two features that would send document content to a third party are off unless the owner configures them. PAPERLESS_AI_ENABLED defaults to NO, so AI suggestions and the document chat are inactive and no LLM endpoint, model or API key is set. PAPERLESS_REMOTE_OCR_ENGINE is unset, so cloud OCR through Azure AI is inactive. MOS sets neither variable and exposes neither in its install form, so reaching an external AI or OCR service requires the owner to add configuration that the MOS package does not provide.
Source: src/paperless/settings/__init__.py AI_ENABLED, LLM_BACKEND and REMOTE_OCR_ENGINE at v3.0.5, and apps/paperless-ngx/manifest.json · checked 2026-08-11
-
Six scheduled tasks ship enabled. Five are local: training the classifier hourly, optimising the search index nightly, a weekly sanity check, emptying the trash daily, and checking scheduled workflows hourly. The sixth, 'Check all e-mail accounts', runs every ten minutes and would connect to IMAP servers, but it iterates over mail accounts the owner has added and MOS creates none. Whether it is a true no-op with zero accounts configured has not yet been observed on the wire.
Source: src/paperless/settings/custom.py parse_beat_schedule at v3.0.5 · checked 2026-08-11
-
Where the app does accept an owner-supplied URL, it defends the operator's own network against it. src/paperless/network.py resolves every outbound hostname, refuses the request if it resolves to a private, loopback, link-local, multicast or unspecified address unless internal access is explicitly allowed, and then connects to the vetted IP while preserving the original Host header and TLS SNI so the pinning cannot be defeated by DNS rebinding. This governs the AI, embedding and remote-OCR endpoints. It is a deliberate anti-SSRF design rather than an egress allow-list, and this review records it as a point in the package's favour.
Source: src/paperless/network.py at v3.0.5 · checked 2026-08-11
-
The project publishes no privacy policy and no terms of service, and the absence is consistent rather than an omission: paperless-ngx operates no hosted service, no account system and no back end of its own, so there is no data controller relationship for a policy to describe. The repository at v3.0.5 contains LICENSE, SECURITY.md, CONTRIBUTING.md and CODE_OF_CONDUCT.md and no policy document. The GNU General Public License v3 is the only instrument binding an operator. The publisher is the paperless-ngx organisation, a GitHub-verified community project reachable at hello@paperless-ngx.com, with no corporate owner and no ownership change to report.
Source: repository contents at tag v3.0.5 and the paperless-ngx GitHub organisation record · checked 2026-08-11
What this review does not settle
Published rather than smoothed over. Each of these is a limit of the method, not a finding against the app.
- TLS payloads were not decrypted, so every conclusion bounds behaviour by destination, resolved name and volume rather than asserting the contents of encrypted requests. In this package that limit binds unusually little, because no external destination was contacted at all outside the injected control.
- The client-side result rests on the reviewer watching the browser network panel during the session rather than on a retained HAR. No artifact exists to re-analyse, so unlike the server-side capture it cannot be revisited if the finding is later challenged, and nothing is claimed about request headers, cookies or bodies. Capturing and keeping a HAR would close this.
- The capture was taken over loopback, so browser requests carried localhost as their origin. A deployment reached through a public domain would send that domain in Referer and Origin headers to any external host it contacted; no external host was contacted, so nothing was observed leaving, but the reasoning is an inference from the absence of destinations rather than a measurement against a domain-based install.
- The packet capture attached about three seconds after the containers started. Database migrations, superuser creation and the application's own startup all fall inside the window, since Paperless takes considerably longer than that to answer, but the first few seconds of container life are not covered.
- Features an owner can add after installation are outside this result: mail accounts, which would connect to an IMAP server every ten minutes; AI suggestions and document chat through an OpenAI-compatible service or Ollama; remote OCR through Azure AI; and documents deposited into the consume volume by a scanner. MOS configures none of them and exposes none in its install form, and the review deliberately does not disable them.
Policies read
- license — paperless-ngx organisation · retrieved 2026-08-11
How this review was produced
- Method — AI-assisted review, checked by Rasmus Puls
- Model — Claude Opus 5 (Anthropic)
- Workflow —
assess-app-privacy at
5146372c3c87 - Repository commit —
3fa1119973ba
Technical reference
Maintained alongside the package at apps/paperless-ngx/README.md and rendered here
as-is.
Paperless-ngx MOS Package
Services
paperless: Paperless-ngx web and consumer service, exposed through the single publicpaperless.<base-domain>app route.broker: internal Valkey service. Paperless requires a Redis-protocol broker for its task queue; document consumption, scheduled tasks and the automatic matcher do not run without it.
Only paperless is routed through Caddy. The broker is package-internal on the package-owned Docker network.
The package uses Paperless’ built-in SQLite database (PAPERLESS_DBENGINE=sqlite). PostgreSQL, Tika and Gotenberg are optional upstream and are not part of this package.
Environment Variables
PAPERLESS_REDIS=redis://broker:6379: broker connection, addressed by service id on the package network.PAPERLESS_DBENGINE=sqlite: built-in database; no other connection variables apply.PAPERLESS_URL: projected from the app public URL. Paperless appends its hostname toALLOWED_HOSTSand adds the URL toCSRF_TRUSTED_ORIGINSandCORS_ALLOWED_ORIGINS.PAPERLESS_ALLOWED_HOSTS=paperless: the internal service name, so the MOS health probe reaches a real response instead of a DjangoDisallowedHost. Setting it also takesALLOWED_HOSTSoff its*default; Paperless then appendslocalhostitself, andPAPERLESS_URLappends the public hostname, leaving exactly the three names that legitimately reach the service.PAPERLESS_ENABLE_UPDATE_CHECK=false: MOS owns app updates, so Paperless’ own release check is switched off rather than left to the upstream default. It reacheshttps://api.github.com/repos/paperless-ngx/paperless-ngx/releases/latest. The variable is a UI-level control, not a server-side block: it setsupdate_checking.backend_setting, which the web client copies into a user’supdate_checking_enabledpreference only when that preference is unset, andRemoteVersionViewperforms the fetch whenever/api/remote_version/is called without consulting the setting. A user who turns the toggle back on inside Paperless will therefore still cause the request.PAPERLESS_SECRET_KEY: generated by MOS and stored as a secret reference. Paperless refuses to start without it.PAPERLESS_ADMIN_USER,PAPERLESS_ADMIN_PASSWORD: collected during install; Paperless creates this superuser on first start. It will not change an existing superuser’s password on later starts.PAPERLESS_OCR_LANGUAGE: collected during install, defaulteng.PAPERLESS_TIME_ZONE: collected during install, defaultEurope/Amsterdam.
Volumes And Persistence
data:/usr/src/paperless/data: SQLite database, search index and classification model.media:/usr/src/paperless/media: the documents themselves - originals, archived PDFs and thumbnails.export:/usr/src/paperless/export: destination fordocument_exporterruns.consume:/usr/src/paperless/consume: watched folder; files dropped here are imported and removed.redis-data:/dataonbroker: queue persistence.
media and data are the volumes that matter for recovery. Disable stops and removes containers while keeping routes, volumes and stored secret references. Uninstall removes containers, routes, MOS-owned Homepage shortcuts, these Docker volumes, stored config and secrets.
OCR Languages
The upstream image ships Tesseract data for eng, deu, fra, ita and spa. The package Dockerfile adds tesseract-ocr-nld at build time so Dutch works from the pinned image.
PAPERLESS_OCR_LANGUAGES (plural) is the upstream mechanism for adding more, but it apt-installs on every container start, needs the network at runtime and is unsupported in rootless containers. Adding a language to this package means adding its Tesseract package to the Dockerfile and extending the ocrLanguage field label, not setting that variable.
Setting PAPERLESS_OCR_LANGUAGE to a code with no installed data leaves documents failing in the consumer rather than failing the install.
Health Check
http://paperless:8000/accounts/login/
The allauth login page, which returns 200 without a session. Note that the MOS probe treats any status below 500 as healthy, so a misconfigured PAPERLESS_ALLOWED_HOSTS would still pass while returning 400 - the variable above is what makes this probe meaningful.
Consuming Documents
Documents can be uploaded through the web interface, which is the path this package expects owners to use. The consume volume is a MOS-managed Docker volume, so the watched-folder and scanner-drop workflows described upstream need server-side access to that volume; there is no Suite Manager path to write into it.
Upgrades
Paperless runs database migrations on start, so the manifest declares backupRequired: true and brief downtime.
Paperless-ngx 3.0 was a major release: it dropped API versions below 9, removed document and thumbnail encryption, replaced the Whoosh search backend with tantivy, and changed the pre/post consume script arguments. This package pins 3.0.5 as its first release, so those breaks do not apply to an upgrade path within MOS, but they matter to anyone importing an export from an older Paperless install.