--- status: "accepted" date: 2026-10-11 decision-makers: ["Moritz Graf", "Antigravity"] consulted: [] informed: [] --- # ADR 0005: Personal Data Isolation, Authentik Authentication Boundaries, and Hosting Segregation ## Context and Problem Statement On 2026-10-11, during the development of mobile-first PWA dashboards for baby monitoring (Mila Graf) and personal nutrition/Garmin energy balance (Moritz Graf), personal health, sleep, diaper, and nutrition data was temporarily made available at unauthenticated root URLs: - `https://hosting.haumdaucher.de/mila.html` - `https://hosting.haumdaucher.de/moritz.html` While [ADR 0004](0004-structured-personal-data-datastore.md) established the conceptual routing design (`/family/*` protected by Traefik ForwardAuth, public recipes at `/recipes/*`), a gap analysis revealed critical systemic vulnerabilities: 1. **Unapplied Kubernetes Ingress:** The ForwardAuth Ingress (`minio-hosting-family`) declared in `k8s/minio/hosting-ingress.yaml` was never applied to the Kubernetes cluster. 2. **Missing Authentik Identity Provider:** Authentik had no Proxy Provider or Application configured for `hosting.haumdaucher.de`, returning HTTP 404 on ForwardAuth checks. 3. **Dual-Publishing Anti-Pattern:** Dashboard generator scripts (`update_website.py`, `update_moritz_website.py`) mirrored files to root (`/mila.html`, `/moritz.html`) for convenience, completely bypassing the planned `/family/` path boundary. 4. **Lack of Automated Ingress & Storage Auditing:** No automated test or CI/Cron audit prevented personal data from being uploaded to public prefixes. How do we establish a permanent, verifiable, and automated security boundary that guarantees personal and family data can never be exposed publicly without authentication? --- ## Decision Drivers * **Zero Public Exposure for Personal Data:** Health, sleep, nutrition, body, and family data must NEVER be accessible without authentication. * **Defense-in-Depth:** Security must not rely on a single layer (e.g. secret URLs or developer memory). Protection must be enforced at identity provider, reverse proxy, storage bucket, and generator script levels. * **Declarative Infrastructure as Code (ADR 0003):** All Authentik providers, policies, groups, and Kubernetes Ingresses must be versioned in Git and applied via Terraform/Kubectl. * **Automated Guardrails & Fast Feedback:** Continuous audits must detect and quarantine any unauthorized files outside protected paths. * **Seamless PWA Experience:** The PWA capabilities (manifest, service worker, offline shell) must function within the authenticated cookie session without breaking ForwardAuth. --- ## Decision Outcome Chosen Strategy: **Multi-Layer Boundary Enforcement with ForwardAuth, Strict Storage Segregation, and Continuous Auditing**. ### 1. Storage & URL Path Segmentation Architecture ``` Internet Request to https://hosting.haumdaucher.de │ ▼ ┌───────────────────┐ │ Traefik Ingress │ └─────────┬─────────┘ │ Path Inspection & Priority │ ┌────────────────┴────────────────┐ │ │ Path: /family/* Path: /* (Fallback) Priority: 100 Priority: 10 │ │ ▼ ▼ ┌─────────────────────────┐ ┌─────────────────────────┐ │ Authentik ForwardAuth │ │ Public Ingress │ │ • Host: hosting.haumd...│ │ • ONLY /recipes/* │ │ • Allowed: 'family' & │ │ • ONLY /showcase/* │ │ 'infra-admins' groups │ │ • NO personal files │ └────────────┬────────────┘ └────────────┬────────────┘ │ │ HTTP 200 (Authed) │ │ │ ▼ ▼ ┌──────────────────┐ ┌──────────────────┐ │ MinIO Bucket │ │ MinIO Bucket │ │ key: family/* │ │ key: public/* │ │ (Protected Data) │ │ (Public Static) │ └──────────────────┘ └──────────────────┘ ``` #### Rule 1: Canonical Protected Prefix `/family/` - All dashboards containing personal, familial, or physiological metrics MUST reside under `/family/`: - `https://hosting.haumdaucher.de/family/mila.html` (Baby sleep, feeds, diapers) - `https://hosting.haumdaucher.de/family/moritz.html` (Personal nutrition, Garmin deficits, health) - `https://hosting.haumdaucher.de/family/nutrition.html` (Family nutrition overview) - `https://hosting.haumdaucher.de/family/sleep.html` (Sleep analytics) - Associated PWA assets (manifests, service workers) MUST also be scoped within `/family/` (e.g. `/family/mila.webmanifest`, `/family/sw.js`). #### Rule 2: Root Prefix `/` Strictly Restricted - The root of `hosting.haumdaucher.de` is restricted to public static assets (`/recipes/*`, `/showcase/*`). - Publishing scripts MUST NEVER write personal dashboards to the bucket root. --- ### 2. Declarative Authentik ForwardAuth Configuration Authentik configuration is codified in `terraform/authentik.tf`: 1. **Proxy Provider (`authentik_provider_proxy.hosting_family`):** - External Host: `https://hosting.haumdaucher.de` - Mode: `forward_single` - Bound to `authentik_outpost.embedded` - Session duration: 30 days (`access_token_validity = "days=30"`) 2. **Application (`authentik_application.hosting_family`):** - Slug: `hosting-family` - Protocol Provider: `authentik_provider_proxy.hosting_family.id` 3. **Role-Based Policy Bindings:** - Group `family` (Moritz & Amelie Graf): Granted access - Group `infra-admins`: Granted access - Unauthenticated requests are redirected (HTTP 302) to `https://auth.haumdaucher.de/application/o/authorize/...` --- ### 3. Kubernetes Ingress Deployment `k8s/minio/hosting-ingress.yaml` defines the two ingress tiers: - **`minio-hosting-family` (Priority 100):** - Path: `/family` (Prefix) - Middlewares: `auth-authentik-forwardauth@kubernetescrd,minio-hosting-add-prefix@kubernetescrd` - **`minio-hosting` (Priority 10):** - Path: `/` (Prefix) - Middlewares: `minio-hosting-add-prefix@kubernetescrd` --- ### 4. Generator Scripts & Codebase Hardening 1. **Elimination of Root Duplication:** - `skills/datastore/scripts/update_website.py`: Removed all S3 upload and local sync calls to root `mila.html`. Target is strictly `family/mila.html`. - `skills/datastore/scripts/update_moritz_website.py`: Removed S3 upload and local sync calls to root `moritz.html`. Target is strictly `family/moritz.html` (and `family/nutrition.html`). 2. **PWA Scope Alignment:** - Web App Manifests configure `start_url: "/family/mila.html"` and `scope: "/family/"`. - Service workers are registered at `/family/sw.js`. 3. **Telegram Link Consistency:** - Agent prompts for `mila` (Topic 20) and `koch` (Topic 21) only broadcast the secure URL: `https://hosting.haumdaucher.de/family/...`. --- ### 5. Automated Continuous Leak Audit Guard An automated audit check runs continuously (via OpenClaw periodic audit and datastore maintenance): - Inspects the MinIO `hosting` bucket root (`prefix=""`). - Detects any forbidden files matching patterns: `*mila*`, `*moritz*`, `*nutrition*`, `*sleep*`, `*schlaf*`, `*health*`, `*diaper*`. - If detected: 1. Immediately deletes/quarantines the offending object from the public bucket root. 2. Raises a critical notification/event to the system log. --- ## Consequences ### Positive - **Guaranteed Confidentiality:** Family and personal health data is physically impossible to access without passing Authentik ForwardAuth. - **Reproducible Infrastructure:** The entire authentication chain is codified in Terraform and Kubernetes manifests. - **Persistent Mobile PWA:** Authentik's 30-day session lifetime ensures seamless standalone mobile app operation without repetitive login prompts. - **Automated Safety Net:** Even if a developer or agent script mistakenly attempts to mirror a file to root, the automated audit deletes it. ### Negative & Mitigations - **Authentication Step on First Mobile Launch:** - *Mitigation*: When installing the PWA on iOS/Android, the user authenticates once via Authentik. The persistent session cookie maintains login across app launches.