mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
Carries the v1.4.36 perf carry-over for the full /api/analytics route (correlations + healthScore + bp_in_target on the rollup- coverage probe, 111s cold worst-case down to ~1.5-3s on power-user accounts), consolidates Apple Health step samples into a one-row- per-day list view with chevron drill-down and a nightly drain (~50x table compression once it converges), restores IntakeHistoryListV2 to the pre-v1.4.28 contract, converges every Coach affordance behind a single feature gate with an invariant test pin, lifts the medication-detail Mounjaro card to byte- equivalent parity with Ramipril, brings the Insights overview hero row to height parity, hardens getClientIp behind a new TRUST_CF_CONNECTING_IP env flag, ships the Arztbericht hero card on Settings -> Export, adds the Medikamenteneinnahme quick-add overlay to the dashboard, drops the timezone override toggle for a silent browser-zone auto-seed, and fixes the v1.4.36 red CI workflows. Documentation refreshed across all three repos with five new architecture diagrams in the Dracula palette. Unit suite 4354 -> 4486 passing; typecheck + lint clean.
120 lines
8.3 KiB
XML
120 lines
8.3 KiB
XML
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 620" font-family="ui-sans-serif, -apple-system, Segoe UI, Roboto, sans-serif" role="img" aria-labelledby="title desc">
|
|
<title id="title">HealthLog security model</title>
|
|
<desc id="desc">Three concentric layers: an outer auth perimeter (passkey-first WebAuthn, password fallback with Argon2id), a session perimeter (HttpOnly SameSite=Strict cookie, Postgres-backed sessions with sliding expiry), and the encrypted core (AES-256-GCM with versioned keys for secrets at rest). Side rails cover the rate limiter, audit log, HMAC API tokens, CSP/HSTS headers and SSRF-guarded outbound test endpoints.</desc>
|
|
|
|
<rect x="0" y="0" width="1200" height="620" fill="#282a36" />
|
|
|
|
<text x="600" y="40" fill="#f8f8f2" font-size="22" font-weight="700" text-anchor="middle">Security model</text>
|
|
<text x="600" y="64" fill="#6272a4" font-size="13" text-anchor="middle">The path a request takes through the auth, session and encryption layers.</text>
|
|
|
|
<!-- Outer auth layer -->
|
|
<g>
|
|
<rect x="240" y="100" width="720" height="440" rx="20" fill="none" stroke="#8be9fd" stroke-width="2" />
|
|
<text x="270" y="124" fill="#8be9fd" font-size="13" font-weight="700">Layer 1 — Auth perimeter (WebAuthn first)</text>
|
|
</g>
|
|
<!-- Session layer -->
|
|
<g>
|
|
<rect x="320" y="160" width="560" height="320" rx="18" fill="none" stroke="#ffb86c" stroke-width="2" />
|
|
<text x="350" y="184" fill="#ffb86c" font-size="13" font-weight="700">Layer 2 — Session perimeter (server-side sessions)</text>
|
|
</g>
|
|
<!-- Encryption core -->
|
|
<g>
|
|
<rect x="420" y="220" width="360" height="220" rx="16" fill="none" stroke="#50fa7b" stroke-width="2" />
|
|
<text x="450" y="244" fill="#50fa7b" font-size="13" font-weight="700">Layer 3 — Encrypted core (AES-256-GCM)</text>
|
|
|
|
<rect x="450" y="265" width="300" height="50" rx="8" fill="#44475a" stroke="#50fa7b" stroke-width="1" />
|
|
<text x="600" y="285" text-anchor="middle" fill="#f8f8f2" font-size="12" font-weight="600">Secrets at rest</text>
|
|
<text x="600" y="302" text-anchor="middle" fill="#bd93f9" font-size="11">Withings tokens · provider keys · VAPID · Codex</text>
|
|
|
|
<rect x="450" y="325" width="140" height="50" rx="8" fill="#44475a" stroke="#50fa7b" stroke-width="1" />
|
|
<text x="520" y="345" text-anchor="middle" fill="#f8f8f2" font-size="12" font-weight="600">Versioned keys</text>
|
|
<text x="520" y="362" text-anchor="middle" fill="#bd93f9" font-size="11">ENCRYPTION_KEYS</text>
|
|
|
|
<rect x="610" y="325" width="140" height="50" rx="8" fill="#44475a" stroke="#50fa7b" stroke-width="1" />
|
|
<text x="680" y="345" text-anchor="middle" fill="#f8f8f2" font-size="12" font-weight="600">Online rotation</text>
|
|
<text x="680" y="362" text-anchor="middle" fill="#bd93f9" font-size="11">rotate-encryption-key</text>
|
|
|
|
<rect x="450" y="385" width="300" height="40" rx="8" fill="#44475a" stroke="#50fa7b" stroke-width="1" />
|
|
<text x="600" y="409" text-anchor="middle" fill="#bd93f9" font-size="11">Bearer tokens stored as HMAC-SHA256 digests</text>
|
|
</g>
|
|
|
|
<!-- Auth nodes (cyan) -->
|
|
<g>
|
|
<rect x="260" y="200" width="180" height="60" rx="10" fill="#44475a" stroke="#8be9fd" stroke-width="1.5" />
|
|
<text x="350" y="224" text-anchor="middle" fill="#f8f8f2" font-size="13" font-weight="600">Passkey (WebAuthn)</text>
|
|
<text x="350" y="244" text-anchor="middle" fill="#bd93f9" font-size="11">SimpleWebAuthn v13</text>
|
|
|
|
<rect x="260" y="280" width="180" height="60" rx="10" fill="#44475a" stroke="#8be9fd" stroke-width="1.5" />
|
|
<text x="350" y="304" text-anchor="middle" fill="#f8f8f2" font-size="13" font-weight="600">Password fallback</text>
|
|
<text x="350" y="324" text-anchor="middle" fill="#bd93f9" font-size="11">Argon2id + zxcvbn</text>
|
|
</g>
|
|
|
|
<!-- Session nodes (orange) -->
|
|
<g>
|
|
<rect x="340" y="380" width="220" height="60" rx="10" fill="#44475a" stroke="#ffb86c" stroke-width="1.5" />
|
|
<text x="450" y="404" text-anchor="middle" fill="#f8f8f2" font-size="13" font-weight="600">HttpOnly · SameSite=Strict</text>
|
|
<text x="450" y="424" text-anchor="middle" fill="#bd93f9" font-size="11">cookie · 30d sliding expiry</text>
|
|
|
|
<rect x="640" y="380" width="220" height="60" rx="10" fill="#44475a" stroke="#ffb86c" stroke-width="1.5" />
|
|
<text x="750" y="404" text-anchor="middle" fill="#f8f8f2" font-size="13" font-weight="600">Postgres sessions</text>
|
|
<text x="750" y="424" text-anchor="middle" fill="#bd93f9" font-size="11">requireAuth · requireAdmin</text>
|
|
</g>
|
|
|
|
<!-- Side rails -->
|
|
<g>
|
|
<rect x="40" y="160" width="180" height="60" rx="10" fill="#44475a" stroke="#ff5555" stroke-width="1.5" />
|
|
<text x="130" y="184" text-anchor="middle" fill="#f8f8f2" font-size="13" font-weight="600">Rate limiter</text>
|
|
<text x="130" y="204" text-anchor="middle" fill="#bd93f9" font-size="11">sliding window · per IP+user</text>
|
|
|
|
<rect x="40" y="240" width="180" height="60" rx="10" fill="#44475a" stroke="#ff5555" stroke-width="1.5" />
|
|
<text x="130" y="264" text-anchor="middle" fill="#f8f8f2" font-size="13" font-weight="600">Audit log</text>
|
|
<text x="130" y="284" text-anchor="middle" fill="#bd93f9" font-size="11">every sensitive op + IP</text>
|
|
|
|
<rect x="40" y="320" width="180" height="60" rx="10" fill="#44475a" stroke="#ff5555" stroke-width="1.5" />
|
|
<text x="130" y="344" text-anchor="middle" fill="#f8f8f2" font-size="13" font-weight="600">CSP + HSTS</text>
|
|
<text x="130" y="364" text-anchor="middle" fill="#bd93f9" font-size="11">nonce CSP · X-Frame-Options</text>
|
|
|
|
<rect x="40" y="400" width="180" height="60" rx="10" fill="#44475a" stroke="#ff5555" stroke-width="1.5" />
|
|
<text x="130" y="424" text-anchor="middle" fill="#f8f8f2" font-size="13" font-weight="600">SSRF guard</text>
|
|
<text x="130" y="444" text-anchor="middle" fill="#bd93f9" font-size="11">outbound test endpoints</text>
|
|
|
|
<rect x="980" y="160" width="180" height="60" rx="10" fill="#44475a" stroke="#bd93f9" stroke-width="1.5" />
|
|
<text x="1070" y="184" text-anchor="middle" fill="#f8f8f2" font-size="13" font-weight="600">API tokens</text>
|
|
<text x="1070" y="204" text-anchor="middle" fill="#bd93f9" font-size="11">hlk_* Bearer · HMAC stored</text>
|
|
|
|
<rect x="980" y="240" width="180" height="60" rx="10" fill="#44475a" stroke="#bd93f9" stroke-width="1.5" />
|
|
<text x="1070" y="264" text-anchor="middle" fill="#f8f8f2" font-size="13" font-weight="600">Refresh tokens</text>
|
|
<text x="1070" y="284" text-anchor="middle" fill="#bd93f9" font-size="11">24h access · rotated refresh</text>
|
|
|
|
<rect x="980" y="320" width="180" height="60" rx="10" fill="#44475a" stroke="#bd93f9" stroke-width="1.5" />
|
|
<text x="1070" y="344" text-anchor="middle" fill="#f8f8f2" font-size="13" font-weight="600">Off-host backup</text>
|
|
<text x="1070" y="364" text-anchor="middle" fill="#bd93f9" font-size="11">AES-GCM · S3-compatible</text>
|
|
|
|
<rect x="980" y="400" width="180" height="60" rx="10" fill="#44475a" stroke="#bd93f9" stroke-width="1.5" />
|
|
<text x="1070" y="424" text-anchor="middle" fill="#f8f8f2" font-size="13" font-weight="600">Per-user scope</text>
|
|
<text x="1070" y="444" text-anchor="middle" fill="#bd93f9" font-size="11">no cross-account reads</text>
|
|
</g>
|
|
|
|
<!-- Arrows: side rails into perimeters -->
|
|
<g stroke="#ff5555" stroke-width="1.2" stroke-dasharray="3 3" fill="none" marker-end="url(#a5-r)">
|
|
<path d="M220 190 L 260 220" />
|
|
<path d="M220 270 L 320 320" />
|
|
<path d="M220 350 L 320 350" />
|
|
<path d="M220 430 L 320 410" />
|
|
</g>
|
|
<g stroke="#bd93f9" stroke-width="1.2" stroke-dasharray="3 3" fill="none" marker-end="url(#a5-pu)">
|
|
<path d="M980 190 L 880 410" />
|
|
<path d="M980 270 L 880 410" />
|
|
<path d="M980 350 L 780 280" />
|
|
<path d="M980 430 L 780 410" />
|
|
</g>
|
|
|
|
<!-- Footnote -->
|
|
<text x="600" y="585" fill="#6272a4" font-size="11" text-anchor="middle">No data leaves the host unless an integration explicitly asks for it. Bearer tokens never elevate to admin — admin actions require a cookie session.</text>
|
|
|
|
<defs>
|
|
<marker id="a5-r" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#ff5555" /></marker>
|
|
<marker id="a5-pu" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#bd93f9" /></marker>
|
|
</defs>
|
|
</svg>
|