Si vous avez déjà vu passer des rafales de requêtes sur /wp-json/ dans vos logs Apache/Nginx, vous avez déjà touché du doigt le vrai sujet : la REST API WordPress n’est pas “dangereuse” en soi, mais elle devient un excellent levier dès qu’un endpoint est trop bavard ou mal protégé.

Ce guide cible WordPress 6.9.4 (avril 2026) et PHP 8.1+. Je vais rester volontairement près du code, avec des patterns que j’applique en audit quand je dois réduire la surface d’attaque sans casser Elementor, Divi 5 ou Avada.

La menace

Un attaquant qui trouve un endpoint REST “sensible” mal protégé peut généralement faire une (ou plusieurs) de ces choses :

  • Énumérer des données (utilisateurs, auteurs, IDs d’articles, brouillons, types de contenus custom, médias) et préparer une attaque ciblée (phishing, brute force, credential stuffing).
  • Modifier ou injecter du contenu si l’endpoint accepte des écritures sans contrôle strict (création d’articles, modification de meta, publication, uploads indirects).
  • Déclencher des actions coûteuses (recalculs, exports, génération d’images, appels externes) et faire du DoS applicatif.
  • Contourner des règles métier quand un plugin expose une route REST en “admin-only” mais oublie de vérifier les capacités (capabilities) correctement.

Dans mon expérience, le scénario le plus fréquent n’est pas “on a hacké WordPress via la REST API”. C’est plutôt : un plugin ajoute un endpoint custom, le développeur met permission_callback => __return_true “temporairement”, et ça finit en production. Ou bien un endpoint permet d’écrire dans une option/meta sans whitelist stricte.

WordPress core a fait beaucoup d’efforts côté REST depuis des années, notamment sur la structure des contrôleurs, les schémas, et les permissions. Le problème vient presque toujours du code custom (thème, mu-plugin, snippets) et d’écosystèmes plugin. Référence utile : REST API Handbook et register_rest_route().

En langage simple : si une URL /wp-json/monplugin/v1/... répond avec des infos internes ou accepte des paramètres sans contrôle, elle devient une porte d’entrée. Pas besoin d’“exploit” sophistiqué : une requête HTTP suffit.

Résumé rapide

  • Ne bloquez pas “la REST API” globalement : ciblez des routes précises, sinon vous casserez Gutenberg, les builders et souvent WooCommerce.
  • Chaque endpoint doit avoir un permission_callback strict (capabilities + authentification), jamais __return_true sur une route sensible.
  • Validez et assainissez tout paramètre via args (validate/sanitize callbacks) et refusez ce que vous ne comprenez pas.
  • Réduisez l’énumération (users, search, embeds) avec des règles fines plutôt qu’un “deny all”.
  • Ajoutez une couche serveur (rate limiting, WAF, logs) mais ne comptez pas dessus pour corriger un endpoint vulnérable.
  • Testez avec WP-CLI + logs et surveillez les 401/403/429 sur /wp-json/.

Code vulnérable — ce qu’il ne faut PAS faire

Voici un exemple réaliste que je retrouve sur des sites “avancés” : un endpoint custom pour mettre à jour une option (ou une meta) depuis un builder, une intégration Zapier-like, ou un script interne. Le développeur veut “juste un endpoint simple”.

<?php
/**
 * DANGEREUX : exemple d'endpoint REST trop permissif.
 * Compatible WordPress 6.9.4, mais volontairement vulnérable.
 */

add_action('rest_api_init', function () {
	register_rest_route('acme/v1', '/settings', [
		'methods'  => 'POST',
		'callback' => function (WP_REST_Request $request) {
			// ERREUR : on accepte n'importe quel champ, sans whitelist.
			$payload = $request->get_json_params();

			// ERREUR : on écrit directement en base, sans capacité, sans nonce.
			update_option('acme_settings', $payload);

			return new WP_REST_Response([
				'ok' => true,
			], 200);
		},
		// ERREUR CRITIQUE : endpoint public.
		'permission_callback' => '__return_true',
	]);
});

Pourquoi c’est exploitable

