# Security

Invoices contain your customers' names, addresses, amounts and tax numbers. This page describes how BelegDock protects them, and also what remains readable without the key.

## Sign-in

- **Two-factor sign-in is mandatory.** Without an authenticator app, BelegDock opens no workspace. For emergencies there are eight recovery codes, each usable once.
- **Security notices by email** when your password is changed or reset, two-factor sign-in is turned off or a recovery code is used.
- BelegDock stores passwords only as Argon2id hashes. Sessions, password reset links, recovery codes and API keys are stored only as hashes.
- Sign-in attempts are limited per account and per source. Failed attempts from the internet cannot lock your account.
- Session cookies are `HttpOnly`, `Secure`, `SameSite` and bound to the domain (`__Host-`). Every change must come from BelegDock's own pages.

## Encryption

BelegDock encrypts with AES-256-GCM, a method that detects any modification of the data. Stored encrypted are:

- the invoice data from Stripe and from the API, every XRechnung, every PDF and every validation report
- email addresses, names of people and workspaces, company details, email templates and logos
- credentials for Stripe and Resend, webhook secrets and two-factor keys
- support requests, pending jobs and every outgoing email including its attachments

What BelegDock needs to look up, such as an email address at sign-in or the order number of an API invoice, is stored as a hash with a secret key (HMAC). Someone who has only the database cannot even check whether a particular address has an account.

The key is not stored in the database. A copy of the database or a backup is useless without it.

**Readable without the key** is only the operational data BelegDock needs to work: internal IDs, timestamps, statuses, invoice numbers, invoice amounts, plan and usage counters, and the IDs of Stripe objects.

## Workspace isolation

Every workspace is isolated in the database itself: PostgreSQL lets each query see only the rows of the workspace it runs for (row-level security). A bug in a single query therefore cannot return another workspace's invoices. A Stripe account can be connected to only one workspace.

## Stripe and API

- BelegDock has **read-only access** to your Stripe account. It changes nothing there, creates nothing and triggers no payments.
- You see an API key exactly once; BelegDock stores only its hash. Test keys and live keys are separate; every key can be revoked immediately.
- BelegDock accepts webhooks from Stripe and Resend only with a valid signature and processes each event exactly once.

## Operations

- All connections use HTTPS with HSTS. Cloudflare sits in front of the server and drops requests for addresses that do not exist at BelegDock before they reach the server.
- A strict Content Security Policy, no third-party scripts in the app, no embedding in other sites.
- The application runs in containers without root privileges and with a read-only file system. The KoSIT validation service is not reachable from the internet.
- Issued API invoices are locked against modification and deletion in the database.

## Service providers

Hetzner runs the servers, Cloudflare the network in front of them, Resend sends emails, and Stripe processes payments and supplies the data of your Stripe invoices. Invoice data goes only to Resend, for the emails to the recipients you specify, and through the Stripe app into your own Stripe Dashboard.

## Limits

No system is immune to attack. Whoever takes over the running server can also reach the key; separate credentials, up-to-date software and the isolation described above help against this, but there is no guarantee. BelegDock has no ISO 27001 or similar certification.
