Security posture
Application-, container-, and CI/CD-layer security practices for the Digital Sovereignty Heatmap.
Contenu en anglais — la navigation de la page reste en français.
Security Posture
This project follows security best practices across application, container, and CI/CD layers.
Current Measures
HTTP Security Headers
Applied via Next.js middleware (src/middleware.ts) to all routes:
| Header | Value |
|---|---|
| Content-Security-Policy | default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' fonts.googleapis.com; font-src 'self' fonts.gstatic.com; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self' |
| X-Frame-Options | DENY |
| X-Content-Type-Options | nosniff |
| Referrer-Policy | strict-origin-when-cross-origin |
| Permissions-Policy | camera=(), microphone=(), geolocation=() |
API routes additionally receive Cache-Control: no-store.
Container Security
- Multi-stage Docker build — production image contains only runtime dependencies
- Non-root user (
nextjs, UID 1001) in the runtime stage - Read-only root filesystem (
read_only: truein docker-compose) - Resource limits: 512 MB memory, 1 CPU
- Restrictive data directory permissions (750)
- Alpine-based images for minimal attack surface
- OCI image labels for provenance
- Healthcheck built into the container
CI/CD Security
- Trivy — container image vulnerability scanning (CRITICAL + HIGH)
- gitleaks — secret detection in git history
- pnpm audit — dependency vulnerability scanning
- Minimal GitHub Actions permissions (
contents: readfor CI) - Release workflow scans image before pushing to GHCR
Application Security
- Environment-based configuration (no secrets in code)
.envfiles excluded from version control- Request body size limits on every POST endpoint
- Rate limiting on POST endpoints (in-memory token bucket, per client IP; limits are deliberately not published)
- Zod schema validation on all POST request bodies
- No prompt payloads logged in Claude integration
Telemetry & Privacy
Telemetry is on by default (opt-out): only an explicit TELEMETRY_ENABLED=false disables it server-side, and visitors can switch it off per browser in the footer. This is defensible because nothing that is collected qualifies as personal data. The following privacy guarantees apply:
| Guarantee | Implementation |
|---|---|
| No raw events stored | Server increments aggregated counters only (metrics_counters table) |
| Strict allowlists | Every telemetry field is validated against a fixed enum. The one free-text input, application names from the list import that the catalogue does not recognise, is sanitised (anything resembling an e-mail address, URL or number is dropped) and counted per name without any other context |
| No IP addresses stored | Client IP is used for rate limiting only (in-memory, never persisted) |
| No personal data | No usernames, emails, sessions, or device fingerprints |
| Opt-out on both layers | Server: on unless TELEMETRY_ENABLED=false. User: on unless switched off in the footer; the explicit choice is stored in localStorage (no cookie) |
| k-anonymity | Public /trends page only shows buckets above a configurable minimum count; smaller buckets are grouped as "Other" |
| Score bands only | Raw scores (0-100) are bucketed into 3 bands (0-33, 34-66, 67-100) before transmission |
What is collected (when both server and user opt in):
- Event type:
assessment_completed,vendor_selected,memo_generated - Score band (not raw score)
- Category and vendor slug (from curated allowlist only)
- Context enum values (sector, data classification, criticality, EU residency)
What is NOT collected:
- IP addresses, user agents, or device identifiers
- Raw assessment scores
- Free-text input of any kind
- Timing data, session identifiers, or referrer information
Reporting Vulnerabilities
If you discover a security vulnerability, please report it responsibly by opening a private security advisory on the GitHub repository.
Future Considerations
- Authentication & authorisation strategy
- CORS policy for cross-origin API access
- Dependency update automation (Dependabot / Renovate)