tests: gate the hermes vhost, and stop specs from silently not running

Two gaps behind the same near-miss.

1. **No spec covered hermes.franksdank.cloud** — the one guarded vhost whose
   upstream is a host address (`reverse_proxy 192.168.0.143:8787`) rather than a
   container name, because its service is host-networked. That is how a
   loopback-only bind could take the vhost down with a 502 and leave every gate
   green. tests/e2e/specs/17-hermes-vhost.spec.ts asserts what a dead upstream
   violates: no 5xx through the guard chain, no Caddy error page, and no app
   bytes to an unauthenticated raw client. It does not assert SSO-silence, since
   this app has its own password login.

2. **Specs are matched by number prefix, not globbed.** A spec file whose prefix
   is in no project silently never runs — which is exactly what happened to the
   new one until it was registered. The prefix is registered now, the comment in
   playwright.config.ts says why, and tests/e2e_scaffold.bats fails if any
   spec file matches no project.

The hermes-webui seam assertion is corrected too: it asserted the loopback bind
I wrongly imposed, and now asserts the real contract — the unit's bind must serve
the address the Caddyfile fronts it on.
This commit is contained in:
Frank
2026-09-16 07:33:38 -07:00
parent 9c8a62f9fe
commit 25f387dd76
5 changed files with 111 additions and 18 deletions
+17 -7
View File
@@ -116,13 +116,23 @@ Before first deploy, set a strong `HERMES_WEBUI_PASSWORD` in the live
- The `/webui` mount is **rw on purpose**: `bootstrap.py` creates `.venv` in
the repo root. Do not flip it to `:ro`.
- The webui binds `0.0.0.0:8787` today, so the SSH tunnel is an access path for
the phone, not a security boundary: any LAN host that can route to
`192.168.0.143` reaches the API and needs only `HERMES_WEBUI_PASSWORD`. The
webui can run agent commands, so treat that password as a credential with
command-execution blast radius. To make loopback-only binding true again, set
`HERMES_WEBUI_HOST=127.0.0.1` in the unit and require the tunnel for every
client — that is an exposure change to decide deliberately, not a silent fix.
- The webui binds `0.0.0.0:8787`, and **two consumers depend on that**: the SSH
tunnel (which dials the host's `localhost`) *and* Caddy, which fronts
`hermes.franksdank.cloud` with `reverse_proxy 192.168.0.143:8787` — a host
address rather than a container name, because this unit is host-networked and
there is no port forwarder to bridge the gap (`dankclaw/Caddyfile` records the
same constraint). So the SSH tunnel is an access path for the phone, not a
security boundary: any LAN host that can route to `192.168.0.143` reaches the
API and needs only `HERMES_WEBUI_PASSWORD`. The webui can run agent commands,
so treat that password as a credential with command-execution blast radius.
To make loopback-only binding true again you must set
`HERMES_WEBUI_HOST=127.0.0.1` **and** give Caddy another way in (a
host-gateway route or a bridge-network unit) — that is an exposure change to
decide deliberately, not a silent fix. It was tried on 2026-09-16 as a silent
fix and broke the vhost with a 502 while the whole e2e suite stayed green;
`tests/e2e/specs/17-hermes-vhost.spec.ts` now asserts that vhost reaches its
app through the guard chain, so a dead upstream fails a gate instead of
hiding.
- The webui and the gateway share one data root (`/data` is a bind mount of
`/home/dank/Containers/hermes/data`). A config change made on one side takes
effect when the other restarts (same caveat as the dashboard).