Security best practices
These rules come from real incidents or from checks made on the Bemba servers. They complement JWT authentication, the password policy and rate limiting.
Accounts
- One account per person; never shared credentials, not for an enumerator nor for a contractor.
- Deactivate an account on the day the person leaves; review every quarter the accounts without a recent sign-in.
- Minimal role and minimal scope (institution and projects). National registry access is an exception, granted by name.
- Limit
ROLE_ADMINto two or three people: it bypasses partitioning and holds every permission. - Encourage two-factor authentication (TOTP app), available to every user under My profile → Security, and require it for administrators.
- Never send a password by messaging; prefer the "forgot password" link or the magic link.
Personal data
The registry holds named data on vulnerable households.
- Sensitive grievances (sexual exploitation and abuse, sexual harassment) are only visible with
plaintes.sensibles_view; grant it to a very small circle. - Photos and attachments are only served through signed links valid for 12 hours: do not copy them into shared documents.
- Excel or CSV exports take data out of partitioning: store them in a protected space and delete them after use.
- For a test or a demo, use pre-production, never production data copied to a workstation.
Secrets
- No secret in the Git repository, in this documentation, in a ticket or in a chat. A secret committed and then "removed" by a new commit remains readable in the history: it must be changed.
- Secrets live in the environment file read by Compose on each server (
~/pch-sig/.env.prodin production) and inbackend/.env, all in mode 600. JWT keys are inbackend/config/jwt/, private key in mode 600. - After a suspected leak, or when someone who had access leaves: apply the rotation procedure
docs/ROTATION_SECRETS.mdin the repository (database, JWT, SMTP, Anthropic and Cohere API keys, Kobo tokens).
Servers
| Rule | Status |
|---|---|
| Only 22 (SSH), 80 and 443 published on the Internet, plus 2222 (GitLab SSH, pre-production) and 2525 (SMTP relay, open to the 220 server only) | applied; exporters and Loki bound to 127.0.0.1 |
| SSH by key only, fail2ban active | applied |
Always docker compose -f <file> | without -f, Compose starts the Symfony skeleton, with a default-password database |
Console commands as www-data | a command run as root breaks the cache (500 errors) |
| Security headers and CSP | set by the frontend Nginx, see HTTPS |
Logs and audit
- User actions (sign-ins, changes, deletions, exports) are under Administration → Audits.
- Technical logs are kept for one year in Loki, errors go to Sentry. See Logs.
- Never log a password, a token or a full payment account number; do not paste them into a report.
Backups
The database is backed up daily and replicated to the recovery host; see Backup for the exact list and the points still open. A backup is only worth something if its restore has been tested: run a restore drill at least once a quarter.
In case of incident
- Contain: deactivate the accounts involved; if a secret leaked, change it immediately.
- Cut sessions: emptying the
refresh_tokenstable signs everyone out within an hour. - Understand: application audit, Loki logs, Traefik logs.
- Fix and document: cause, period, data exposed.
- Inform UCP-PCH, and the World Bank if beneficiary data may have been exposed.
Regular checks
| Frequency | Check |
|---|---|
| Daily | API health, empty failed e-mail queue, errors in Sentry |
| Weekly | disk space, backups present, abnormal failed sign-ins in the audit |
| Monthly | Ubuntu and Docker image security updates |
| Quarterly | account and role review, restore drill |
| Yearly | secrets rotation, review of this page |