# Security Policy ## Reporting a Vulnerability Please do not open public issues for suspected vulnerabilities. Report security concerns privately through the contact links on https://khe.ee. Include the affected URL or repository, a short reproduction path, and the impact you believe the issue has. Do not include secrets, tokens, internal host details, private logs, or personally sensitive data in public issues or pull requests. ## Supported Scope Only the current `main` branch and the live homelab deployment are supported. This is a personal infrastructure repository, so there is no formal SLA, but I treat reports that could affect users, data, deployment credentials, or service availability as high priority. ## Automated writers A merge to `main` deploys to the VM, so every automated writer is bounded by the required `validate.yml` check: - **Renovate** opens image bump PRs and automerges patch, minor and digest updates outside the critical-infrastructure group, except Home Assistant minor releases, which wait for review. It is disabled for the estate's own images (`ghcr.io/khelias/*`). - **Per-repo pin Apps** (`khe-adventure-pins` for `khe-ai-adventure`) open the estate image pin PRs with auto-merge. An App has Contents and Pull requests write here and nothing else, so it cannot change workflow files. The guard in `validate.yml` fails any PR the App wrote or pushed to that changes more than its own `:main@sha256:` digests, and `scripts/verify-estate-pins.sh` fails any PR whose `ghcr.io/khelias/*` digests are not attested by their repo's `ci.yml` on `main`, or whose images of one repo come from different commits. The accepted residual risk (the App can merge or fast-forward an already-green change it did not write) is in [estate ADR-008](https://github.com/khelias/khe-architecture/blob/main/decisions/008-container-images.md). - **The edge fallback sync** (`edge-fallback.yml`, hourly) deploys two Cloudflare Workers with a copy of the public static sites ([estate ADR-016](https://github.com/khelias/khe-architecture/blob/main/decisions/016-static-fallback-at-the-edge.md)). Its API token can edit Workers in the one account and zone only and sits in the `edge-fallback` environment, which only `main` can use; the workflow never runs on pull requests. The staging script copies an allowlist of public trees, fails on a symlink or an `apiSecret` value, and replaces the adventure `config.js` with a stub, so the private trips atlas and the API secret never reach Cloudflare. ## Automated readers - **The agent's log reader** is mcp-grafana on the operator's machine, run read-only by `scripts/mcp-grafana.sh` with a Grafana service account token of role Viewer in `~/.config/khe/grafana-token` (mode 600, never in git). Grafana answers on the LAN only. Loki itself has `auth_enabled: false` and is reachable only on the `observability` Docker network, through Grafana. - **Public Actions logs** get only aggregate output: `ops-status.yml` prints the snapshot's summary, and the snapshot itself goes to Loki. - **Homepage** (dash.khe.ee, internet-facing behind Access) reads Docker through `homepage-socket-proxy`, not the socket: no writes or exec, but `CONTAINERS: 1` still lets it inspect every container, environment included. Its only host mount is `./config` (read-write, Homepage writes sample files there); the `/srv` share, with backups, runner credentials and service data, is not mounted. ## Network exposure The router forwards no ports; the internet reaches the homelab only through Cloudflare Tunnel. The edge fallback Workers sit in front of the tunnel on `khe.ee/*` and `games.khe.ee/*` and add no path into the homelab: they forward the request as it came and, on failure, answer from their own copy. Docker-published ports bypass UFW, because Docker's iptables rules come before UFW's, so every published port (Grafana's 3030 included) is reachable from the LAN and over the Tailscale subnet route, whatever the UFW port list says.