- Python 70.8%
- HTML 28.9%
- Dockerfile 0.3%
| app | ||
| deploy/reverse-proxy | ||
| scripts | ||
| tests | ||
| web | ||
| .dockerignore | ||
| .gitignore | ||
| bridge.py | ||
| docker-compose.mariadb.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| README.md | ||
| requirements.txt | ||
agent-webui
Interface web multi-utilisateurs pour discuter avec les agents Hermes
(profils de l'API api_server de Hermes), façon ChatGPT/Claude :
conversations persistantes, streaming SSE, rendu markdown, pièces
jointes, et espace d'administration.
Fonctionnalités
- Authentification — comptes locaux (PBKDF2-HMAC-SHA256, 310 000
itérations), premier compte
admin/admin(bannière + invitation à changer le mot de passe tant qu'il n'a pas été modifié), et OpenID Connect (découverte.well-known, validation JWKS/RS256, provisionnement automatique des liens de compte). - Anti brute-force — 5 échecs par (utilisateur, IP) sur 15 min → blocage 15 min, qui double à chaque récidive (15/30/60/120 min…).
- Espace admin — gestion des utilisateurs (création, rôle, activation/désactivation, mot de passe), activation/désactivation de l'inscription publique, gestion des agents (URL API + clé par agent), réglages OIDC, vue de toutes les sessions des utilisateurs.
- Multi-agents — un utilisateur ne voit que les agents auxquels l'admin l'a autorisé (table d'accès). Chaque conversation est liée à un agent ; les sessions Hermes sont réelles et conservent tout l'historique.
- Sessions isolées — chaque utilisateur ne voit que ses conversations ; les admins peuvent lister et ouvrir celles des autres (badge de propriété dans la sidebar).
- Fichiers — upload de pièces jointes (images envoyées en
image_urlnatif, fichiers en texte inline si texte) stockées dans le workspace de l'agent : en local directement, ou via un pont HTTP (bridge.py) si l'agent tourne sur une autre machine. Dans l'autre sens, les fichiers produits par l'agent sont listés et téléchargeables depuis l'interface. Limite 25 Mo, rétention 30 jours, traversal de chemin refusé. - Demandes de permission — quand une exécution d'outil attend une
approbation (
approval.requestsur le flux SSE), une modale s'affiche ; l'approbation repart versPOST /v1/runs/{run_id}/approvalde Hermes. - Markdown — rendu
marked+ assainissementDOMPurify(assets vendus localement, aucun CDN), mise en page pendant le streaming. - Bases de données — SQLite (défaut, zéro config) ou
MariaDB/MySQL via
DATABASE_URL.
Démarrage rapide (local)
python -m venv .venv
.venv/bin/pip install -r requirements.txt
HERMES_UPSTREAM=http://127.0.0.1:8642/p/webresearch \
HERMES_API_KEY=<clé API Hermes> \
.venv/bin/python -m uvicorn app.main:app --host 127.0.0.1 --port 8711
Ouvrir http://127.0.0.1:8711 puis se connecter admin / admin
(ou ADMIN_PASSWORD=... pour fixer un autre mot de passe initial).
Docker
# SQLite (le plus simple)
HERMES_UPSTREAM=http://host.docker.internal:8642/p/webresearch \
HERMES_API_KEY=<clé> docker compose up -d --build
# MariaDB
DB_PASSWORD=*** DB_ROOT_PASSWORD=*** \
HERMES_UPSTREAM=http://host.docker.internal:8642/p/webresearch \
HERMES_API_KEY=<clé> \
docker compose -f docker-compose.mariadb.yml up -d --build
L'interface écoute sur le port 8080 du conteneur ; les données
(SQLite, uploads, clé secrète) vivent dans le volume nommé webui-data.
Image pré-construite (registre conteneurs Forgejo)
L'image est publiée sur le registre de paquets de Forgejo, pas besoin de builder soi-même :
echo <token-forgejo> | docker login git.emitlinks.com -u emitlinks --password-stdin
docker pull git.emitlinks.com/emitlinks/agent-webui:latest # ou tag = commit
Les deux docker-compose*.yml référencent déjà cette image ; build: .
reste présent pour builder localement si besoin.
Variables d'environnement
| Variable | Défaut | Rôle |
|---|---|---|
DATABASE_URL |
sqlite:///<AW_DATA_DIR>/agent_webui.db |
DSN SQLAlchemy. MariaDB : mysql+pymysql://user:pass@host:3306/agentwebui |
AW_DATA_DIR |
./data |
Dossier des données (SQLite, uploads locaux, clé secrète) |
AW_PORT / WEBUI_PORT |
8710 |
Port quand on lance python -m app.main (uvicorn prend la main sinon) |
HERMES_UPSTREAM |
— | URL de base de l'agent initial (ex. http://127.0.0.1:8642/p/webresearch) |
HERMES_API_KEY |
— | Clé API de l'agent initial (agente webresearch) |
AW_SEED_AGENT_URL / AW_SEED_AGENT_KEY / AW_SEED_AGENT_NAME |
repris des deux précédents | Agent créé au premier lancement |
ADMIN_PASSWORD |
admin |
Mot de passe du compte admin au premier lancement |
AW_MAX_UPLOAD_BYTES |
26214400 (25 Mo) |
Taille max d'upload |
AW_UPLOAD_RETENTION_DAYS |
30 |
Purge automatique des uploads |
AW_IMAGE_DATAURL_MAX |
2000000 |
Envoi des images en base64 en dessous de cette taille |
AW_COOKIE_SECURE |
0 |
1 si HTTPS (cookie de session Secure) |
AW_TRUSTED_PROXIES |
— | Liste d'IP/CIDR de proxies de confiance (surcharge si le réglage admin est vide) |
La clé de signature des cookies de session est générée au premier lancement et stockée en base — rien à configurer.
Derrière un reverse proxy
Par défaut, la WebUI n'accorde aucune confiance aux en-têtes
X-Forwarded-For : l'IP retenue (notamment pour le rate-limiting
anti brute-force) est l'IP TCP de la connexion. Un client peut donc
toujours spoofeer ces en-têtes — sans config, le rate-limiting reste
indexé sur l'IP du proxy, ce qui est sûr mais grossier.
Pour traiter les vrais clients derrière un proxy (nginx, Traefik,
Caddy…), renseignez dans Espace admin → Réglages → Reverse proxy
la liste des adresses/CIDR de vos proxies, séparées par des virgules
(ex. 192.168.5.56, 10.0.0.0/8). Alors seulement :
X-Forwarded-Forest parcouru de droite à gauche et l'IP réelle du visiteur (le premier maillon non-proxy) est utilisée pour le rate-limiting ;X-Forwarded-Proto: httpsactive le cookie de sessionSecure;- l'
redirect_uriOIDC est reconstruite depuisX-Forwarded-Proto/X-Forwarded-Host(sinon l'IdP verrait l'URL interne du conteneur).
Ne listez que des proxies dont vous contrôlez la config : quiconque peut joindre la WebUI directement « devient » le proxy et peut forger son IP. En Docker, le proxy doit être joignable depuis le réseau du conteneur (son IP côté bridge, pas son IP LAN).
Pont de fichiers (agent distant)
Quand l'agent tourne sur une autre machine que la WebUI, lancez
bridge.py sur la machine de l'agent :
BRIDGE_KEY=<clé-aléatoire-longue> \
BRIDGE_WORKSPACE=/chemin/du/workspace/agent \
BRIDGE_EXTRA_DIRS=/autre/dossier/autorisé \
python -m uvicorn bridge:app --host 0.0.0.0 --port 8712
Puis, dans l'espace admin → Agents, renseignez bridge_url
(http://machine-agent:8712) et bridge_key pour cet agent. Les
uploads partiront dans BRIDGE_WORKSPACE/uploads/AAAA/MM/JJ/ (en
réalité uploads/YYYYMMDD/) et les fichiers de l'agent seront
proposés au téléchargement. Toutes les requêtes du pont exigent
l'en-tête x-bridge-key et restent confinées aux racines déclarées.
OpenID Connect
Espace admin → Réglages : activer OIDC, saisir l'émetteur (ex.
https://id.exemple.com/realm/monrealm), le client ID, le secret et
le scope. La WebUI découvre .well-known/openid-configuration,
récupère les JWKS et valide les JWT (RS256, iss, aud, expiration).
Le premier utilisateur authentifié via OIDC est lié à un compte local
(par sub) ; un compte existant peut être lié depuis l'espace admin.
Sécurité
- Cookies de session signés (HMAC),
HttpOnly,SameSite=Lax. - CSRF : en-tête
x-requested-withexigé sur toutes les mutations. - Clés API des agents : chiffrées au repos (Fernet, clé dérivée du
secret applicatif), jamais renvoyées par l'API (masquées
***). - Anti brute-force par (utilisateur, IP) avec blocage exponentiel.
- Traversal de chemin refusé sur tous les endpoints de fichiers (local et pont).
Lancer les tests
.venv/bin/python tests/e2e.py # 36 vérifs : auth, isolation, rate limit, fichiers…
# pont distant (lancez bridge.py sur 8713 avec BRIDGE_WORKSPACE=data/fake_agent_ws) :
.venv/bin/python tests/bridge_test.py # 11 vérifs