Voici ce qui se passe en coulisses :

  • La route est enregistrée publiquement, donc accessible sans authentification.
  • Le code prend le JSON et le stocke tel quel. Un attaquant peut injecter des valeurs inattendues : URLs externes, HTML, structures énormes (DoS), ou configuration détournée.
  • Si votre thème ou plugin réutilise ensuite acme_settings pour générer du contenu, vous venez d’introduire une surface XSS/SSRF “secondaire”.

Le point le plus piégeux : ce code “marche” en staging. Beaucoup de gens le copient dans functions.php, oublient une parenthèse, corrigent vite fait, et le laissent. J’ai souvent vu ce problème sur des sites avec un plugin de snippets : le snippet est activé, puis oublié.

Docs pertinentes : Adding custom endpoints et WP_REST_Request.

Code sécurisé — la bonne implémentation

On garde la même fonctionnalité (mettre à jour des réglages), mais avec des protections concrètes :

  • Authentification (cookie + nonce pour les utilisateurs connectés, ou Application Passwords pour un usage serveur-à-serveur).
  • Autorisation via capabilities (ex : manage_options ou une cap custom).
  • Validation/sanitation des paramètres (whitelist stricte, types, formats).
  • Réponses cohérentes (WP_Error avec codes, pas de messages bavards).

Implémentation “propre” sous forme de mini-plugin

Évitez de coller ça dans functions.php. En sécurité, la stabilité compte : un thème switché ne doit pas désactiver votre contrôle d’accès. Préférez un plugin (ou mu-plugin si vous gérez l’infra).

<?php
/**
 * Plugin Name: ACME REST Hardening (exemple)
 * Description: Exemple d'endpoint REST sécurisé + durcissement ciblé.
 * Requires at least: 6.9
 * Requires PHP: 8.1
 */

namespace ACMERestHardening;

use WP_Error;
use WP_REST_Request;
use WP_REST_Response;

final class Plugin {
	public function hooks(): void {
		add_action('rest_api_init', [$this, 'register_routes']);
	}

	public function register_routes(): void {
		register_rest_route('acme/v1', '/settings', [
			'methods'             => 'POST',
			'callback'            => [$this, 'update_settings'],
			'permission_callback' => [$this, 'can_update_settings'],
			'args'                => [
				// Whitelist stricte : uniquement les clés attendues.
				'webhook_url' => [
					'type'              => 'string',
					'required'          => false,
					'sanitize_callback' => 'esc_url_raw',
					'validate_callback' => function ($value) {
						if ($value === '' || $value === null) {
							return true;
						}
						// Refusez les schémas exotiques.
						return (bool) preg_match('#^https?://#i', (string) $value);
					},
				],
				'enabled' => [
					'type'              => 'boolean',
					'required'          => false,
					'sanitize_callback' => function ($value) {
						return (bool) $value;
					},
				],
			],
		]);
	}

	public function can_update_settings(WP_REST_Request $request): bool|WP_Error {
		// 1) Auth obligatoire.
		if (!is_user_logged_in()) {
			return new WP_Error(
				'acme_rest_unauthenticated',
				'Authentification requise.',
				['status' => 401]
			);
		}

		// 2) Nonce REST (cookie auth) : protège contre CSRF depuis le navigateur.
		// Le client doit envoyer l'en-tête: X-WP-Nonce: <nonce>
		$nonce = $request->get_header('X-WP-Nonce');
		if (!$nonce || !wp_verify_nonce($nonce, 'wp_rest')) {
			return new WP_Error(
				'acme_rest_bad_nonce',
				'Nonce REST invalide.',
				['status' => 403]
			);
		}

		// 3) Autorisation par capability.
		if (!current_user_can('manage_options')) {
			return new WP_Error(
				'acme_rest_forbidden',
				'Accès refusé.',
				['status' => 403]
			);
		}

		return true;
	}

