Skip to content

Draft: not yet reviewed by a clinician

Use this page to prepare questions for your care team. It does not replace their advice. Your own targets and doses come from your nephrologist, diabetes team and renal dietitian.

Security

kidney-health holds health data, and a tampered potassium target or food value could lead to an unsafe meal, so integrity matters as much as privacy (operator security guide). The shipped defaults do most of the work; this page lists what they cover and the few things only you can do.

What the defaults do

Default What it stops
Rootless engine, default user namespace (never keep-id) an escape lands as an unused subordinate UID, not root and not you
UID 10001, no shell, no package manager in the image an attacker with code execution has no tools
Read-only root, writable /data and /tmp only, both noexec a compromised process cannot rewrite the app
All capabilities dropped, no-new-privileges, seccomp no kernel privileges to gain
Memory 512 MiB, 128 processes a flood or bug cannot take the host down
Published on 127.0.0.1 only LAN devices cannot skip your HTTPS proxy
Secrets as files (*_FILE) environment variables leak through inspect and crash dumps
Host allowlist (PUBLIC_URL, ALLOWED_HOSTS) DNS rebinding: unknown names get 400 Unknown host
Strict CSP with Trusted Types, frame-ancestors 'none', no-referrer, HSTS over HTTPS script injection, clickjacking, referrer leaks
X-Requested-With plus Origin/Sec-Fetch-Site checks on every change cross-site request forgery
Local accounts, 15-character passwords, Argon2id, throttling, a one-time setup code password guessing and a "first visitor becomes admin" race
Every query scoped to the signed-in person; "not yours" is 404 one household member reading another's log
No outbound requests until you switch a feature on silent data leaving the house

Sources: design note 01, design note 07, operator security guide.

What you must do

  • Put HTTPS in front, and set PUBLIC_URL (HTTPS for phones).
  • Set TRUSTED_PROXIES from the address the app actually sees (below).
  • Mount SECRET_KEY_FILE from your engine's secret store, and keep a copy elsewhere.
  • Encrypt backups and test a restore (Backups).
  • Track :0.12 or a verified digest, never :latest or :edge (Upgrades).
  • Never mount podman.sock or docker.sock into any container.
  • On Kubernetes, prove that NetworkPolicy is enforced; a policy without an enforcing plugin does nothing (Kubernetes: Network Policies; Kubernetes).
  • Tick the checklist for your engine in the operator security guide.

Proxy trust: find the address, do not guess it

TRUSTED_PROXIES lists the addresses whose X-Forwarded-For/-Proto the app believes. Too wide lets anyone fake their address (or, in proxy sign-in mode, their identity); too narrow is safe but makes everyone look like the proxy.

  1. Open the app once through your proxy.
  2. Read the access log; each line starts with the peer, for example 10.88.0.5:51234 - "GET / HTTP/1.1" 200.
  3. Put that address in TRUSTED_PROXIES and restart. Server administration → About this server then shows the trusted proxy and a count of identity headers ignored from untrusted peers.
Setup What the app sees
Rootless Podman (pasta), proxy on the same host the container's own address, not 127.0.0.1; every local process looks the same
Rootless Podman with slirp4netns 10.0.2.100 for every client: switch to pasta
Rootless Docker (default) the RootlessKit gateway for every client, a same-host proxy included: no proxy, keep the loopback default; with an HTTPS proxy on the same host, trust that gateway address (safe while the port is published on 127.0.0.1)
Proxy in the same compose network the proxy container's address (the Caddy overlay pins it)
Kubernetes the Gateway pods' addresses: keep the range narrow

(operator security guide, section 4)

Verify an image before you run it

Release images are built from hash-locked dependencies on digest-pinned bases and scanned before any tag moves. Once the repository is public, they are also signed keylessly with cosign and carry GitHub-signed provenance and SBOM attestations (security policy).

The repository's scripts/verify-image.sh checks the signature and the attestations and prints the digest to pin. By hand, cosign verify (cosign 3.0 or later: older versions cannot see the bundle-format signatures the release workflow makes and report "no signatures found") checks that the signature came from this repository's release workflow (Sigstore). The exact commands are in the security policy's verification section. Podman cannot enforce these signatures at pull time, so verify first and then deploy by digest.

Serving /learn from its own origin (optional)

By default the app serves this handbook at /learn/ on the same address as the app, with no sign-in (it holds no personal data). The handbook's theme needs a few inline scripts, so /learn gets a slightly looser policy than the app: those scripts are allowed by exact hash, computed when the app starts from the files it serves, and Trusted Types are off there. No handbook page reads or sends data, and the policy still blocks every other script (design note 08).

For stricter isolation, serve the handbook from another host name, so its policy never shares an origin (cookies, storage, API) with the app: copy the built site out of the image, serve it from your reverse proxy with the header that python -m app.handbook csp prints, and point the app's HANDBOOK_PUBLIC_URL at it (Configuration). The commands are in the operator security guide's handbook section.

A public copy on GitHub Pages works the same way through HANDBOOK_PUBLIC_URL, but then readers load it from the internet.

Data at rest

The key that encrypts stored API keys lives with the app, so encryption protects a leaked database file or app.admin backup, not someone who holds both the volume and the key. Passwords are hashed, not encrypted. Health data is not encrypted in the database: protect the disk and the backups (operator security guide, section 10). If you run the server for people outside your household, their health data is a special category under EU law (GDPR, Article 9).

Report a vulnerability

Use GitHub's private vulnerability reporting on the repository, not a public issue. Anything that makes the app give a dose or warn against treating a low counts as a security issue (security policy).

Sources

Not yet reviewed by a clinician. Facts and sources last checked 2026-10-06.