Skip to content

[GH-P3] Webhook app-level : HMAC, anti-rejeu, routage d'événements, handle_push partagé - #235

Merged
bobdivx merged 1 commit into
mainfrom
feat/gh-app-p3-webhook
Oct 14, 2026
Merged

bobdivx merged 1 commit into
mainfrom
feat/gh-app-p3-webhook

Conversation

@bobdivx

@bobdivx bobdivx commented Oct 14, 2026

Copy link
Copy Markdown
Owner

[GH-P3] Webhook app-level : signature, anti-rejeu, routage d'événements

Plan : devforge-plan-github-app.md, phase P3. S'appuie sur P0–P2.

Checklist

  • P3-01 Nouvelle route POST /api/v1/webhooks/github-app (github_app_events.rs, montée dans main.rs). L'ancienne /api/v1/webhooks/github garde la même signature/HMAC/bypass lab.
  • P3-02 Sécurité :
    • HMAC-SHA256 du corps brut avec le webhook_secret descellé, hmac::verify_slice (temps constant). En-tête absent ⇒ 401. Aucun bypass (DEVFORGE_ALLOW_INSECURE_WEBHOOK ignoré, testé). Pas d'app ⇒ 503.
    • DefaultBodyLimit 25 Mo sur cette route.
    • Anti-rejeu : X-GitHub-Delivery → github_webhook_deliveries (ON CONFLICT DO NOTHING). Si déjà vu ⇒ 200 {"duplicate":true} sans retraitement. Purge > 7 jours dans auto_deploy::tick.
    • installation.id ∉ github_installations ⇒ 202 ignoré + log (sauf installation.created, qui la crée). repository.id ↔ projects.github_repo_id vérifié au push.
    • Réponse immédiate (200 accepted) ; le traitement tourne dans tokio::spawn, avec statut final + duration_ms par livraison.
  • P3-03 Routage :
    • installation : created (+ dépôts), new_permissions_accepted, suspend/unsuspend, deleted (installation + dépôts supprimés). Les projets sont conservés et comptés dans disconnected_projects (statut GET /api/v1/github-app, pour l'alerte UI).
    • installation_repositories added/removed ⇒ mise à jour de github_installation_repos.
    • push ⇒ la logique de github_webhook est extraite dans handle_push(state, payload, scope), partagée par les deux routes. Suppressions de branche (deleted:true) et tags ignorés.
    • pull_request ⇒ enregistré et ignoré jusqu'à P5.
    • check_run.rerequested / check_suite.rerequested ⇒ redéploiement de la branche head_branch (push synthétique vers handle_push).
  • P3-04 GET /api/v1/github-app/deliveries (admin) : 20 dernières livraisons (id, événement, action, statut, durée, installation, date), sans payload.

Anti double déploiement (migration)

PushScope::Legacy (ancien webhook) cible tous les projets du dépôt, sauf ceux liés à une installation quand le mode App est actif. PushScope::Installation(id) (nouveau) ne cible que les projets liés à cette installation et à ce repository.id. Sans mode App, l'ancien webhook est strictement inchangé (testé). Aucun projet n'est lié aujourd'hui (le lien arrive en P6/P7), donc rien ne change en prod.

Tests

  • Signature valide/invalide/absente/corps modifié ⇒ 200/401/401/401 ; aucune livraison n'est enregistrée sur un rejet.
  • Même delivery id deux fois ⇒ 1 seul traitement (le 2ᵉ est duplicate).
  • Installation inconnue ⇒ 202 ; installation.created accepté ; ping ⇒ 200.
  • Fixtures JSON (apps/server/tests/fixtures/github/*.json : push, push supprimé, installation created/suspend/unsuspend/deleted, installation_repositories added/removed, check_run/check_suite rerequested, pull_request, ping) ⇒ effets en base vérifiés, dont disconnected_projects après deleted.
  • handle_push : non-régression de la sélection des projets pour les deux routes (Legacy sans/avec mode App, Installation), suppression/tag ignorés.
  • Purge > 7 j ; liste des livraisons sans payload ni secret.
  • Local : cargo test --workspace ⇒ 627 passés, 0 échec.

Validation (manuelle, après création de l'app en prod)

« Redeliver » depuis la page Advanced de l'app ⇒ visible duplicate. Push sur main d'un repo installé ⇒ déploiement < 5 s après réception. Pas encore faite : il faut d'abord l'app en prod (P1) et un projet lié.

Risques

  • check_*.rerequested redéploie la tête de branche, pas strictement head_sha (run_real_deploy déploie la branche). Les deux ne divergent que si la branche a avancé entre-temps. À affiner en P4.
  • La pile handle_push (déploiement) tourne en tâche détachée : en cas de redémarrage du serveur pendant le traitement, la livraison reste received. GitHub ne la renverra pas d'elle-même (pas de retry 2xx) ; il faut la relancer à la main.
  • Sur l'ancienne route, ignorer deleted:true et les tags est un léger changement : un push de suppression de la branche suivie ne déclenche plus de déploiement voué à l'échec.

@bobdivx
bobdivx merged commit 902b7cd into main Oct 14, 2026
4 checks passed
@bobdivx
bobdivx deleted the feat/gh-app-p3-webhook branch October 14, 2026 07:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant