fix(hermes-pocket): active la porte d'auth du serve headless

dashboard.public_url engage auth_required : la racine ne sert plus le
token de session en clair derriere Pangolin. Le flux client passe par
/auth/password-login puis un ws-ticket a usage unique. nginx annonce
https et la sonde devient /api/status.
This commit is contained in:
Hermes Agent
2026-10-19 02:07:20 -04:00
parent 25a4da39af
commit f859ddb8e5
+30 -15
View File
@@ -8,17 +8,26 @@
# -> 127.0.0.1:9129 (nginx, réécrit Host: 127.0.0.1:9119)
# -> 127.0.0.1:9119 (hermes serve --isolated, profil pocket).
#
# L'appli ouvre ws(s)://<domaine>/api/ws?token=<HERMES_DASHBOARD_SESSION_TOKEN>.
# Deux points vérifiés le 27/09/2026 sur cette machine :
# 1. Le serveur n'accepte le token QU'en paramètre de requête (Authorization:
# Bearer est refusé en 403), d'où la ressource Pangolin sans SSO : l'appli
# native ne peut pas faire le flux navigateur. La protection est donc le
# token de session du profil pocket (profiles/pocket/serve.env, 0600).
# 2. Le serveur refuse tout en-tête Host non loopback (anti DNS-rebinding),
# d'où la réécriture Host par nginx, comme dans hermes-webui et
# hermes-dashboard.
# Depuis le 19/10/2026, la porte d'auth native du serve est ACTIVE : serve.env
# déclare dashboard.public_url (HERMES_DASHBOARD_PUBLIC_URL), ce qui engage
# auth_required=true même sur un bind loopback (doctrine upstream : derrière
# un proxy, le token de session local ne doit jamais être servi sans auth).
# Sans cette porte, le serve headless servait la page racine avec
# window.__HERMES_SESSION_TOKEN__ en clair ; publiée par Pangolin (SSO
# désactivé), elle exposait le token de session au monde entier. Fuite fermée
# et token rotaté le 19/10/2026.
#
# Flux client (appli « Hermes Mobile » F-Droid, mode Username & Password) :
# POST /auth/password-login (provider basic, identifiants dans serve.env)
# -> cookies de session -> POST /api/auth/ws-ticket
# -> ws(s)://<domaine>/api/ws?ticket=<billet> (usage unique, TTL 30 s).
#
# Notes de la période NON gardée (27/09/2026) : le token n'était accepté qu'en
# paramètre de requête (Bearer refusé), et le serveur refuse tout Host non
# loopback (anti DNS-rebinding), d'où la réécriture Host par nginx comme dans
# hermes-webui et hermes-dashboard.
# Ne jamais ouvrir 9119 ou 9129 au firewall : le serveur expose l'API complète
# de l'agent (outils, fichiers, terminal) et le token protège tout l'accès.
# de l'agent (outils, fichiers, terminal) ; la porte d'auth protège l'accès.
{
config,
lib,
@@ -48,8 +57,9 @@ in {
"HERMES_REAL_HOME=/var/lib/hermes"
];
# .env = clés de provider du profil ; serve.env = token de session
# (HERMES_DASHBOARD_SESSION_TOKEN), les deux hors du store nix.
# .env = clés de provider du profil ; serve.env = réglages de la porte
# d'auth (HERMES_DASHBOARD_PUBLIC_URL, HERMES_DASHBOARD_BASIC_AUTH_*) et
# token de session legacy — les deux hors du store nix.
EnvironmentFile = [
"${profileDir}/.env"
"${profileDir}/serve.env"
@@ -62,8 +72,9 @@ in {
};
};
# Proxy local : seul point d'entrée loopback, il réécrit le Host que le
# serveur impose, et laisse passer l'Upgrade WebSocket.
# Proxy local : seul point d'entrée loopback ; il réécrit le Host que le
# serveur impose, laisse passer l'Upgrade WebSocket, et annonce au backend
# https + l'IP cliente réelle (cookies Secure, audit) via X-Forwarded-*.
services.nginx.virtualHosts."hermes-pocket-proxy" = {
listen = [
{
@@ -76,6 +87,8 @@ in {
proxyWebsockets = true;
extraConfig = ''
proxy_set_header Host ${backendHost};
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
@@ -101,7 +114,9 @@ in {
enabled = true;
hostname = "127.0.0.1";
port = proxyPort;
path = "/";
# La racine gardée redirige vers /login (302) ; /api/status est la
# sonde publique (200) tant que la porte est active.
path = "/api/status";
scheme = "http";
mode = "http";
method = "GET";