fix(docker): let Provider Settings admit the host through the published port

Docker delivers the host's own browser to the container from the container
network's gateway, never from 127.0.0.1, so the loopback-only admission gate
answered every POWER UP request with 403 and no key could be saved. The README
and CHANGELOG claimed otherwise; both are corrected here.

- keySetupCore: GEV_KEY_SETUP_TRUSTED_PEERS, an exact-address list (no
  wildcards, prefixes or hostnames) admitted alongside loopback. Unset trusts
  nothing, and the sharing, Host, Origin, Content-Type and proxy-header checks
  all still apply to a listed address. Twelve assertions pin each refusal.
- scripts/docker-start.mjs: the container entry point, shaped after
  scripts/pinokio-start.mjs. It reads the default gateway from /proc/net/route,
  trusts exactly that address, and starts Vite. No gateway (--network none)
  trusts nothing. The pure helpers are unit-tested and a contract test pins the
  Dockerfile CMD and the loopback-only publish.
- Dockerfile: install git. `npm test` builds fixtures with `git init`, so the
  suite failed 71 tests inside the image and the README's claim that it runs in
  the container was wrong.
- SECURITY.md and .env.example document the setting and why it is safe only
  behind a 127.0.0.1 publish; README gains the Linux/NAS id note and .dockerignore
  drops macOS AppleDouble sidecars, both reported on the PR.

Verified in Docker on a clean build: saving a key from the panel writes the
bind-mounted .env (0600 in the container), it survives `compose down`/`up`, and
REMOVE comments it back out. A foreign Origin, a foreign Host, a proxy header
and a sibling container on the same network are each refused. `npm run doctor`,
`npm test` (5067 passed, 1 skipped, 0 failed) and `npm run build` are green
inside the container.

Reported-by: @jklaze
Co-Authored-By: Bogdan Petrut <[email protected]>
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
committed 2026-10-10 19:48:02 +02:00
1 parent 2e50836b08
commit 1c6c291774
10 files changed
+328 -9

No files matched your search

+30 -5
View File
@@ -156,11 +156,34 @@ docker compose up
```
Open **`http://localhost:4173`** as above. The repo is bind-mounted, so `.env`,
`.gev-cache` and `.gev-logs` live on the host and source edits still hot-reload.
The container binds `0.0.0.0` *inside* the container, but the port is published
to `127.0.0.1` only, so the default matches the native one: nobody else can
reach your server. Widening that mapping puts you under
[Sharing an instance](#-sharing-an-instance) — keys and all.
`.gev-cache` and `.gev-logs` live on the host, edits still hot-reload, and keys
you save from Provider Settings survive `docker compose down`. The port is
published to `127.0.0.1` only, so the default matches the native one.
Publishing it wider is the [Sharing an instance](#-sharing-an-instance) opt-in,
and it hands out **Provider Settings** too: the launcher trusts the container
gateway as this machine, and a wider publish delivers everyone else from that
same address. For an instance you mean to share, put the keys in `.env` by hand
instead. [SECURITY.md](SECURITY.md) has the detail.
<details>
<summary>File ownership on Linux and NAS hosts</summary>
On Linux, export your ids first so the files the container writes stay yours:
`export UID GID` before `docker compose up`. On a NAS or anywhere the share has
its own owner, set them explicitly instead (`UID=1026 GID=100 docker compose
up`) — Docker Desktop on macOS and Windows maps ownership for you.
</details>
<details>
<summary>IPv6-only networks, rebuilding, and the test suites</summary>
The launcher reads the IPv4 default gateway, so an IPv6-only Docker network
finds none: it logs `No default gateway; Provider Settings stays loopback-only`
and the panel refuses every save. Put your keys in `.env` by hand there, or name
the gateway yourself with `GEV_KEY_SETUP_TRUSTED_PEERS` after reading
[SECURITY.md](SECURITY.md).
`node_modules` deliberately stays inside the image, so it survives in an
anonymous volume across restarts: after a `git pull` that changes dependencies,
@@ -169,6 +192,8 @@ image also skips Puppeteer's Chromium download, which keeps it small but leaves
`npm run test:track` to the host — `npm run build` and `npm test` run fine in
the container.
</details>
### Then power it up — in the app, not in a file
Keys are upgrades, not prerequisites. When you want one, click the **POWER UP**