github MBombeck/HealthLog v1.3.3
v1.3.3 — Pulse oximetry + audit-fix marathon

6 hours ago

Pulse oximetry (SpO₂) ships as first-class + 14 CRITICAL/HIGH audit fixes

What's new

  • Pulse oximetry (SpO₂) as a first-class measurement type. Withings ScanWatch (type 54) syncs automatically; manual entry, doctor-report PDF row, threshold override, AI insights, OpenAPI spec all wired through. Default bands (95–100% green, 92–94% orange, <92% red) follow consumer pulse-oximeter consensus + NICE NG115; COPD users with a doctor-set baseline of 88–92% can override.
  • Body-composition + glucose surfaces finally visible in v1.3 UI. TBW + Bone Mass + Blood Glucose now show in the measurements list filter, badge, mobile icon, edit dialog, and the server-rendered doctor PDF — the v1.3 server already ingested them, but the client list-rendering had drifted to the v1.2 type map.
  • Effective-range thresholds for TOTAL_BODY_WATER + BONE_MASS (was returning nominal for any value).

Security — 3 CRITICALs closed

  • Bearer-scope wildcard handling (a token with permissions:["medication:ingest"] could DELETE the user account)
  • Account-deletion completeness cascading through Feedback + AuditLog (GDPR Art. 17)
  • Withings webhook secret header migration + idempotency Bearer-resolver + GlitchTip URL-strip

Security — 10 HIGHs closed

  • moodLog webhook secret encrypted at rest (AES-256-GCM) with transparent legacy-plaintext rotation
  • CSP chatgpt.com + api.openai.com gated to /settings/ai/** (was a global blanket → DOM-XSS exfil channel)
  • Web-Push subscription endpoint now requires HTTPS + isPublicUrl() SSRF guard
  • IP-geolocation lookup is HTTPS-only by default (was plaintext HTTP — GDPR Art. 32 + 44)
  • /api/ai/test no longer leaks provider URLs / partial keys
  • /api/import rate-limited (5/h/user)
  • Trusted-proxy XFF semantics (TRUST_PROXY_HOPS env, default 1) — closes per-IP rate-limit rotation bypass
  • Audit-log retention purge (default 365 days, configurable, GDPR Art. 5(1)(e))
  • Idempotency cachable filter pinned as exported, tested function
  • Bearer mock tightening — assertions now catch any regression to raw-token comparison

Correctness

  • Server-side enum drift cousins closed. 5 hardcoded MeasurementType[] module arrays now derive from measurementTypeEnum.options; 2 contract enums (kindEnum, widgetIdEnum) extended for the new measurements
  • Truthfulness pass on medical citations: SpO₂ → consumer-pulse-oximeter consensus + NICE NG115; TBW → Watson formula / ICRP Reference Man (was misattributed to ESPEN); steps → Saint-Maurice JAMA 2020 (WHO publishes minutes/week, not steps); body composition → "bioimpedance-estimated, not DEXA-comparable"
  • isPublicUrl SSRF helper no longer falsely classifies DNS labels starting with fc/fd (e.g. fcm.googleapis.com) as IPv6 unique-local

Tests + docs

  • 358/358 tests pass (was 305 at branch start, +53)
  • Doctor-PDF text-content tests now use pdf-parse to assert real DE + EN labels (replaces bytes-only theatre)
  • README, AGENTS.md, CLAUDE.md, OpenAPI all synced (model count, vitest config name, migration range, SHA-256 → HMAC-SHA-256 wording)

Upgrade notes

  • No DB migration required for moodLog encryption — the worker auto-rotates legacy plaintext rows on next boot.
  • New env vars (all optional, with safe defaults):
    • TRUST_PROXY_HOPS (default 1) — set to your real proxy hop count
    • AUDIT_LOG_RETENTION_DAYS (default 365)
    • IP_GEO_LOOKUP_URL (default https://ipwho.is) / IP_GEO_LOOKUP_DISABLED=1 to opt out of any IP-egress

Multi-arch image

GHCR build pipeline is running for linux/amd64 + linux/arm64; check the Build & Publish workflow for the v1.3.3 image tag.

🤖 End-to-end audit-fix marathon by Claude Code overnight (V3 holistic audit → action plan → execution → multi-agent review → merge → tag)

Don't miss a new HealthLog release

NewReleases is sending notifications on new releases.