Security and Data Protection

Security test report, 2026-10-15

A self-conducted security test of the Klarbefund app and the demo hub, by the project, with the tools the repository can run: static analysis, dependency and container scanning, secret scanning, configuration tests and anonymous black-box checks of the live hub. It is not an independent penetration test. What such a test would add is listed at the end. Every number here is reproducible with the commands given; the raw reports of this run are not committed (they carry paths), the scripts are.

Item Value
Commit 64737039 on feat/186-insurer-report (the fixes below were made on the same branch)
Date 2026-10-15, 18:34 to 18:58 UTC
Machine macOS 26.7.1, Xcode 26 (Swift 6.4), syft 1.51.1, grype 0.118.0, trivy 0.74.0, gitleaks 8.30.1, semgrep 1.176.0, CodeQL CLI
Scope clients/ios (the app), ui, services/*, the Dockerfiles, the Kubernetes manifests, the live hub ehds.mabu.red from outside
Not in scope The EDC connectors and Keycloak internals (the hub's own threat model, threat-model.md), Apple's platform, the model providers behind ADR-034

1. Method

Layer Tool, what it checked Command
Static analysis, Swift CodeQL, security queries over 31 app and 51 Shared files clients/ios/Scripts/codeql-swift.sh
Static analysis, TS/JS CodeQL in CI on every push (codeql.yml); Semgrep with the repository's own rules locally scripts/security/scan-repo.sh
Dependencies syft bills of materials, grype and npm audit against them scripts/security/generate-sbom.sh, scan-repo.sh
Filesystem trivy: vulnerabilities, secrets, misconfiguration in Dockerfiles and Kubernetes manifests scan-repo.sh
Secrets gitleaks over the tree and the last 200 commits scan-repo.sh
Build configuration SecurityPostureTests (9 tests): entitlements, privacy manifest, ATS, cryptography, PKCE, HealthKit cd clients/ios && swift test --filter SecurityPostureTests
Behaviour The unit suites: 419 Swift tests, 2488 UI tests, 22 proxy tests on this branch swift test, npm test
Live hub, anonymous 40 black-box checks: route gates, redirects, dotfiles, CORS, headers, TLS versions, Keycloak console scripts/security/check-hub-blackbox.sh

2. Results

Check Result
CodeQL, Swift 0 findings, 0 accepted
Semgrep, local rules 0 findings
grype over the five bills 0 matches (ui 530 components, proxy 180, epa-ingest 29, claude-federation 20, app 20 Apple frameworks, 0 third-party)
npm audit, production, four lockfiles 0 critical, 0 high, 0 moderate, 0 low
trivy, vulnerabilities 0 after the fix below (6 before, all in one Python package of the guideline scripts)
trivy, secrets 0
trivy, misconfiguration 0 after the fixes (3 HIGH, 2 MEDIUM and 12 LOW before; the last ten, the missing health checks of F6, closed 2026-10-16)
gitleaks, tree 0 after review (2 before, both reviewed and allowlisted with the reason)
gitleaks, last 200 commits 0
hadolint 5 info, 0 warning, 0 error
shellcheck, error severity 0
SecurityPostureTests 9 of 9 pass
Unit suites all pass
Live hub, black-box (second run, online, 20:37 UTC) 44 of 46 checks pass: every API route refuses an anonymous caller (401), protected pages redirect, no dotfile served, no CORS reflection, all five security headers, TLS 1.0 and 1.1 refused, metrics closed. Two fails: one expectation of the script (a route that does not exist, fixed) and F10 below
Live hub, black-box not run to completion: the hub was in off-hours mode (ADR-053, stops 18:13 UTC), every API route answered 503 and the script exits 3 by design. Repeat Mon to Fri 05:17 to 18:13 UTC. What the run did establish: TLS 1.0 and 1.1 refused, 1.2 and 1.3 offered (ECDHE, ChaCha20-Poly1305), certificate valid to 2027-02-08; no CORS origin reflected; Keycloak admin console, metrics and health not reachable from the internet (404); the dotfiles a scanner tries are rewritten to the offline page, not served

3. Findings

# Severity Finding Where Action
F1 High docling 2.131.0 with three published advisories (two high) scripts/leitlinien/uv.lock Fixed: uv lock --upgrade-package docling, now 2.139.0; grype and trivy report 0
F2 High (scanner) Three Dockerfiles without a USER instruction (trivy DS-0002) load-tests/azure, jad/keycloak-theme, scripts/azure/vault-unseal Fixed: the k6 and Keycloak images already ran unprivileged, now stated; the vault-unseal sidecar drops to vault after its root-only apk add
F3 Low gitleaks: IdentityHub superuser key in the compose stack's env file jad/issuerservice.env (tracked) Reviewed: the published development default of the local JAD stack; Azure takes keys from Vault (ADR-046). Allowlisted with the reason in .gitleaks.toml
F4 Info gitleaks: a public search key of the AWMF guideline register embedded in a fetch script scripts/leitlinien/fetch_index.py Reviewed: the register's own public page embeds it; grants nothing a browser lacks. Allowlisted with the reason
F5 Medium Kubernetes manifest: image tag :latest and a ConfigMap flagged as sensitive content k8s/health-dataspace-ui.yaml Fixed 2026-10-15: tag pinned to the package version; the flagged ConfigMap keys are a public client id and issuer URL (the secrets already sit in the Secret), recorded as a reviewed false positive in .trivyignore; trivy reports 0 on the manifest
F6 Closed No HEALTHCHECK in ten Dockerfiles ui, services/*, scripts/azure/*, others Fixed 2026-10-16: every image carries a HEALTHCHECK matched to what it serves: the hub's /api/health, the proxy's and the explanation service's /health, the crawlers' Prometheus endpoint, Keycloak's /health/ready and Grafana's /api/health through a bash socket (neither image has curl), vault status for the unseal sidecar; the two one-shot jobs say HEALTHCHECK NONE. Container Apps still takes its probes from the app definition. trivy reports 0 misconfigurations over the repository
F7 Low Kubernetes manifest runs with UID and GID below 10000 k8s/health-dataspace-ui.yaml Fixed 2026-10-15: runs as 10001
F8 Observe On the off-hours page the response carried Referrer-Policy and Permissions-Policy but not HSTS, X-Frame-Options or X-Content-Type-Options, although next.config sets all five for every path ehds.mabu.red/, off hours Closed 2026-10-15 20:37 UTC: online, all five headers are present on /; only the off-hours page lacked them
F10 Closed Keycloak admin console reachable from the internet: auth.ehds.mabu.red/admin/master/console/ answers 200 (login still required; found when the black-box checks ran online at 20:37 UTC) Keycloak ingress Fix scripted: scripts/azure/guard-keycloak-console.sh adds a Caddy sidecar that answers 403 for /admin/*/console/* and proxies the rest, keeping the admin REST API (which the hub and the provisioning scripts use with a token) reachable; Keycloak's own docs say hostname-admin does not block the console. Applied and verified 2026-10-15 21:19 UTC: with Keycloak started briefly behind the guard, discovery 200, the console paths 403, the admin REST API 401 without a token, the token endpoint answering, and check-keycloak-health.sh reporting the realm served from a working database; then stopped again. Rollback: the definition saved under ~/.mvhd/keycloak-rollback/
F9 Closed No app lock, no jailbreak detection, no certificate pinning in the app clients/ios Decided 2026-10-15 (ADR-060): app lock implemented, on by default; pinning and jailbreak detection declined with reasons

4. What an independent test would add

This report is the project testing itself with deterministic tools. It does not replace, and the self-assessment does not claim, an independent penetration test. Such a test, by a BSI-recognised laboratory or a contracted tester, would add:

  1. A release build on a device: runtime inspection of the Keychain items, the sealed store and the file protection classes with the phone locked and unlocked; the cable test repeated by someone else.
  2. Authenticated black-box testing of the hub as each of the seven roles, in office hours: horizontal access between patients (refuseForeignRecord), the app token's scope, rate limits under load, the App Attest verification with a replayed or forged attestation.
  3. The TR-03161 part 1 requirement list, item by item against the official document, which this project assesses only at the level of its objective groups (docs/klarbefund/self-assessment.md).
  4. Keycloak and EDC configuration review beyond what the public endpoints show.

Until then, the status in the self-assessment reads "tested by the project, independent test open", and this document is the evidence for the first half of that sentence.