Skip to content

Phase 23: Security Assessment & BSI C5 Plan — Demo Hardening + Production Roadmap #4

@ma3u

Description

@ma3u

Context

This is a Minimum Viable Demo (MVD) of an EHDS-compliant health dataspace. The stack is intentionally run in dev-mode (no TLS, hardcoded secrets, in-memory Vault) so it can be deployed with a single docker compose up. The security assessment has two distinct tracks:

Track Goal When
🟢 Demo / MVD Prove the architecture is sound; fix real code-level risks; document the threat model Now — this issue
🔵 Production Full infrastructure hardening for a live deployment; BSI C5 audit Future milestone

The demo track does not require replacing Vault dev-mode or adding TLS — those are documented as known dev-only shortcuts. It does require fixing injection risks, proving auth works correctly, and producing the BSI C5 gap analysis that shows what would change in production.


Why BSI C5?

BSI C5 is the German Federal Office for Information Security's cloud security catalogue. Under EHDS Article 50(6), secondary-use health dataspaces must demonstrate conformance with a recognised security framework. BSI C5 is the applicable standard for EU health infrastructure. The demo proves we understand what needs to change and why — the production track delivers the actual changes.


🟢 Demo / MVD Phase — Implement Now

These are real security fixes that can and should be done in the demo codebase today. They demonstrate security maturity without requiring infrastructure changes.

D1 — Fix Cypher Injection Risk (CRITICAL for any phase)

