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:
- 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.
- 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. - 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). - 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.