	public function update_settings(WP_REST_Request $request): WP_REST_Response|WP_Error {
		// get_param() respecte args + sanitize/validate.
		$webhook_url = $request->get_param('webhook_url');
		$enabled     = $request->get_param('enabled');

		// Construisez la valeur finale explicitement : pas de "tout le JSON".
		$new_settings = [
			'webhook_url' => is_string($webhook_url) ? $webhook_url : '',
			'enabled'     => is_bool($enabled) ? $enabled : false,
		];

		// Écriture atomique “raisonnable” : update_option sérialise, mais limitez la taille.
		$encoded = wp_json_encode($new_settings);
		if ($encoded !== false && strlen($encoded) > 10_000) {
			return new WP_Error(
				'acme_rest_payload_too_large',
				'Charge utile trop volumineuse.',
				['status' => 413]
			);
		}

		update_option('acme_settings', $new_settings, false);

		return new WP_REST_Response([
			'ok'       => true,
			'settings' => $new_settings,
		], 200);
	}
}

add_action('plugins_loaded', static function () {
	$plugin = new Plugin();
	$plugin->hooks();
});

Explication simple (ce qui vous protège réellement)

  • Le nonce REST bloque les appels cross-site depuis un navigateur déjà connecté (CSRF). En pratique, c’est une protection indispensable si l’endpoint est utilisé depuis l’admin.
  • La capability (manage_options) évite le “je suis connecté donc j’ai accès”. Sur des sites multi-auteurs, c’est la différence entre un éditeur et un admin.
  • Les args imposent un contrat : types, sanitation, validation. Sans ça, vous finissez par accepter des tableaux, objets, ou chaînes inattendues.
  • La whitelist empêche l’attaquant d’introduire de nouvelles clés “magiques” qui influenceront plus tard votre code.

Explication technique (les détails qui cassent souvent)

Nonce REST et clients JS : si vous appelez cet endpoint depuis un script dans l’admin, récupérez le nonce via wpApiSettings.nonce (ou via wp_create_nonce('wp_rest') côté PHP et passez-le à JS). Doc : REST API Authentication.

Application Passwords : pour un service externe (CI, intégration), préférez les mots de passe d’application plutôt que d’inventer un token maison. Ils sont dans le core depuis WP 5.6 et restent pertinents en 6.9.4. Doc : Application Passwords.

Pourquoi pas rest_authentication_errors partout ? Parce que c’est un marteau. Je l’utilise pour des règles globales (ex : bloquer l’accès anonyme à un namespace custom), mais pas pour “sécuriser” un endpoint qui fait des écritures : la permission doit vivre au plus près de la route.

Durcissement ciblé : bloquer un namespace custom en anonyme

Quand un site accumule des endpoints historiques, je commence souvent par une règle simple : tout votre namespace custom doit exiger une authentification, sauf exceptions explicites. Ça réduit vite le risque.

<?php
/**
 * Durcissement ciblé : refuse les requêtes anonymes sur votre namespace.
 * À placer dans un plugin/mu-plugin, pas dans functions.php.
 */

add_filter('rest_authentication_errors', function ($result) {
	// Si une autre couche a déjà décidé (erreur ou auth OK), respectez-la.
	if (!empty($result)) {
		return $result;
	}

	// Ne pas casser le chargement du site si la requête n'est pas REST.
	if (!defined('REST_REQUEST') || !REST_REQUEST) {
		return $result;
	}

	// Détecte le namespace demandé.
	$route = isset($_SERVER['REQUEST_URI']) ? (string) $_SERVER['REQUEST_URI'] : '';
	if (strpos($route, '/wp-json/acme/') === false) {
		return $result;
	}

	// Autorise les utilisateurs connectés (cookie auth) ou l'auth Basic (Application Passwords).
	if (is_user_logged_in()) {
		return $result;
	}

	// Refus par défaut.
	return new WP_Error(
		'acme_rest_namespace_requires_auth',
		'Authentification requise.',
		['status' => 401]
	);
}, 20);

Piège classique : mettre ce filtre avec une priorité trop basse et se battre avec un plugin de sécurité/WAF qui renvoie déjà une erreur. Ici, je mets 20 pour laisser les mécanismes plus “primaires” agir, mais ce n’est pas universel.

Compatibilité Gutenberg / Elementor / Divi 5 / Avada

Bloquer /wp-json/ globalement casse souvent :

  • Gutenberg (éditeur de blocs), qui s’appuie massivement sur REST.
  • Elementor (éditeur + certaines features de contenu dynamique), selon la config.
  • Divi 5 (nouvelle architecture, beaucoup d’AJAX/REST selon modules).
  • Avada (Fusion Builder / options / assets dynamiques).

La stratégie qui tient en prod : ne bloquez que vos namespaces et, côté core, ne changez le comportement que pour les endpoints réellement sensibles (écriture, export, recherche interne, endpoints de plugins).

Configuration serveur

La couche serveur ne remplace pas les permission_callback, mais elle aide contre la volumétrie (brute force, scan, DoS léger) et ajoute des garde-fous quand un endpoint vous échappe.

.htaccess (Apache) : limiter la pression sur /wp-json/

Si vous êtes sur Apache avec mod_ratelimit ce n’est pas toujours disponible. Une approche plus réaliste est de bloquer certaines méthodes sur des routes spécifiques ou de restreindre un namespace par IP (ex : endpoints internes).

# Exemple .htaccess (Apache) : restreindre un namespace custom à une IP (ou un VPN).
# ATTENTION : à utiliser uniquement si vous contrôlez l'IP source, sinon vous casserez vos intégrations.

<IfModule mod_rewrite.c>
RewriteEngine On

# Bloque /wp-json/acme/ pour tout le monde sauf 203.0.113.10
RewriteCond %{REQUEST_URI} ^/wp-json/acme/ [NC]
RewriteCond %{REMOTE_ADDR} !^203.0.113.10$
RewriteRule ^ - [F,L]
</IfModule>

Piège fréquent : copier ce bloc dans le mauvais fichier (ex : .htaccess à la racine serveur au lieu de celui du site), ou oublier que derrière un proxy/CDN, REMOTE_ADDR n’est pas l’IP client. Dans ce cas, faites la règle au niveau du reverse proxy (Nginx/Cloudflare) plutôt que dans Apache.

Nginx : limiter le débit sur /wp-json/

Sur Nginx, le rate limiting est plus propre. Exemple (à adapter) :

# Dans http { } :
limit_req_zone $binary_remote_addr zone=wpjson:10m rate=30r/m;

server {
  # ...

  location ~* ^/wp-json/ {
    # Burst tolère des pics courts, nodelay évite la latence (à discuter).
    limit_req zone=wpjson burst=20 nodelay;

    # Headers utiles pour debug.
    add_header X-WPJSON-RateLimit "on" always;

    try_files $uri $uri/ /index.php?$args;
  }
}

Ça ne “sécurise” pas un endpoint d’écriture, mais ça rend les scans et l’énumération moins confortables. Sur un site à fort trafic, ajustez le rate et excluez les IPs de confiance (monitoring, back-office).

Headers HTTP : réduire les fuites et renforcer le navigateur

Ces headers ne protègent pas la REST API directement, mais réduisent l’impact d’une XSS qui pourrait ensuite appeler la REST API via cookie auth.

# Exemple Apache (à mettre dans la conf vhost, idéalement) :
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
# CSP : à adapter, ne copiez pas tel quel sans tester.
# Header always set Content-Security-Policy "default-src 'self'; img-src 'self' data:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"

Référence : CSP header (MDN).

wp-config.php : forcer HTTPS et réduire certains risques

<?php
// Dans wp-config.php

// Force l'admin en HTTPS (utile si un proxy mal configuré laisse passer du HTTP).
define('FORCE_SSL_ADMIN', true);

// Désactive l'éditeur de fichiers (réduit l'impact si un admin est compromis).
define('DISALLOW_FILE_EDIT', true);

// Optionnel : bloque l'installation/maj de plugins/thèmes depuis l'admin (workflow Git/CI).
// Attention : à n'activer que si vous avez un processus de déploiement.
// define('DISALLOW_FILE_MODS', true);

Docs : wp-config.php (Developer Resources).

Vérifier si votre site est vulnérable

Objectif : repérer les endpoints custom exposés, et vérifier qu’ils ont des permissions strictes. Sans outils d’exploitation, vous pouvez déjà faire beaucoup.

1) Lister les routes REST et repérer les namespaces suspects

La REST API expose un index des routes. Sur un site public, /wp-json/ est généralement accessible (au moins partiellement). Ce n’est pas un bug. Ce que vous cherchez : des namespaces de plugins “maison” ou inconnus.

  • Ouvrez https://votre-site.tld/wp-json/ et cherchez "namespaces".
  • Cherchez "/wp-json/xxx/v1" dans votre code (grep) si vous avez accès.

2) WP-CLI : trouver le code qui enregistre des routes

WP-CLI ne liste pas nativement toutes les routes avec un simple wp rest route list (selon versions et packages). En pratique, je fais plutôt :

# Cherchez les register_rest_route dans vos plugins/thèmes (nécessite accès shell).
grep -R --line-number "register_rest_route" wp-content/plugins wp-content/themes

Ensuite, auditez chaque route :

  • permission_callback existe-t-il ?
  • Retourne-t-il une capability, un nonce, ou juste true ?
  • Les args valident-ils les entrées ?

3) Vérifier les logs (ce que je cherche en premier)

Dans les access logs, filtrez les requêtes vers /wp-json/ :

# Nginx access.log (exemple)
grep "/wp-json/" /var/log/nginx/access.log | tail -n 200

Signaux faibles utiles :

  • Rafales de GET sur /wp-json/wp/v2/users, /wp-json/wp/v2/search, /wp-json/oembed/1.0/proxy (énumération/reconnaissance).
  • Beaucoup de 401/403 sur un namespace custom (tentatives d’accès).
  • Des POST répétitifs sur une route custom (tentatives d’écriture).

Tableau de diagnostic

Symptôme Cause probable Vérification Solution
Un endpoint custom répond en 200 sans être connecté permission_callback trop permissif (__return_true) Requête anonyme + lecture du code register_rest_route Exiger auth + capabilities + nonce (si usage navigateur)
Gutenberg/Elementor ne charge plus l’éditeur Blocage global de /wp-json/ (serveur ou plugin) Console navigateur + 403 sur routes wp/v2 Remplacer par un blocage ciblé (namespace custom, endpoints sensibles)
Beaucoup de 429 sur /wp-json/ Rate limiting trop agressif Logs Nginx/Apache + corrélation trafic réel Ajuster rate/burst, exclure IPs de confiance
Des options/meta changent “toutes seules” Endpoint d’écriture sans contrôle ou compte admin compromis Audit code + journaux + historique des users Corriger endpoint + rotation mots de passe + forcer 2FA

Erreurs de sécurité fréquentes

Erreur Risque Solution
permission_callback => '__return_true' “temporaire” Accès public à des actions sensibles Permission stricte (capabilities) + auth + nonce si navigateur
Copier le code dans functions.php Perte de contrôle au changement de thème, snippets cassés, erreurs fatales Mettre dans un plugin ou mu-plugin, versionné
Oublier une parenthèse / point-virgule Fatal error, site down (pire en prod) Déployer via CI, tester en staging, activer WP_DEBUG_LOG en staging
Utiliser un hook inadapté (ex : init au lieu de rest_api_init) Route non enregistrée ou comportement non déterministe Enregistrer les routes sur rest_api_init
Confusion actions/filtres (ex : tenter de “return” dans une action) Contrôle d’accès inefficace Utiliser permission_callback et rest_authentication_errors correctement
Tester sur production sans sauvegarde Risque d’indisponibilité et de régression Staging + snapshot + plan de rollback
Oublier de vider le cache (plugin/CDN) Vous croyez avoir corrigé, mais l’ancien comportement persiste Purger cache applicatif + CDN + navigateur, invalider /wp-json/ si caché
Code d’un ancien tutoriel incompatible Permissions cassées, endpoints exposés, warnings PHP 8.1+ Vérifier la doc officielle (WP 6.9.4), typage, callbacks
Problème de priorité de hook Votre filtre est écrasé par un plugin de sécurité Ajuster priorité, diagnostiquer avec Query Monitor Définir une stratégie claire (qui décide en dernier) et documenter

