Sur un Supabase self-hosted, le Studio exposé via Kong est une deuxième porte d’entrée qu’on oublie : j’avais protégé le vhost dédié du Studio, mais l’hôte de l’API Kong sert lui aussi le Studio sur /, avec en prime /pg/ (postgres-meta) et /mcp. Le Studio n’a pas d’authentification propre : c’est un accès admin complet à la base, SQL Editor compris. Le correctif tient en un snippet Apache qui ne laisse publics sur l’hôte API que les préfixes utilisés par les applications, et réserve tout le reste au mesh WireGuard d’administration.
Le setup : deux vhosts Apache devant Coolify
L’instance tourne sur srv-a, déployée par Coolify v4 (une douzaine de conteneurs : db, kong, auth, rest, realtime, storage, studio, meta…). Apache est en frontal, Traefik derrière :
https://supabase.dev.example.com → Apache:443 → Traefik:8443 → supabase-studio:3000
https://api.supabase.dev.example.com → Apache:443 → Traefik:8443 → supabase-kong:8000
Ces deux noms sont d’ailleurs ceux qui avaient cessé de résoudre à cause d’un TXT _acme-challenge oublié qui cassait le wildcard DNS.
Jusqu’au 25 septembre 2026, la règle était : tout Studio Supabase derrière une Basic Auth Apache, sur un vhost avec ServerName exact pour primer sur le catch-all *.dev.example.com (qui, lui, ne filtre rien). Ce matin-là, j’ajoute une deuxième instance avec le même modèle. Et la règle avait une exception explicite : pas de Basic Auth sur l’API Kong, puisque les applications s’y authentifient avec leur apikey.
Pourquoi l’hôte API sert-il le Studio ?
Kong est la gateway de Supabase : elle route /auth/v1/ vers GoTrue, /rest/v1/ vers PostgREST, /storage/v1/ vers le stockage, etc. Mais la configuration Kong du déploiement contient aussi une route dashboard sur /, qui renvoie vers le Studio, protégée par la Basic Auth de Kong (realm="service", identifiants DASHBOARD_USERNAME / DASHBOARD_PASSWORD).
Résultat : mon vhost Studio était verrouillé, mais https://api.supabase.dev.example.com/ affichait le même Studio derrière un simple mot de passe, exposé à Internet. Même chose pour /pg/, l’API postgres-meta, l’interface REST de gestion de Postgres sur laquelle s’appuie le Studio, et pour /mcp.
Protéger le vhost d’une interface d’admin ne sert à rien si la gateway de l’API la republie sur un autre nom : il faut auditer chaque hôte, pas chaque application.
La fausse piste : une Basic Auth devant Kong
Première idée : mettre la Basic Auth Apache sur l’hôte API aussi. Impossible : les clients supabase-js appellent cet hôte depuis le navigateur et les serveurs avec leur apikey, une authentification Apache globale les casserait tous. Il fallait raisonner par chemin, pas par hôte.
J’en ai profité pour abandonner aussi la Basic Auth sur le Studio : un mot de passe devant un accès complet à la base, c’est mince. Les Studios sont désormais réservés aux deux postes admin du mesh WireGuard (10.8.0.2 en split-tunnel, 10.8.0.3 en full-tunnel). Les autres machines du mesh (srv-b, nas, pc-llm…) sont exclues exprès.
Le correctif : filtrer l’API Kong par préfixe
Deux snippets Apache sur srv-a portent la règle, pour qu’elle ne vive qu’à un seul endroit. Celui du Studio est un Require ip inclus dans le <Location /> du vhost :
# /etc/apache2/supabase-acl-studio.conf
Require ip 10.8.0.2 10.8.0.3
Celui de l’hôte API, inclus juste après le ServerName, ferme tout l’hôte au mesh puis rouvre une liste blanche de préfixes :
# /etc/apache2/supabase-acl-api.conf
<Location />
Include /etc/apache2/supabase-acl-studio.conf
</Location>
<LocationMatch "^/((auth|rest|realtime|storage|functions)/v1/|graphql/v1|sso/saml/|\.well-known/oauth-authorization-server)">
Require all granted
</LocationMatch>
Pas de lookahead négatif à maintenir : Apache fusionne les sections <LocationMatch> après les <Location>, donc son Require all granted l’emporte sur les préfixes publics. Le / de Kong, /pg/ et /mcp restent sous le Require ip du Studio.
| Chemin sur l’hôte API | Accès |
|---|---|
/auth/v1/, /rest/v1/, /realtime/v1/, /storage/v1/, /functions/v1/ | public |
/graphql/v1, /sso/saml/, /.well-known/oauth-authorization-server | public |
/ (Studio via la route dashboard), /pg/, /mcp, tout le reste | mesh admin |
Chaque instance Supabase a donc besoin de deux vhosts dédiés, un pour le Studio et un pour l’API, puis sudo apache2ctl configtest && sudo systemctl reload apache2. Le poste en split-tunnel demande en plus une résolution DNS du nom du Studio vers l’IP mesh, sinon la requête sort par la box et prend un 403.
Vérifier depuis Internet et depuis le tunnel
Le test qui compte se fait des deux côtés :
curl -so/dev/null -w '%{http_code}' https://supabase.dev.example.com/
# depuis Internet : 403
# tunnel WireGuard monté : 307
Le même test sur https://api.supabase.dev.example.com/ doit lui aussi renvoyer 403 depuis Internet, tandis qu’un appel applicatif sur /rest/v1/ doit continuer de passer. Le fichier htpasswd de l’ancienne Basic Auth n’est plus utilisé ; je l’ai gardé pour un éventuel retour arrière.
Lessons learned
- L’hôte API de Supabase self-hosted sert aussi le Studio. La route Kong
dashboardpublie le Studio sur/, protégé seulement par la Basic Auth de Kong. - Une interface d’admin se protège sur tous les hôtes qui la servent. Verrouiller le vhost dédié au Studio ne ferme pas la route que Kong expose à côté.
- Une API à clé se filtre par chemin, pas par mot de passe. Une liste blanche de préfixes publics laisse passer les clients et ferme
/,/pg/et/mcp. - Un accès complet à la base mérite mieux qu’un mot de passe. Restreindre le Studio aux IP du mesh d’administration retire toute surface d’attaque depuis Internet.
- Le test de fermeture se fait depuis l’extérieur. Un
curlqui doit répondre 403 depuis Internet et 307 depuis le tunnel prouve les deux moitiés de la règle.