Rapport d'audit de sécurité — gitfed
- Date : 2026-07-28
- Périmètre : audit défensif complet avant mise en production, avec focus sur l'authentification et la certification (fédération / CA / SSH).
- Cible : dépôt
gitfed(branchemain), commit523f049(Deploy v0.6.0). - Nature : revue de code statique + inspection de la configuration de déploiement (Docker / Kubernetes).
1. Synthèse
Le cœur de sécurité du projet est sain :
- Aucune backdoor détectée.
- La chaîne de confiance fédérée est cryptographiquement vérifiée :
CertChecker.CheckCertvérifie la signature du certificat (golang.org/x/crypto/ssh/certs.go:456-459), et le code épingle la clé d'autorité avant vérification (internal/ssh/server.go:148-170). - Aucun secret commité : le dossier
demo/(clés CA, host keys, DB) est bien couvert par.gitignoreet non suivi par git. - Pas d'injection shell : git est invoqué via
exec.Commandsans passer par un shell (internal/gitexec/gitexec.go). - Pas de XSS stocké :
html/templateéchappe automatiquement, et goldmark (sansWithUnsafe) filtre le HTML brut et les URLs dangereuses (javascript:,data:,vbscript:) — vérifié dansgoldmark@v1.8.4/renderer/html/html.go:518. - Mots de passe hachés en bcrypt, jamais renvoyés par l'API (bucket séparé
auth).
Les vulnérabilités identifiées sont des durcissements classiques d'avant-production, pas des failles critiques exploitables à distance sans conditions. La plus prioritaire est l'absence de limitation des tentatives de connexion web.
Tableau récapitulatif
| ID | Sévérité | Titre | Fichier principal |
|---|---|---|---|
| H1 | 🔴 High | Aucune limitation des tentatives de login web | cmd/gitfed-web/handlers_auth.go:41 |
| M1 | 🟠 Medium | SSRF via la découverte de fédération | internal/federation/wellknown.go:47 |
| M2 | 🟠 Medium | Pas de révocation de certificat | internal/ssh/server.go:130 |
| M3 | 🟠 Medium | En-têtes HTTP de sécurité absents | cmd/gitfed-web/render.go |
| M4 | 🟠 Medium | gitfed-renew-cert : host key non vérifié par défaut |
cmd/gitfed-renew-cert/main.go:84 |
| M5 | 🟠 Medium | Timeouts HTTP absents + corps de réponse non borné | cmd/gitfed-web/main.go:55, internal/federation/wellknown.go:53 |
| L1 | 🟡 Low | Énumération d'utilisateurs par timing | internal/admin/admin.go:332 |
| L2 | 🟡 Low | Fuite de messages d'erreur internes vers l'utilisateur | multiples handlers web |
| L3 | 🟡 Low | Session : TTL 30 j, pas de rotation, pas de purge | internal/admin/admin.go:353, internal/store/sessions.go |
| L4 | 🟡 Low | bcrypt coût 10 ; politique de mot de passe minimale | internal/admin/admin.go:312-322 |
| L5 | 🟡 Low | Squat de namespace de repo | cmd/gitfed-web/handlers_dashboard.go:88 |
| L6 | 🟡 Low | Pas de quota repos/utilisateurs (DoS disque) | internal/admin/admin.go:186 |
| L7 | 🟡 Low | Durcissement conteneur K8s incomplet | deploy/k8s/deployment.yaml |
2. Détail des vulnérabilités
🔴 H1 — Aucune limitation des tentatives de connexion web
Fichier : cmd/gitfed-web/handlers_auth.go:41 (handleLogin)
Le formulaire de login est exposé sur Internet (ingress /) sans rate-limiting, sans lockout de compte, sans délai ni captcha. bcrypt (coût 10) ralentit chaque essai, mais un brute-force en ligne ciblé reste réalisable.
Aggravant architectural : le socket admin (/data/admin.sock) est un accès « god-mode » sans authentification (protégé uniquement par le mode fichier 0600). Le conteneur web détient donc toute la capacité admin de l'instance ; les gardes de route (requireLogin / requireAdmin) sont la seule barrière entre un visiteur anonyme et le contrôle total. Toute faiblesse du login en amplifie l'impact.
Impact : compromission de compte (y compris admin) par force brute / bourrage d'identifiants.
🟠 M1 — SSRF via la découverte de fédération
Fichiers : internal/federation/wellknown.go:47 (Fetch), internal/admin/admin.go:291 (GrantCollaborator), internal/federation/resolver.go:63 (EnsureTrust)
Fetch(domain) construit https://<domain>/.well-known/gitfed.json et effectue une requête HTTP sortante. Le domain provient de GrantCollaborator, exposé via POST /repo-grant/{repo} à tout propriétaire de repo — pas uniquement à un admin d'instance (la garde est canAdminister sur le repo, handlers_repo.go:441).
Un utilisateur authentifié peut donc ajouter un collaborateur du type x@169.254.169.254 ou x@10.0.0.5:6379 et déclencher une requête vers une IP interne ou l'endpoint de métadonnées cloud.
Limitations pour l'attaquant :
- SSRF « aveugle » : seul le succès/échec fuit (via le log d'audit et le timing) ; la réponse est validée (
doc.Domain == domain+ca_public_keyrequis) et n'est pas réfléchie. - Rate-limité à 10 nouvelles découvertes/minute (
resolver.go:33).
Manques : aucune validation du format de domaine, aucun blocage des IP littérales / plages privées / link-local / metadata, pas de protection anti-DNS-rebinding.
Impact : scan de ports interne, sondage de services internes, atteinte potentielle aux métadonnées cloud.
🟠 M2 — Pas de révocation de certificat
Fichiers : internal/ssh/server.go:130 (checkCert), internal/ca/ca.go:101 (IssueUserCert)
Un certificat émis reste valide jusqu'à son expiration (48 h par défaut, config.go:39) même après :
- la suppression de l'utilisateur (
DeleteUser), - le retrait de la clé publique certifiée.
checkCert ne valide que la signature CA et le format du principal — il ne vérifie jamais que l'utilisateur ou la clé existent encore côté serveur. CertChecker est instancié sans IsRevoked (server.go:167), donc aucune KRL/CRL n'est consultée.
À l'usage, un utilisateur supprimé conserve, tant que son cert est valide, l'accès en lecture aux repos publics (l'autorisation par repo via acl.Check limite le reste). Mais la révocation immédiate d'un compte compromis est impossible.
Impact : fenêtre de révocation jusqu'à 48 h ; pas de kill-switch pour une clé/compte compromis.
🟠 M3 — En-têtes HTTP de sécurité absents
Fichier : cmd/gitfed-web/render.go, ensemble du serveur web
Aucun en-tête de sécurité n'est émis :
Content-Security-Policy(pertinent : le shell contient un<script>inline,render.go:439),X-Frame-Options: DENY/frame-ancestors→ clickjacking possible sur les formulaires admin (make-admin, delete-user, approve-domain),X-Content-Type-Options: nosniff,Referrer-Policy,Strict-Transport-Security(HSTS).
Impact : clickjacking, absence de défense en profondeur contre l'exécution de contenu injecté, fuite de referrer.
🟠 M4 — gitfed-renew-cert : host key non vérifié par défaut
Fichier : cmd/gitfed-renew-cert/main.go:84
Sans l'option -host-key, l'outil utilise gossh.InsecureIgnoreHostKey(). Il émet un avertissement mais poursuit la connexion. Un attaquant en position de MITM peut intercepter le renouvellement de certificat.
Impact : interception du canal de renouvellement ; usurpation de l'instance d'origine. Outil côté client, mais distribué avec le projet et documenté comme point d'entrée officiel.
🟠 M5 — Timeouts HTTP absents + corps de réponse non borné
Fichiers : cmd/gitfed-web/main.go:55, cmd/gitfed-server/main.go:127, internal/federation/wellknown.go:53
- Les serveurs web et well-known utilisent
http.ListenAndServesansReadHeaderTimeout/ReadTimeout/WriteTimeout/IdleTimeout→ exposition Slowloris. Fetchdécoderesp.Bodyviajson.NewDecodersansio.LimitReader→ un pair distant malveillant peut renvoyer un corps arbitrairement grand (DoS mémoire pendant la découverte).
Impact : épuisement de connexions / mémoire.
🟡 L1 — Énumération d'utilisateurs par timing
Fichier : internal/admin/admin.go:332 (VerifyPassword)
Quand l'utilisateur ou le hash est absent, la fonction retourne sans appeler bcrypt. La différence de temps de réponse permet de distinguer un utilisateur existant d'un inexistant.
Correctif : comparer systématiquement contre un hash bcrypt factice.
🟡 L2 — Fuite de messages d'erreur internes
Fichiers : handlers_auth.go:49,60, handlers_settings.go, handlers admin
Plusieurs chemins renvoient err.Error() brut à l'utilisateur, pouvant divulguer des détails internes (chemins, structure du store).
Correctif : messages génériques côté client, détail journalisé côté serveur.
🟡 L3 — Gestion de session
Fichiers : internal/admin/admin.go:353 (sessionTTL = 30 j), internal/store/sessions.go
- TTL de 30 jours, sans rotation à l'élévation de privilège, sans expiration d'inactivité.
- Les sessions expirées ne sont supprimées que paresseusement (à l'accès), pas purgées — croissance non bornée du bucket.
🟡 L4 — Politique de mot de passe / coût bcrypt
Fichier : internal/admin/admin.go:312-322
- bcrypt
DefaultCost(10) ; recommandation 2026 : 12. - Longueur minimale 8, sans vérification de mot de passe compromis.
🟡 L5 — Squat de namespace de repo
Fichier : cmd/gitfed-web/handlers_dashboard.go:88 (handleCreateRepo)
Le nom du repo est libre : un utilisateur alice peut créer bob/x. L'owner reste alice@domain (pas d'usurpation d'identité), mais le nom est trompeur. Le path traversal est bien bloqué (ResolvePath rejette ..).
🟡 L6 — Pas de quota
Fichier : internal/admin/admin.go:186 (CreateRepo)
Aucun plafond sur le nombre de repos/utilisateurs. Chaque repo déclenche un git init --bare → DoS disque possible par un utilisateur authentifié.
🟡 L7 — Durcissement conteneur K8s incomplet
Fichier : deploy/k8s/deployment.yaml
Bon socle (non-root, allowPrivilegeEscalation: false, limites de ressources). Manquent :
readOnlyRootFilesystem: true,capabilities: { drop: [ALL] },seccompProfile: { type: RuntimeDefault },- une
NetworkPolicyrestreignant les flux sortants (renforce M1).
3. Contrôles vérifiés et jugés sains
| Domaine | Constat |
|---|---|
| Certificats | Signature vérifiée + autorité épinglée (server.go:163-170) |
| Fédération | Réponse well-known validée (domaine + clé CA présente) |
| Injection | git via exec.Command sans shell ; noms de repo validés par regex + rejet .. |
| Path traversal | Chemins de fichiers bornés par git (git show ref:path) |
| XSS | html/template + goldmark filtre HTML brut et URLs dangereuses |
| Open redirect | sanitizeNext restreint next aux chemins locaux |
| CSRF | Cookies HttpOnly + Secure + SameSite=Lax (couvre le POST cross-site) |
| Secrets au repos | Clés CA / host / DB / socket en 0600 ; demo/ gitignoré |
| Conteneurs | Exécution non-root, privilège non escaladable |
| TLS | Terminaison Traefik + cert-manager (Let's Encrypt) en prod |
Prérequis de déploiement à documenter : le endpoint well-known est servi en HTTP clair sur
:8443et dépend entièrement de la terminaison TLS de l'ingress. En Kubernetes c'est correct (insecure_federation: false), mais toute exécution hors de ce chemin ferait transiter la clé publique CA en clair (MITM sur la découverte de fédération). Ne jamais exposer:8443directement sur Internet sans TLS devant.
4. Note sur le CSRF
L'absence de jetons CSRF explicites est largement compensée par SameSite=Lax : les navigateurs n'envoient pas le cookie de session sur une requête POST cross-site, ce qui protège tous les endpoints de mutation (qui sont en POST). Le seul endpoint mutateur en GET est /lang/{lang} (changement de langue, sans impact sécurité). Le risque résiduel est donc faible, mais l'ajout de jetons CSRF reste recommandé en défense en profondeur pour les actions admin (voir plan, tâche 8).
5. Addendum (2026-07-29) — clone HTTPS anonyme
Ce rapport date du commit 523f049 et reste exact pour ce qu'il décrit à
cette date. Depuis, une fonctionnalité délibérée a changé un fait qui
n'était pas remis en cause à l'époque, à savoir que gitfed n'exposait
strictement aucun protocole git en HTTP : la v0.8.0 ajoute un endpoint
HTTP git-upload-pack en lecture seule, réservé aux dépôts déjà publics,
pour permettre git clone https://<domaine>/<owner>/<repo>.git sans
compte ni clé SSH (demande explicite du mainteneur — voir
ARCHITECTURE.md §7 pour le détail d'implémentation et
HOW_IT_WORKS.md §8 pour l'explication utilisateur).
Aucune des vulnérabilités listées ci-dessus n'est directement affectée :
la surface ajoutée est strictement lecture seule (aucune route
git-receive-pack n'existe côté HTTP), repo.Public est revérifié à
chaque requête sans mise en cache, et l'exemption CSRF qui l'accompagne
(un client git n'envoie jamais Origin/Referer) est sans risque
puisque la requête ne porte aucun cookie de session et ne modifie aucun
état. Un futur audit devrait néanmoins revalider ce chemin spécifiquement
(parsing du protocole git smart-HTTP, gestion des corps gzip, limites de
taille/temps).