Checklist de durcissement

  • Chaque route custom a un permission_callback qui vérifie au minimum une capability.
  • Les routes d’écriture (POST/PUT/PATCH/DELETE) exigent auth + nonce REST si appel depuis navigateur/admin.
  • Chaque paramètre est déclaré dans args avec sanitize_callback et, si nécessaire, validate_callback.
  • Les payloads JSON ne sont jamais stockés “tels quels” (pas de update_option($x, $request->get_json_params())).
  • Vos namespaces (/wp-json/votre-marque/) sont inaccessibles en anonyme par défaut.
  • Rate limiting Nginx/Proxy activé sur /wp-json/ (ou au moins sur namespaces sensibles).
  • Vous avez une stratégie d’auth externe (Application Passwords) au lieu de tokens maison.
  • Les logs d’accès conservent au moins 7–30 jours et vous savez filtrer /wp-json/.
  • Les plugins/page builders sont à jour (Elementor, Divi 5, Avada) et vous surveillez les CVE.

Que faire si le site est déjà compromis ?

  1. Coupez l’hémorragie : mettez le site en maintenance (ou au minimum bloquez temporairement les écritures) et désactivez le plugin/snippet qui expose l’endpoint suspect. Si vous passez par un CDN/WAF, appliquez une règle temporaire sur le namespace.
  2. Snapshot complet (fichiers + base). Ne “nettoyez” pas avant d’avoir une copie forensic. J’insiste : sans snapshot, vous perdez la capacité de comprendre l’entrée initiale.
  3. Réinitialisez les secrets :
    • Rotation des mots de passe admin + comptes à privilèges.
    • Révoquez les Application Passwords non reconnus.
    • Regénérez les clés de salage dans wp-config.php via WordPress.org secret-key service.
  4. Audit des utilisateurs : cherchez des comptes admin ajoutés récemment, ou des changements de rôle. Vérifiez aussi les comptes “éditeur” sur des sites multi-auteurs.
  5. Audit des plugins/thèmes : supprimez tout ce qui est nul/abandonné. Réinstallez depuis des sources propres (zip officiel) plutôt que “nettoyer” à la main.
  6. Contrôle d’intégrité : comparez le core avec WP-CLI si possible :
    # Vérifie les fichiers du core (à exécuter depuis la racine WP)
    wp core verify-checksums
  7. Recherchez des charges persistantes : mu-plugins inconnus, tâches cron suspectes, fichiers PHP dans uploads, modifications dans wp-config.php.
  8. Nettoyez la base : options siteurl/home, injections dans wp_options, contenus avec scripts, shortcodes inconnus. Faites-le avec prudence, idéalement sur une copie.
  9. Remettez en ligne progressivement : d’abord lecture seule, puis réactivez les écritures. Surveillez logs + alertes.

Si vous suspectez une compromission via REST (écritures), cherchez les traces applicatives : contenus modifiés, options changées, comptes créés. WordPress ne logge pas tout nativement ; sur des sites sensibles, j’ajoute souvent un plugin d’audit (ou une journalisation custom) pour les actions admin.

Conseils de maintenance et compatibilité

Évitez les “solutions globales” qui cassent l’écosystème

Le réflexe “je bloque /wp-json/” revient souvent. Sur WordPress 6.9.4, c’est presque toujours une mauvaise idée : vous cassez l’éditeur, parfois la recherche, et vous créez des contournements (AJAX admin-ajax.php) qui sont souvent pires.

Pattern recommandé : Service + configuration centralisée

Sur des sites complexes (builders + plugins), je centralise les règles REST dans un service dédié (plugin) et j’évite les snippets dispersés. Ça réduit les conflits de priorité et rend l’audit faisable.

Si vous utilisez un conteneur d’injection de dépendances (DI) côté projet, gardez-le léger. WordPress n’impose pas de container, mais un petit “service registry” interne suffit pour éviter le spaghetti.

