{"id":19024018,"date":"2026-09-19T09:08:57","date_gmt":"2026-09-19T09:08:57","guid":{"rendered":"https:\/\/wordpress.org\/support\/topic\/3-0-0-en-multisitio\/"},"modified":"2026-09-19T09:08:57","modified_gmt":"2026-09-19T09:08:57","slug":"3-0-0-en-multisitio","status":"publish","type":"topic","link":"https:\/\/wordpress.org\/support\/topic\/3-0-0-en-multisitio\/","title":{"rendered":"3.0.0 en multisitio"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Hola Fernando,<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Enhorabuena por la 3.0.0. Me he le\u00eddo el diff completo y el changelog entero, y es un salto de versi\u00f3n que se lo ha ganado. El salto del segundo factor usando un subsitio como puerta era serio, y la manera de cerrarlo, la uni\u00f3n de lo que pide cada sitio en lugar de leer solo el principal, es la correcta: leer solo el principal habr\u00eda apagado el 2FA en todas las redes que lo ten\u00edan configurado por subsitio, que hasta ahora era la \u00fanica forma de configurarlo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Y gracias por la menci\u00f3n en el changelog. \ud83d\ude42<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Van dos cosas de <strong>multisitio<\/strong>. La primera creo que la querr\u00e1s mirar antes de que alguien actualice una red grande.<\/p>\n\n\n\n<ol>\n<li>La autoprotecci\u00f3n es por sitio, y los ficheros son de la instalaci\u00f3n<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Es el mismo razonamiento que te llev\u00f3 al registro \u00fanico de red en la 2.11.3, aplicado al c\u00f3digo nuevo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El estado vive en una opci\u00f3n del sitio:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ class-self-integrity.php:834\n$state = get_option( self::STATE_OPTION, array() );<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Los checksums de wordpress.org, en un transient del sitio (:754). El throttle del watchdog, en otro (:1769). Y vigilante_daily_maintenance es un evento de cron por sitio, que llama a las dos entradas:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ vigilante.php:819\nif ( $this-&gt;self_integrity ) {\n    if ( $this-&gt;self_integrity-&gt;is_enabled() ) {\n        $this-&gt;self_integrity-&gt;detect_version_change();\n        $this-&gt;self_integrity-&gt;run_watchdog();<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">El chequeo de ficheros que hay al final de run_watchdog() queda fuera del bloque de owns_shared_files(), as\u00ed que corre en todos los sitios, no solo en el que manda.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En una red de N sitios eso son, cada d\u00eda y para unos ficheros que son uno solo: N recorridos completos del \u00e1rbol con hash y normalizaci\u00f3n de los ficheros de texto (2,9 MB, 63 ficheros), hasta N peticiones HTTP a wordpress.org, y N copias de vigilante_self_integrity_state. Y maybe_run_watchdog() cuelga de admin_init, as\u00ed que ese wp_remote_get() con timeout de 5 segundos puede caer dentro de una carga del escritorio.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El argumento ya lo tienes escrito, en el comentario de maybe_send_self_alert(): &#8220;the plugin files are the same for every site and every network of an installation, so one email&#8221;. Si vale para el correo, vale para el chequeo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pero lo que m\u00e1s me preocupa no es el coste, es lo que se ve. Al actualizar en una red, solo el sitio donde corre el updater recibe el contexto &#8216;upgrader&#8217;. Los dem\u00e1s llegan por detect_version_change(), con contexto &#8216;version_change&#8217;, y ah\u00ed la rama que se toma depende de si wordpress.org confirma el manifiesto nuevo. En las primeras horas despu\u00e9s de publicar no lo confirma, porque los checksums todav\u00eda no est\u00e1n generados, y tu propio c\u00f3digo cachea ese 404 una hora. Entonces cada uno de esos sitios levanta manifest_unverified:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\"MANIFEST.sha256 changed after a Vigilant version change but could not be verified\n against WordPress.org. If you did not just update Vigilant, treat this as possible tampering.\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">O sea, una actualizaci\u00f3n normal de una red deja ese aviso en todos los subsitios menos uno. Y display_state() no lo tapa: se queda con el chequeo m\u00e1s reciente de los dos, que es el del subsitio, porque corri\u00f3 despu\u00e9s.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Yo lo llevar\u00eda a lo mismo que hiciste con la l\u00ednea base: estado en opci\u00f3n de red, y el evento del updater anclado tambi\u00e9n en una opci\u00f3n de red (versi\u00f3n, huella del manifiesto y momento), de modo que un sitio hermano que se encuentre el cambio de versi\u00f3n pueda leer que lo escribi\u00f3 el actualizador de WordPress y no tenga que deducirlo de wordpress.org. Con eso desaparecen de golpe el aviso falso, las N peticiones y los N recorridos, y de paso floor_version() en class-self-repair.php deja de depender de desde qu\u00e9 sitio se pulse el bot\u00f3n de reparar.<\/p>\n\n\n\n<ol start=\"2\">\n<li>two_factor_demands_for() no memoiza, y se pregunta en cada pantalla del escritorio<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Esta es barata de arreglar y se nota en cualquier red con muchos sitios.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En class-settings.php:1038, para un superadministrador:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>if ( is_super_admin( $user-&gt;ID ) ) {\n    $blog_ids = array_merge( $blog_ids, get_sites( array( 'fields' =&gt; 'ids', 'number' =&gt; 200 ) ) );\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">y luego, por cada uno de esos IDs, un get_blog_option() y un WP_User::for_site(), que carga las capacidades de la cuenta en ese blog.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Quien la llama son dos ganchos del escritorio, no solo el login:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ class-two-factor-totp.php\nadd_action( 'admin_notices', array( $this, 'show_grace_period_notice' ) );\nadd_action( 'admin_init', array( $this, 'force_totp_setup_redirect' ) );<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Los dos pasan por handles_second_factor() y acaban en two_factor_demands_for(). En una red de 200 sitios son hasta 400 lecturas de opci\u00f3n y 400 de meta de capacidades por cada p\u00e1gina de wp-admin que abra un superadministrador, y en el mismo request se repite entera la segunda vez porque no hay ninguna cach\u00e9: busqu\u00e9 un static en la clase y no hay.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El comentario del m\u00e9todo ya avisa de la parte dif\u00edcil, que la pregunta no es solo de login (&#8220;the dashboard hooks of the TOTP class ask it on every admin screen of every site of a network, at least once per hook&#8221;), as\u00ed que supongo que fue quedarse sin manos. Con una cach\u00e9 est\u00e1tica por petici\u00f3n indexada por ID de usuario se queda en una pasada:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>private static function two_factor_demands_for( $user ) {\n    if ( empty( $user-&gt;ID ) ) {\n        return array();\n    }\n\n    static $cache = array();\n\n    if ( isset( $cache&#091; $user-&gt;ID ] ) ) {\n        return $cache&#091; $user-&gt;ID ];\n    }\n\n    \/\/ ... el cuerpo actual, guardando en $cache&#091; $user-&gt;ID ] antes de devolver.<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Dentro de una misma petici\u00f3n los ajustes de los otros sitios no van a cambiar, as\u00ed que no se pierde nada.<\/p>\n\n\n\n<ol start=\"3\">\n<li>Tres cosas menores (para seguir ayudando)<\/li>\n<\/ol>\n\n\n\n<ul>\n<li>get_wporg_sha256_checksums() construye la URL con el slug fijo &#8216;vigilante&#8217; (:762), mientras que Vigilante_Self_Repair::folder_is_the_distributed_one() s\u00ed contempla que la carpeta se haya renombrado y por eso no ofrece el bot\u00f3n. Es coherente, pero entonces en una instalaci\u00f3n con la carpeta renombrada el chequeo sigue preguntando por un slug que puede no ser el suyo. Quiz\u00e1 merezca que el mensaje de &#8220;carpeta renombrada&#8221; cubra tambi\u00e9n el chequeo, y no solo la reparaci\u00f3n.<\/li>\n<\/ul>\n\n\n\n<ul>\n<li>disabled_by() lista todos los callbacks enganchados a vigilante_self_integrity_enabled, devuelvan true o false. Hoy no se nota porque solo la llamas cuando el chequeo est\u00e1 apagado, pero si alg\u00fan d\u00eda la llamas desde otro sitio, un plugin que enganche el filtro para devolver true expl\u00edcitamente saldr\u00eda como el que lo apag\u00f3.<\/li>\n<\/ul>\n\n\n\n<ul>\n<li>Y una de expectativas m\u00e1s que de c\u00f3digo: Loco Translate escribe por defecto los .po y .mo dentro de la carpeta del plugin, as\u00ed que en instalaciones que lo usen van a aparecer como self_extra. La severidad est\u00e1 bien, es warning y no cr\u00edtico, pero una l\u00ednea en SECURITY.md diciendo que un fichero de traducci\u00f3n ah\u00ed dentro es eso y no un ataque ahorrar\u00eda alg\u00fan susto.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Nada de esto es urgente salvo lo del punto 1, y ah\u00ed lo urgente no es el coste sino el aviso de posible manipulaci\u00f3n que van a ver los subsitios de cualquier red que actualice en las primeras horas.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Por mi parte, y por si te sirve para saber que el acoplamiento sigue sano: los cuatro m\u00e9todos de los que depende <em>mi<\/em> Vigilante Network Sync est\u00e1n intactos en la 3.0.0, y gracias por haber dejado escrito en get_user_data_keys() que alguien de fuera la lee. La 3.0.0 s\u00ed me oblig\u00f3 a un cambio, pero era fallo m\u00edo: trusted_proxies entr\u00f3 en esa lista en la 2.11.9 y mi red de seguridad hizo lo correcto, preservarla por sitio, mientras trusted_proxy_header s\u00ed se copiaba. Los subsitios se quedaban con la cabecera puesta y la lista vac\u00eda, y como desde la 2.11.9 la cabecera solo se acepta desde un par reconocido, detr\u00e1s de un proxy p\u00fablico ve\u00edan a todos los visitantes con la direcci\u00f3n del proxy. Las dos claves describen el mismo hosting, as\u00ed que ahora viajan juntas. Lo digo por si alguien te llega con un multisitio donde el bloqueo de login se come a todo el mundo a la vez: puede ser eso, y se arregla actualizando mi plugin.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Un saludo,<br \/>Albert<\/p>\n","protected":false},"template":"","class_list":["post-19024018","topic","type-topic","status-publish","hentry"],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/support\/wp-json\/wp\/v2\/topic\/19024018","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wordpress.org\/support\/wp-json\/wp\/v2\/topic"}],"about":[{"href":"https:\/\/wordpress.org\/support\/wp-json\/wp\/v2\/types\/topic"}],"version-history":[{"count":0,"href":"https:\/\/wordpress.org\/support\/wp-json\/wp\/v2\/topic\/19024018\/revisions"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/support\/wp-json\/wp\/v2\/media?parent=19024018"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}