Security & Secrets

Notipo stores credentials for every connected WordPress site and Notion workspace. Treat the host like you would any other auth-server: minimal access, encrypted backups, rotated keys.

ENCRYPTION_KEY

Every WordPress application password and Notion integration token in the database is encrypted with AES-256-GCM using the key set in ENCRYPTION_KEY. This is the single most important secret in your stack.

Rotation breaks credentials
Rotating ENCRYPTION_KEYdoes not re-encrypt existing data. After rotation, every stored credential becomes unreadable and users must reconnect WordPress and Notion. There's currently no built-in re-encrypt-on-rotate flow — if you need one, it's a small migration script you'd have to run manually.

Generate it once with:

openssl rand -hex 32

Store the value somewhere safer than just the .envfile on the host. A password manager, a private vault, or an encrypted file on a different machine all work. The DB backup alone is useless without it; that's by design.

Per-user API keys

Each user gets a personal API key issued at signup, used for the REST API, the CLI (NOTIPO_API_KEY), and the MCP server. Keys are scoped to that user's tenant — they cannot read or write data from other tenants.

The frontend stores a hash of the key in localStoragerather than the raw value, so a stolen browser session can't leak the API key directly. The API does not currently support rotating a user's key from the UI — if a key is compromised, the user must delete and recreate their account, or you can reissue manually via the database.

The admin API_KEY

The API_KEY environment variable gates the /api/admin/* routes — listing all tenants, creating tenants, retrieving any tenant's decrypted WordPress credentials, and impersonating any user via the X-Impersonate-Tenant header. Treat it as a root credential.

On a single-user instance you'll rarely need it. On a multi-tenant instance, never paste it into a frontend, never put it in URLs, and rotate it via .env + a stack restart if it leaks.

Rate limiting

The API has built-in rate limits via @fastify/rate-limiton auth and password-reset routes. They're sensible defaults but were tuned for the hosted product's traffic profile. If you're running a private instance, you can leave them; if you're running a public-signup instance, consider tightening them via a fork.

Hardening checklist

  • Lock down signups after creating your account: ALLOW_SIGNUP=false in .env, then docker compose up -d.
  • Restrict the host firewall to inbound 80 + 443 only. Postgres should never be reachable from the internet — the bundled compose stack only exposes it on the internal Docker network.
  • Pin to a specific minor tag (ghcr.io/kfuras/notipo-api:1.2) instead of :latest so a release on main can't silently swap your container on the next docker compose pull.
  • Back up Postgres + the encryption key separately. A single-source compromise should never have both.
  • Subscribe to release notifications on GitHub so you see security patches as they ship. The release notes flag CVE bumps explicitly.
  • Watch the security advisories page for the project.

Reporting a vulnerability

Don't open a public issue for security problems. Use GitHub's private security advisory flow at github.com/kfuras/notipo-app/security/advisories/new or email security@notipo.com. See SECURITY.md for the full policy.