Durcissement de la vitrine publique et des catégories d'annonce - #660
Open
Pokled wants to merge 2 commits into
Open
Durcissement de la vitrine publique et des catégories d'annonce#660Pokled wants to merge 2 commits into
Pokled wants to merge 2 commits into
Conversation
…e publique Un compte membre sans aucun privilège pouvait faire remonter un fil en tête de la page d'accueil et dans l'index de recherche fédéré nodyx.org, en postant simplement dans une catégorie d'annonces : les surfaces vitrine n'avaient aucune notion de rôle ni de curation, elles triaient par fraîcheur. - migration 117 : colonne categories.post_min_role (défaut 'member', inerte), threads.showcased_at, users.last_seen_ip ; verrou de la catégorie annonces ; rognage des pseudos existants (garde anti-collision). - POST /forums/threads : refus 403 CATEGORY_RESTRICTED si le rôle de l'auteur est sous le post_min_role de la catégorie. - PATCH /forums/threads/:id : is_featured passe sous le même verrou admin que pin/lock (il était ouvert aux modérateurs par omission) et pose showcased_at. - getFeatured / /instance/threads/showcase : ne renvoient que les fils explicitement mis en avant (showcased_at), plus par fraîcheur. - /instance/threads/recent : masque les fils non-featured des catégories restreintes (un brouillon d'annonce ne fuite plus). - scheduler : n'annonce à l'annuaire fédéré que les fils is_featured. - admin : PATCH /categories accepte post_min_role ; ban IP préfère la vraie adresse (last_seen_ip) et renvoie ip_ban_applied au lieu de mentir. - register : trim() du username. CDC : SPECS/NODYX_DURCISSEMENT_VITRINE_CDC.md
- nouveau sujet : le sélecteur ne propose que les catégories où le rôle de l'utilisateur autorise la publication ; message clair si aucune n'est ouverte ou si le back renvoie CATEGORY_RESTRICTED. - page fil : les boutons épingler / verrouiller / promouvoir ne s'affichent qu'aux owner/admin (ils étaient montrés aux modérateurs pour un 403). - admin catégories : sélecteur « qui peut ouvrir un fil » + badge cadenas. - admin membres : après un ban, indique si l'IP a réellement été bannie (ou pourquoi non), au lieu d'un succès générique trompeur. - i18n : clés forum.* / acat.* / amem.* ajoutées en fr et en.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Contexte
Incident du 2026-09-01 : un compte
memberstandard (nerti/ spam SEO), sans aucune faille d'authentification ni élévation de privilège, a fait remonter un fil « Alsouq Alshabi » :nodyx.org(network_index), propageable aux instances pairs.Cause : n'importe qui peut écrire dans
📣 Annonces, et trois surfaces publiques trient les fils par fraîcheur, sans notion de rôle ni de curation.Nettoyage de l'incident déjà fait à la main (compte + email + IP bannis, fil supprimé, annuaire purgé). Cette PR empêche la récidive.
Changements
Migration
117_durcissement_vitrine.sqlcategories.post_min_role(défautmember, inerte — rétro-compatible pour les instances tierces)threads.showcased_at(+ backfill des fils déjàis_featured)users.last_seen_ipannoncessuradminBackend
POST /forums/threads→ 403CATEGORY_RESTRICTEDsi le rôle de l'auteur est sous lepost_min_rolede la catégoriePATCH /forums/threads/:id→is_featuredpasse sous le même verrou owner/admin que pin/lock (il était ouvert aux modérateurs par omission) et poseshowcased_atgetFeatured//instance/threads/showcase→ ne renvoient que les fils explicitement mis en avant, plus par fraîcheur/instance/threads/recent→ masque les fils non-featured des catégories restreintesscheduler.announceThreadsToDirectory→ n'annonce à la fédération que les filsis_featuredPATCH /admin/categories/:id→ acceptepost_min_role(+logAction)POST /admin/members/:id/ban→ le ban IP préfèrelast_seen_ip(vraie adresse captée au login) àregistration_ip(toujours127.0.0.1depuis le tunnel), et renvoieip_ban_appliedau lieu d'un faux succèsregister→trim()du usernameFrontend
fr+enà parité (clésforum.*,acat.*,amem.*)Tests
forums.test.ts: +5 (catégorie restreinte 403 / admin OK / non-régression member ;is_featuredmod→403, admin→200)admin.test.ts: +2 (ip_ban_appliednull quand pas d'IP publique / =last_seen_ipsinon)durcissement-vitrine.test.ts: 6 invariants sur la migration (colonnes additives, verrou ciblé, backfill non destructif, garde anti-collision)tscclean,npm run check0 erreur, 5 portes i18n vertesDéploiement
Migration idempotente jouée au boot. Dry-run (transaction rollback) sur la prod OK : 3 colonnes ajoutées,
annoncesverrouillé, 17 fils featured rétro-datés, 1 pseudo rogné. Rollback code sans risque (colonnes inertes, aucunDROP).Décisions ouvertes tranchées (voir §9 du CDC)
D1 verrou
annoncesdans la migration · D2is_featuredadmin-only · D3/threads/recentmasque les restreintes · D4 critère fédération =is_featured· D5last_seen_ipajouté · D6 ménage rétroactifnetwork_indexreporté (script séparé)