File: ui/src/app/api/admin/audit/route.ts
Risk: The WHERE clause is built by string-escaping user input (replace(/'/g, "\\'")). This is insufficient — parameterised Cypher must be used.
BSI C5: DEV-07 (secure coding)

// ❌ Current (injection risk)
const where = `WHERE t.status = '${params.status?.replace(/'/g, "\\'")}'`

// ✅ Fix: parameterised Cypher
const result = await runQuery(
  `MATCH (t:TransferEvent) WHERE t.status = $status RETURN t`,
  { status: params.status }
)

Demo value: Shows we understand and fix OWASP A03 (Injection) at the code level.


D2 — Add Content Security Policy Headers

File: ui/next.config.ts
Risk: No CSP → XSS can exfiltrate session tokens from the browser.
BSI C5: DEV-07, NC-03

const securityHeaders = [
  { key: 'Content-Security-Policy',
    value: "default-src 'self'; script-src 'self' 'unsafe-eval'; connect-src 'self' http://localhost:*" },
  { key: 'X-Frame-Options', value: 'DENY' },
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
]

Demo value: Shows headers are correct even in dev mode; easy to tighten in production.


D3 — Security E2E Test Suite (__tests__/e2e/security/)

Playwright tests that prove the auth and middleware actually work:

Test ID What it proves
SEC-01 GET /admin without session → redirects to /auth/signin (not 200)
SEC-02 GET /compliance without session → redirects to /auth/signin
SEC-03 GET /patient/profile without session → redirects to /auth/signin
SEC-04 GET /api/admin/policies without session → 401 or redirect
SEC-05 GET /api/graph without session → 200 (public, not protected)
SEC-06 GET /catalog without session → 200 (public, not protected)
SEC-07 Response headers include X-Content-Type-Options: nosniff
SEC-08 Response headers include X-Frame-Options: DENY
SEC-09 localStorage on / does not contain NEXTAUTH_SECRET or any token
SEC-10 /auth/signin page does not expose Keycloak client secret in page source

Demo value: These tests run in CI and prove that route protection is not accidentally disabled by a future refactor.


D4 — npm audit in CI (Zero High/Critical CVEs)

Add to .github/workflows/test.yml:

- name: Dependency vulnerability scan
  run: |
    cd ui && npm audit --audit-level=high
    cd ../services/neo4j-proxy && npm audit --audit-level=high

Demo value: Shows the dependency supply chain is monitored. Any new CVE fails the build.


D5 — detect-secrets baseline (no secrets in repo)

pip install detect-secrets
detect-secrets scan --baseline .secrets.baseline

Commit .secrets.baseline so future commits are checked against it in pre-commit.

Demo value: Proves no real credentials are accidentally committed — .env.example values are clearly placeholders.


D6 — DSP / DCP Threat Model Document

File: docs/security/threat-model.md

Document the 7 protocol-level attack vectors for DSP and DCP with:

  • Attack description
  • Current mitigation in the demo stack (e.g. "EDC-V validates @id idempotency")
  • Production hardening required (e.g. "add request signing with participant private key")

Demo value: Shows evaluators that we have analysed the protocols — not just wired them up.


D7 — BSI C5 Gap Analysis Document

File: docs/security/bsi-c5-gap-analysis.md

For each of the 10 BSI C5 domains in scope:

  • Current demo status: Compliant / Partial (dev shortcuts) / Not yet implemented
  • What the demo proves: architecture is correct
  • What production would add: TLS everywhere, real key management, etc.

Demo value: The gap analysis IS the deliverable for a demo — it shows we know what C5 requires and have a credible plan.


D8 — Rate Limiting on Admin API Routes

File: ui/src/middleware.ts
Add a simple in-memory rate limiter for /api/admin/* routes (e.g. max 60 req/min per IP).

Demo value: Shows awareness of API abuse risk; trivial to replace with Redis-backed limiter in production.


🔵 Production Phase — Document Now, Implement Later

These require infrastructure changes and are out of scope for the demo. They are documented here so a production team has a clear checklist.

Infrastructure Hardening (not needed for demo)

# Finding Location BSI C5 Production fix
P1 Vault root token used by all services identityhub.env, issuerservice.env COS-04 AppRole per service with least-privilege policy
P2 Vault single key-share (-key-shares=1) vault-init-or-unseal.sh COS-04 Shamir 3-of-5, keys in HSM or cloud KMS
P3 Vault init.json with unseal key on disk vault-init-or-unseal.sh COS-04 Auto-unseal via AWS KMS / Azure Key Vault
P4 tls_disable = true in Vault vault.hcl NC-03 TLS with internal CA; mTLS between services
P5 sslRequired: none in Keycloak keycloak-realm.json NC-03 sslRequired: all; external TLS termination
P6 edc.iam.did.web.use.https=false identityhub.env, issuerservice.env NC-03 HTTPS DID documents with valid certificate
P7 NATS no TLS, no auth nats.conf NC-05 NATS TLS + NKey/Token authentication
P8 PostgreSQL no sslmode=require All JDBC URLs NC-03 sslmode=require + client certificate
P9 Traefik dashboard insecure (port 8090) docker-compose.jad.yml NC-01 Disable dashboard or protect behind VPN
P10 Neo4j HTTP API over plain HTTP api/admin/policies/route.ts NC-03 Neo4j over BOLT+TLS; remove HTTP API usage
P11 IssuerService super-secret-key issuerservice.env IS-07 Vault-managed signing key; rotate on schedule
P12 Provisioner has full Vault sudo bootstrap-vault.sh IS-05 Least-privilege provisioner policy; break-glass only
P13 Vault JWT clock skew 60 s bootstrap-vault.sh COS-01 Reduce to 10 s; add token binding
P14 Neo4j hardcoded password docker-compose.yml COS-01 Vault dynamic DB credentials
P15 Containers may run as root docker-compose.jad.yml INF-07 user: 1000:1000 in all compose services

Protocol Penetration Testing (production pentest, not demo)

Full red-team exercises against DSP and DCP:

  • Replay attack with captured ContractRequest messages
  • Token binding bypass across participant contexts
  • StatusList2021 availability attack (fail-open vs fail-closed)
  • Data plane SSRF via crafted dataAddress.endpoint

Container & Supply Chain Security

  • trivy image scan for all EDC-V, Keycloak, Vault images
  • SBOM generation per image (SPDX format)
  • OWASP Dependency-Check for EDC-V Java dependencies
  • Signed container images (cosign)

Implementation Order (Demo Phase)

D1 → Cypher injection fix          (30 min — code change)
D4 → npm audit in CI               (15 min — workflow change)
D5 → detect-secrets baseline       (20 min — tooling)
D2 → CSP headers in next.config    (30 min — config change)
D3 → Security E2E test suite       (2 h — new test file)
D8 → Rate limiting middleware       (1 h — middleware change)
D6 → Threat model document         (2 h — documentation)
D7 → BSI C5 gap analysis           (3 h — documentation)

Acceptance Criteria (Demo Phase)

  • D1: api/admin/audit/route.ts uses parameterised Cypher — no string interpolation in WHERE clauses
  • D2: CSP + X-Frame-Options + X-Content-Type-Options headers on all Next.js responses
  • D3: __tests__/e2e/security/ contains ≥ 10 passing security tests (SEC-01–SEC-10)
  • D4: npm audit --audit-level=high passes in CI for UI and neo4j-proxy
  • D5: .secrets.baseline committed; pre-commit detect-secrets hook active
  • D6: docs/security/threat-model.md covers DSP + DCP protocol attack vectors
  • D7: docs/security/bsi-c5-gap-analysis.md covers all 10 BSI C5 domains with demo vs production status
  • D8: Rate limiting active on /api/admin/* routes

References

/cc @ma3u

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentationenhancementNew feature or requestsecuritySecurity assessment and hardening

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions