Skip to content

Security

  • By key. A request acts for the tenant of its API key and no other. Asking for another tenant’s template, font or output gets the same answer as asking for one that exists nowhere, so nothing tells a tenant that another’s exists.
  • By the database. Every tenant’s queries run under PostgreSQL row-level security, as the role paprika_app with the tenant set: the database shows and accepts that tenant’s rows only, whatever the query says. Work across tenants (looking up a key, the admin API, migrations, the sweep) runs as the user in PAPRIKA_DATABASE_URL.
  • By storage. Every file is kept under its tenant’s folder; tenants never share an upload. Only the fonts bundled with Paprika are shared.
  • By key secrets. Only a hash of a key’s secret is kept. Keys can expire, are revoked at once, and are refused while their tenant is suspended or deleted.
  • Nothing is fetched from the internet. An image value names an image in the template or one of the tenant’s uploaded resources; Paprika never downloads a URL it is given, so it can’t be used to reach your internal network.
  • Webhooks don’t reach private addresses. A webhook endpoint must be HTTPS on a public address: this host, private networks, link-local addresses (cloud metadata among them) and reserved ranges are refused when the endpoint is registered and again at every delivery, on the address its name resolves to then. For an install whose systems are all on a private network, PAPRIKA_WEBHOOK_ALLOW_PRIVATE=true allows private addresses and plain HTTP.
  • Unknown host names. Each service answers only the host names it is given (PAPRIKA_HOSTS), and a browser request from another site that changes something is refused.
  • Oversized input. Packages, images, batches and queues have limits; fonts and images are checked before they are used.

Uploaded fonts and images are parsed by the render process itself. Until it runs isolated from the rest of the service, accept uploads only from tenants you trust: in a self-hosted install, your own organisation.

Secret What it guards Rotating it
PAPRIKA_ADMIN_TOKEN The admin API List the new token next to the old, restart, then remove the old
PAPRIKA_SIGNING_KEYS Signed download links to outputs Put the new key first (it signs, every key listed verifies), later drop the old
The database password Everything Change it in PostgreSQL and in PAPRIKA_DATABASE_URL together
API keys A tenant’s data, within the key’s scopes Rotate: the old key works on for 24 hours
Webhook secrets That a delivery came from Paprika Rotated by the tenant; the old secret signs on for 24 hours

.env.example holds development placeholders only: never run with them.

  • The admin API (/v1/admin). Route it only from where you administer, or not through the proxy at all and reach it on the machine.
  • PostgreSQL and the data volume.
  • The workers’ probes (port 4180).

The API (/v1, without /v1/admin) and the editor are meant to be reached, over HTTPS through your proxy.

The log has one line per API request: time, request id, method, path, status, duration, size and tenant, and an error’s code and message. It never holds a key’s secret or a file’s contents.