Performance/SEO

  • Rate limiting peut réduire la charge CPU/DB liée aux scans REST. Sur un site très crawlé, surveillez les 429 pour ne pas pénaliser des services légitimes.
  • Ne cachez pas l’index REST globalement via un cache agressif côté CDN sans comprendre l’impact. Certains outils s’appuient sur des réponses dynamiques (capabilities, auth).
  • Si vous restreignez fortement des endpoints publics (ex : oEmbed), vérifiez l’impact sur le partage social et l’embed.

Compatibilité page builders

Quand vous durcissez, testez au minimum :

  • Création/modification d’une page dans Gutenberg.
  • Édition Elementor (frontend et backend) si utilisé.
  • Édition Divi 5 (modules dynamiques inclus).
  • Édition Avada/Fusion Builder si utilisé.

Le piège typique : un filtre rest_authentication_errors trop large qui renvoie 401 sur des routes nécessaires à l’éditeur. Gardez une logique par namespace et documentez vos exceptions.

Ressources

FAQ

Dois-je désactiver la REST API pour être en sécurité ?

Non. Sur WordPress 6.9.4, la REST API fait partie du fonctionnement normal (éditeur, intégrations, plugins). La bonne approche est de sécuriser vos endpoints et de réduire la surface (namespaces custom, endpoints d’écriture), pas de tout couper.

Est-ce que masquer /wp-json/ améliore la sécurité ?

Ça améliore surtout “l’opacité”, rarement la sécurité. Un attaquant peut découvrir d’autres chemins. Corrigez les permissions et la validation ; ensuite, vous pouvez ajouter rate limiting et règles WAF.

Faut-il bloquer /wp-json/wp/v2/users pour éviter l’énumération ?

Ça dépend. Sur un blog multi-auteurs, l’énumération peut aider un attaquant à cibler des comptes. Mais bloquer brutalement peut casser des fonctionnalités (auteurs, blocs). Si vous le faites, faites-le proprement : filtrez les champs retournés, limitez selon rôles, ou exigez auth pour certaines requêtes.

Le nonce REST est-il obligatoire si je vérifie current_user_can() ?

Si l’endpoint est appelé depuis le navigateur (admin), oui, je le considère obligatoire. La capability protège l’accès, le nonce protège contre un site tiers qui déclenche une action via le navigateur d’un admin déjà connecté (CSRF).

Je dois exposer un endpoint à un service externe. Que choisir ?

Utilisez les Application Passwords plutôt qu’un token maison stocké en option. Vous gagnez la révocation, la traçabilité, et un mécanisme core documenté.

Où placer le code de durcissement : thème enfant, plugin, mu-plugin ?

Pour la sécurité, évitez le thème enfant. Préférez un plugin dédié. En environnement géré (agence/infra), un mu-plugin est souvent le meilleur compromis pour garantir l’activation.

Pourquoi mon endpoint renvoie 403 alors que je suis admin ?

Dans 80% des cas : nonce absent/invalide (en-tête X-WP-Nonce), ou cache/proxy qui sert une vieille page admin/JS. Videz les caches, vérifiez l’en-tête dans l’onglet Network, et assurez-vous que le code est chargé (hook rest_api_init).

Mon filtre rest_authentication_errors casse Elementor/Divi. Comment corriger ?

Votre règle est trop large. Restreignez-la à votre namespace (ex : /wp-json/acme/) et laissez les routes core. Ajustez aussi la priorité du filtre si un plugin de sécurité intervient.

Est-ce que je peux “cacher” certains endpoints en supprimant des routes ?

Vous pouvez filtrer certaines choses, mais c’est rarement stable. La méthode robuste reste : permission_callback strict + validation des paramètres. La suppression de routes core peut créer des régressions lors des mises à jour.

Quelles méthodes HTTP sont les plus à risque ?

POST/PUT/PATCH/DELETE sur des routes custom, surtout si elles touchent options/meta/fichiers. Les GET peuvent aussi fuiter des données sensibles, mais les écritures sont la priorité en durcissement.

Comment éviter les régressions lors des mises à jour WordPress/plugins ?

Versionnez votre plugin de durcissement, testez en staging, et ajoutez une suite de tests simples (au minimum des appels REST automatisés) pour vérifier que vos routes sensibles renvoient bien 401/403 en anonyme et 200 en admin avec nonce.