Ctrl K
ComunidadGuías…

SECURITY.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Security Policy

## Reporting a vulnerability

Please report security issues privately through
[GitHub Security Advisories](https://github.com/fabriziosalmi/pdf-ocr/security/advisories/new),
or by email to fabrizio.salmi@gmail.com. Do not open a public issue for a
vulnerability.

Expect an acknowledgement within a few days. This is a spare-time project, so
there is no formal SLA and no bug bounty.

## Supported versions

Only the latest commit on `main` (and the image built from it) is supported.

## Threat model — read this before exposing the app

`pdf-ocr` is designed as a **single-tenant, self-hosted tool**. It accepts an
uploaded PDF and hands it to Poppler and an OCR engine, both of which are large
C/C++ codebases that parse untrusted input. If you put it on the public internet
you are exposing that parsing surface to anyone.

What the app does provide:

- `SECRET_KEY` is required outside development, so session cookies are signed
  with a stable key.
- Uploads must have a `.pdf` extension **and** the `%PDF-` magic bytes.
- Upload size (`MAX_UPLOAD_MB`, default 64 MB) and page count (`MAX_PAGES`,
  default 200) are capped; pages are rendered in small batches so peak memory
  does not scale with document length.
- Engine, output format and language are validated against allowlists.
- A conversion is only readable by the browser session that started it, and the
  result's path on disk is never sent to the client.
- Baseline security headers, including a same-origin CSP; no third-party assets
  are loaded (Tailwind is vendored under `static/vendor/`).
- The image runs as an unprivileged user (uid 10001). The read-only root
  filesystem, dropped capabilities and `no-new-privileges` are **runtime**
  options, not properties of the image: `docker-compose.yml` sets them, and the
  `docker run` command in the README passes the equivalent flags. A plain
  `docker run` without them still runs unprivileged, but with a writable root
  filesystem and the default capability set.
- Uploads, results and finished tasks are deleted automatically. A cancelled
  conversion leaves nothing behind at all.
- A conversion whose worker dies is reported as failed after
  `STALE_TASK_TIMEOUT` rather than pending forever.
- Static analysis (CodeQL, `security-extended`) runs on every push and pull
  request, plus weekly, because its query pack updates independently of this
  code.

What it does **not** provide, and what you must add yourself before exposing it:

- **No authentication.** Anyone who can reach the port can convert documents.
- **No rate limiting.** OCR is CPU- and memory-intensive; a handful of
  concurrent uploads will saturate a small host. Put it behind a reverse proxy
  that enforces limits, or behind an authenticating proxy.
- **No sandboxing of Poppler or Tesseract** beyond the container boundary.
- **No encryption at rest** for uploaded or converted files.
- **No CSRF tokens.** The session cookie is `SameSite=Lax`, which keeps
  mainstream browsers from attaching it to a cross-site POST, but that is a
  mitigation rather than a guarantee. State-changing routes rely on the
  session-ownership check, not on a token.
- **No audit log.** Conversions are logged, but nothing is retained about who
  did what — there is no identity to retain.

The intended deployment is on a private network, or behind an authenticating
reverse proxy, with TLS terminated in front of it (set
`SESSION_COOKIE_SECURE=true` in that case).

## Never set these in a deployment

- **`FLASK_ENV=development`** turns on the Werkzeug debugger, which allows
  arbitrary code execution over HTTP. It exists for local development only.
- **`PDF_OCR_TESTING=1`** makes the app start without `SECRET_KEY`, using a key
  generated per process. The test suite sets it so importing the app does not
  require configuration; in a deployment it means every worker signs cookies
  with a different key and every restart invalidates all sessions.

Neither is read from `.env.example`, and neither is set by the image.