Skip to content

Data flow

Where document content goes, and where it does not.

The full path

Browser ──(1)── nginx ──(2)── backend ──(3)── LLM endpoint
                                 │
                                 └──(4)── OCR endpoint (scanned PDFs only)
                                 │
                                 └──(5)── in-memory cache (15 min, ≤12 h)
# Hop Content Notes
1 Browser → frontend The document (multipart or JSON) Over your TLS-terminating proxy.
2 nginx → backend The same request Internal to the compose network; the backend publishes no port. nginx may spool a large body or response through /tmp, which is a tmpfs — see Data retention.
3 Backend → LLM Document text, in chunks; then the anonymized output for the re-check Only when llm is enabled. OPENAI_API_BASE.
4 Backend → OCR Rendered page images Only for scanned PDFs, only when an OCR engine is configured.
5 Backend → memory Extracted text + detected spans, keyed by request id 15 min, extendable by the reviewer up to 12 h (configurable); max 100 entries, process memory only.
← Backend → browser Source text, anonymized text, entities, warnings Cache-Control: no-store.

Content flows nowhere else. There is no database, no object storage, no volume, no telemetry endpoint, no CDN, and no analytics.

Per endpoint

Endpoint Receives Returns Leaves the backend?
POST /api/v1/anonymize Document or pasted text; or a request id + overrides Source text, anonymized text, entities, validation, timings To the LLM (detection + re-check) and, for scans, to OCR
POST /api/v1/anonymize/stream Same Same, as NDJSON with progress events Same
POST /api/v1/export/pdf The original PDF (re-sent) + overrides The redacted PDF Only on a cache miss (re-runs extraction/detection)
POST /api/v1/export/pdf/pages The original PDF (re-sent) Page PNGs + image boxes No
POST /api/v1/anonymize/{id}/extend A request id Remaining seconds + whether more is possible No — it only postpones the cache eviction
DELETE /api/v1/anonymize/{id} A request id Nothing (204 either way) No — it only forgets the cached detection
GET /api/v1/status — Detector states, OCR engine, endpoint hosts + locality, limits No
GET /health/live, /health/ready — Status only Readiness may probe configured endpoints
GET /api/v1/auth/session — Whether a sign-in gate exists, and the signed-in name/email No
GET /api/v1/auth/login, /auth/callback An authorization code from the provider A redirect + the session cookie To the identity provider — client id, redirect URI, scopes, PKCE challenge and nonce. Never document content.
POST /api/v1/auth/logout — Whether to visit the provider's sign-out No

The three /auth routes exist only when the OIDC gate is configured; with it on, every other row in this table requires a valid session cookie.

/api/v1/status returns hosts, never full URLs, keys, or filesystem paths. It is what the UI uses to warn that content will leave the machine, so it has to stay safe to expose.

In the browser

Stored Where Why
Document text, results, corrections Pinia store — memory only Cleared on reload. Never localStorage/sessionStorage.
Object URLs for previews (original PDF, redacted PDF, rendered pages) Memory, revoked on reset Needed to display a PDF.
darkMode, expertMode, keepFilenames, locale localStorage UI preferences only.
The sign-in session (OIDC gate only) HttpOnly cookie, scoped to /api Signed, absolute expiry, no document content. JavaScript cannot read it.

That split is a hard rule in the codebase: nothing derived from a document is ever persisted client-side.

What is logged

Per request: a short hash of the request id (ref=), source type, character count, entity count, validation status, timings; for exports also the byte size and the number of redaction areas.

Never logged: document text, extracted text, anonymized text, entity text, prompts, filenames — and the request id itself, which is a capability: for as long as the cache entry lives, whoever holds it can ask the API for the document. The ref= hash is there so log lines about one request can still be correlated. A structured logger drops all these field names before the line is written and records rejected_fields=… instead. The escape hatch (APP_ALLOW_INSECURE_CONTENT_LOGGING) prints a loud warning at startup and is refused in production mode.

Retention at each stop

Stop Retention
Browser memory Until reload or Neues Dokument
Browser localStorage UI preferences only, indefinitely
Sign-in cookie (OIDC gate only) OIDC_SESSION_MINUTES (8 h by default) from sign-in, absolute; gone on sign-out
Backend memory (request) The request
Backend cache 15 minutes from creation, extendable by the reviewer in 1 h steps up to 12 h (all configurable), or until the UI drops it, eviction (100 entries), or restart
Backend disk Nothing. Read-only filesystem, tmpfs for /tmp, no volumes
Frontend (nginx) disk Nothing. Spooled bodies/responses go to /tmp, mounted as a tmpfs, and are deleted when the request ends
Backend logs Metadata only, per your log retention
LLM / OCR endpoint Whatever that service does — see below

The one you have to answer yourself

The app's retention story ends at hop 3 and 4. Whether your model endpoint logs prompts, caches them, or trains on them is a property of that service, not of this one.

Before processing real data, establish for every configured endpoint: who operates it, whether it logs request bodies, what its retention is, and whether it is inside your organizational boundary. A hosted API is a data transfer to a processor and needs the corresponding agreement. See the DPIA template.