Accans

Security posture

Application-, container-, and CI/CD-layer security practices for the Digital Sovereignty Heatmap.

Inhoud in het Engels — pagina-navigatie blijft Nederlands.

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: true in 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: read for CI)
  • Release workflow scans image before pushing to GHCR

Application Security

  • Environment-based configuration (no secrets in code)
  • .env files excluded from version control
  • Request body size limits: 16 KB (memo), 2 KB (telemetry)
  • Rate limiting on POST endpoints (in-memory token bucket, per client IP)
  • Zod schema validation on all POST request bodies
  • No prompt payloads logged in Claude integration

Telemetry & Privacy

Telemetry is disabled by default (TELEMETRY_ENABLED=false). When enabled, the following privacy guarantees apply:

Guarantee Implementation
No raw events stored Server increments aggregated counters only (metrics_counters table)
Strict allowlists Every field is validated against a fixed enum — no free text accepted
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
Double opt-in Server must have TELEMETRY_ENABLED=true AND user must opt in via UI toggle
k-anonymity Public /trends page only shows buckets with count >= K_ANON_THRESHOLD (default 25); 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)
Terug naar documentatieDit document is een statische snapshot van de canonieke bron in onze codebase. Wijzigingen worden gepubliceerd bij elke nieuwe build.
Security posture · Digitale Soevereiniteit Heatmap