« Pourquoi ce traitement d’export s’exécute-t-il vingt-deux fois en une heure, alors qu’il est planifié une seule fois par jour ? » Cette question, posée par un développeur consultant les journaux applicatifs d’un site d’intranet associatif, a mené à la découverte d’un événement cron détournable par un utilisateur sans droit particulier.
Le site proposait, dans son tableau de bord d’administration, un bouton « Générer l’export maintenant », destiné à forcer manuellement l’exécution d’un événement normalement planifié une fois par jour via wp_schedule_event(). Ce bouton s’est révélé accessible à des comptes qui n’auraient jamais dû pouvoir le déclencher.
Symptôme
Les journaux du serveur montraient des appels répétés à l’action export_donnees_quotidien, générant chacun un fichier volumineux et consommant plusieurs secondes de traitement CPU intensif. La fréquence de ces appels ne correspondait à aucune planification légitime, et leur horaire ne suivait pas non plus le déclenchement habituel du planificateur WordPress lié au trafic du site.
Diagnostic
Le bouton du tableau de bord pointait vers une URL du type admin.php?page=export&action;=lancer_maintenant, traitée par un gestionnaire qui déclenchait directement l’action associée à l’événement cron :
add_action( 'admin_init', function () {
if ( isset( $_GET['action'] ) && 'lancer_maintenant' === $_GET['action'] ) {
do_action( 'export_donnees_quotidien' );
}
} );
Ce code s’exécutait pour tout utilisateur connecté capable d’atteindre une page d’administration, sans distinction de rôle. Aucun appel à current_user_can() ni aucune vérification de nonce n’entourait le déclenchement. Reproduire le comportement en local, à l’aide de la commande wp cron event run export_donnees_quotidien, a confirmé que le traitement lui-même fonctionnait correctement : le défaut ne résidait pas dans la logique métier de l’export, mais uniquement dans son point de déclenchement manuel.

Correctif
La correction ajoute une vérification de capacité adaptée à l’action réellement sensible, accompagnée d’un nonce pour empêcher qu’un lien forgé ne déclenche l’action à l’insu d’un administrateur légitime naviguant sur le site.
add_action( 'admin_init', function () {
if (
isset( $_GET['action'], $_GET['_wpnonce'] )
&& 'lancer_maintenant' === $_GET['action']
&& current_user_can( 'manage_options' )
&& wp_verify_nonce( $_GET['_wpnonce'], 'lancer_export_maintenant' )
) {
do_action( 'export_donnees_quotidien' );
}
} );
Le lien du bouton a également été régénéré avec wp_nonce_url() pour intégrer ce nonce automatiquement, et la vérification de capacité a été alignée sur celle déjà utilisée pour accéder à la page d’administration elle-même, évitant toute incohérence entre les deux niveaux de contrôle.
Prévention
Ce type de défaut se reproduit facilement dès qu’un bouton d’action directe est ajouté à une page d’administration existante, sous la pression d’un besoin ponctuel, sans repasser par la même rigueur qu’un formulaire classique. Trois réflexes limitent ce risque à l’avenir.
- Traiter tout déclenchement manuel d’un événement cron comme une action sensible à part entière, jamais comme un simple raccourci d’affichage.
- Systématiser l’usage de
wp cron event runen environnement de test pour isoler la logique métier du point de déclenchement, avant toute mise en production. - Revoir chaque bouton d’action directe d’une page d’administration lors des audits de sécurité périodiques, au même titre que les routes REST personnalisées.
Repère retenu de ce diagnostic : un événement cron déclenché manuellement depuis l’administration reste une action, avec les mêmes exigences de contrôle qu’un formulaire de suppression ou de publication.
En résumé
Le problème observé ne tenait ni à la configuration générale de WP-Cron ni à la fiabilité de sa planification, mais à un point de déclenchement manuel ajouté sans les mêmes garde-fous que le reste de l’interface d’administration. La commande wp cron event run s’est révélée précieuse pour isoler cette cause, en permettant de rejouer l’événement indépendamment du bouton défaillant.