Please report security issues privately rather than opening a public issue.
Use GitHub's private vulnerability reporting (Security → Report a vulnerability). That keeps the details between us until a fix exists.
Please include what you need to make the problem reproducible: the endpoint or file, the request or input, what you expected, and what happened. A proof of concept helps but is not required.
I maintain this in my spare time, so expect an initial response in days rather than hours. I will tell you what I plan to do and when, and credit you in the release notes unless you would rather stay anonymous.
Only the main branch and the images built from it
(ghcr.io/jwapps-app/scrinium-api and -web, tag latest) receive fixes.
There are no maintained release branches — update to current main before
reporting.
Understanding what this project does and does not defend against will save us both time.
In scope. Anything reachable by an unauthenticated request; privilege boundaries between accounts in the same library; tenant isolation; handling of hostile documents (a crafted PDF, an office file, a zip import) since those arrive from outside; the public share-link surface; the ingestion paths that accept input from elsewhere (email attachments, the watched folder); and availability bugs where a single request or document can stall the whole application.
Out of scope.
- Anyone with filesystem access to the host. Blobs are stored unencrypted
and the container currently runs as root (documented in
backend/Dockerfile). Encryption at rest is the storage layer's job. - Anyone with database access. The database is trusted; it holds document text and TOTP secrets in the clear.
- A compromised OCR sidecar. The Apple Vision helper is assumed to be the operator's own machine on their own network.
- Denial of service by an authenticated operator against their own library. Uploading a thousand huge books is a supported use, not an attack.
- Missing rate limits on ordinary authenticated endpoints. Auth endpoints are limited; the rest assume an authenticated user is not attacking themselves.
The application assumes it sits behind a reverse proxy that terminates TLS.
Two things matter if you deviate from the shipped docker-compose files:
SECRET_KEYmust be a strong random value. Production refuses to start on a weak or placeholder secret.TRUSTED_PROXIESdecides whoseCF-Connecting-IPheader is believed for rate limiting. It defaults to private ranges, which covers the bundled nginx. If something else fronts the API, set it accordingly — an over-broad value lets a caller forge its own address and defeat the auth rate limits.