[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f9xordBolr8jN_X2ef_KorCZjRBIdNbpG5aT0co0iO8s":3,"$fHlKI0OmLms5ctzUeWIG_wAAyFJ1bctHXlWAYCAWHNyg":25},{"_id":4,"slug":5,"__v":6,"author":7,"body":8,"canonical":9,"category":10,"createdAt":11,"date":12,"description":13,"htmlContent":14,"image":15,"imageAlt":15,"readingTime":16,"tags":17,"title":23,"updatedAt":24},"69fad999aa6b273b0ca386d1","google-is-testing-new-bot-authorization-standard-via-sejournal-martinibuster",0,"Equipe Seogard","Aujourd'hui, distinguer Googlebot d'un scraper qui usurpe son User-Agent repose sur un reverse DNS suivi d'un forward DNS — une méthode lente, fragile, et que personne n'automatise à l'échelle sur un parc de 20 000 pages. Google teste un nouveau standard d'autorisation cryptographique des bots qui pourrait rendre cette vérification instantanée et fiable. Voici ce que ça change techniquement, et comment anticiper l'implémentation.\n\n## Le problème actuel : l'identité des bots repose sur la confiance aveugle\n\nLe User-Agent HTTP est une chaîne déclarative. N'importe quel script Python peut envoyer `Googlebot/2.1 (+http://www.google.com/bot.html)` dans ses headers. C'est exactement ce que font les scrapers commerciaux, les outils de competitive intelligence, et les bots malveillants qui veulent bénéficier des règles permissives que vous accordez à Google dans votre robots.txt.\n\n### La vérification DNS : nécessaire mais insuffisante\n\nLa [méthode officielle recommandée par Google](https://developers.google.com/search/docs/crawling-indexing/verifying-googlebot) pour vérifier l'authenticité de Googlebot repose sur deux étapes :\n\n```bash\n# Étape 1 : Reverse DNS sur l'IP du crawler\nhost 66.249.66.1\n# Résultat attendu : crawl-66-249-66-1.googlebot.com\n\n# Étape 2 : Forward DNS pour confirmer\nhost crawl-66-249-66-1.googlebot.com\n# Résultat attendu : 66.249.66.1\n```\n\nCette méthode fonctionne, mais elle présente des limites sérieuses en production :\n\n- **Latence** : chaque requête DNS ajoute 20 à 100 ms. Sur un site e-commerce qui reçoit 50 000 hits de bots par jour, ça représente une charge non négligeable si vous vérifiez en temps réel.\n- **Pas de standard machine-readable** : la vérification est manuelle ou scriptée ad hoc. Aucun middleware serveur standard ne l'implémente nativement.\n- **Faux négatifs** : les IP de Google évoluent. Le fichier JSON des plages IP Googlebot est mis à jour régulièrement, mais si votre cache DNS est périmé, vous pouvez bloquer le vrai Googlebot.\n- **Aucune granularité** : vous savez que c'est \"un bot Google\", mais vous ne savez pas s'il crawle pour Search, pour Ads, pour Vertex AI, ou pour un autre produit. Pas de distinction de privilèges.\n\n### Le coût réel du bot spoofing\n\nPrenons un cas concret. Un média en ligne avec 8 000 articles, 400 000 sessions organiques/mois. L'équipe SEO constate dans les logs serveur que \"Googlebot\" représente 180 000 hits/jour. Après analyse via reverse DNS, 35 % de ces hits proviennent d'IP qui ne résolvent pas vers `*.googlebot.com` ou `*.google.com`. Ce sont des scrapers déguisés.\n\nConséquence directe : ces faux bots consomment de la bande passante, déclenchent des rendus SSR inutiles (coûteux en CPU sur un setup Next.js), et faussent complètement l'analyse du crawl budget dans les logs. L'équipe pense que Google crawle agressivement des pages de faible valeur alors que c'est un concurrent qui scrape le catalogue.\n\n## Ce que Google teste : une authentification cryptographique des bots\n\nLe standard en test, [rapporté par Search Engine Journal](https://www.searchenginejournal.com/google-is-testing-new-bot-authorization-standard/573957/), repose sur un principe de cryptographie asymétrique appliqué aux requêtes HTTP des crawlers. Le concept est analogue à ce que TLS fait pour les serveurs, mais inversé : c'est le **client** (le bot) qui prouve son identité au serveur.\n\n### Le mécanisme probable : signatures HTTP\n\nSans que Google ait publié de spécification complète, le mécanisme s'aligne avec le draft IETF [HTTP Message Signatures (RFC 9421)](https://www.rfc-editor.org/rfc/rfc9421) qui standardise la signature cryptographique des requêtes HTTP. Le principe :\n\n1. Google publie une clé publique à une URL bien connue (probablement un endpoint `.well-known`).\n2. Chaque requête de Googlebot inclut un header `Signature` et un header `Signature-Input` contenant une signature cryptographique générée avec la clé privée correspondante.\n3. Le serveur récupère la clé publique de Google (avec cache), vérifie la signature, et sait avec certitude cryptographique que la requête provient de Google.\n\nVoici à quoi pourrait ressembler une requête authentifiée :\n\n```http\nGET /catalogue/chaussures-running HTTP/2\nHost: www.example-ecommerce.fr\nUser-Agent: Googlebot/2.1 (+http://www.google.com/bot.html)\nSignature-Input: sig1=(\"@method\" \"@path\" \"@authority\" \"user-agent\");created=1714988400;keyid=\"google-crawl-2026\";alg=\"ed25519\"\nSignature: sig1=:dGVzdCBzaWduYXR1cmUgYmFzZTY0IGVuY29kZWQ=:\n```\n\nLes éléments clés :\n- **`keyid`** : identifie la clé publique à utiliser pour la vérification. Google pourrait utiliser des key IDs distincts par produit (Search, Ads, AI).\n- **`alg`** : l'algorithme de signature. Ed25519 est compact, rapide, et résistant aux attaques par canal auxiliaire.\n- **`created`** : timestamp qui empêche les attaques par rejeu (replay attacks).\n\n### Pourquoi c'est un changement fondamental\n\nCe n'est pas un simple ajustement technique. C'est un changement de paradigme dans la relation crawler/serveur :\n\n**Avant** : le serveur fait confiance au User-Agent et vérifie éventuellement l'IP par DNS. Le modèle est \"présumé légitime sauf preuve contraire\".\n\n**Après** : le bot prouve cryptographiquement son identité à chaque requête. Le modèle devient \"présumé illégitime sauf preuve cryptographique\". C'est exactement le même shift que le passage de HTTP à HTTPS, mais pour l'identité des clients.\n\nCe standard s'inscrit dans une tendance plus large où Google structure ses interactions avec les éditeurs de façon plus formelle. La [récente expansion de la documentation robots.txt](https://seogard.io/blog/google-s-robots-txt-docs-expand-deep-links-get-rules-eu-steps-in-seo-pulse-via-sejournal-mattgsouthern) montre cette même direction : des protocoles plus explicites, plus vérifiables.\n\n## Implémentation côté serveur : comment se préparer\n\nMême si le standard n'est pas finalisé, vous pouvez dès maintenant structurer votre infrastructure pour être prêt. Voici les implémentations probables selon votre stack.\n\n### Nginx : vérification via un module Lua\n\nNginx n'a pas de support natif des HTTP Message Signatures, mais avec `lua-nginx-module` (inclus dans OpenResty), vous pouvez implémenter la vérification :\n\n```nginx\n# /etc/nginx/conf.d/bot-auth.conf\n\nlua_shared_dict google_keys 1m;\n\nserver {\n    listen 443 ssl http2;\n    server_name www.example-ecommerce.fr;\n\n    # Vérification de la signature pour les requêtes Googlebot\n    access_by_lua_block {\n        local user_agent = ngx.var.http_user_agent or \"\"\n        \n        -- Ne vérifier que les requêtes qui prétendent être Googlebot\n        if not string.find(user_agent, \"Googlebot\") then\n            return\n        end\n\n        local sig = ngx.var.http_signature\n        local sig_input = ngx.var.http_signature_input\n\n        if not sig or not sig_input then\n            -- Googlebot sans signature : loguer et décider de la politique\n            ngx.log(ngx.WARN, \"Googlebot claim without signature from \", ngx.var.remote_addr)\n            -- Phase transitoire : ne pas bloquer, juste loguer\n            -- ngx.exit(403)\n            return\n        end\n\n        -- Récupérer la clé publique Google (avec cache de 24h)\n        local keys = ngx.shared.google_keys\n        local pub_key = keys:get(\"google-crawl-2026\")\n        \n        if not pub_key then\n            -- Fetch depuis https://www.google.com/.well-known/crawler-keys.json\n            -- (URL hypothétique basée sur les conventions .well-known)\n            local http = require(\"resty.http\")\n            local httpc = http.new()\n            local res, err = httpc:request_uri(\n                \"https://www.google.com/.well-known/crawler-keys.json\",\n                { method = \"GET\", ssl_verify = true }\n            )\n            if res and res.status == 200 then\n                local cjson = require(\"cjson\")\n                local key_data = cjson.decode(res.body)\n                pub_key = key_data.keys[\"google-crawl-2026\"]\n                keys:set(\"google-crawl-2026\", pub_key, 86400) -- cache 24h\n            end\n        end\n\n        -- Vérification Ed25519 via lua-resty-openssl\n        -- (implémentation simplifiée — la vraie nécessite le parsing de Signature-Input)\n        local openssl = require(\"resty.openssl\")\n        local verified = verify_signature(pub_key, sig, sig_input, ngx.req)\n        \n        if not verified then\n            ngx.log(ngx.ERR, \"Invalid Googlebot signature from \", ngx.var.remote_addr)\n            ngx.exit(403)\n        end\n    }\n}\n```\n\nCe code est volontairement simplifié. En production, vous devrez :\n- Parser le `Signature-Input` pour extraire les covered components\n- Reconstruire la signature base selon RFC 9421 §2.5\n- Gérer la rotation des clés (key rollover)\n- Implémenter un fallback vers la vérification DNS pendant la période de transition\n\n### Node.js / Express : middleware de vérification\n\nPour les stacks JavaScript (Next.js, Nuxt, Express), un middleware est plus naturel :\n\n```typescript\n// middleware/bot-auth.ts\nimport { verify } from '@noble/ed25519';\nimport { createHash } from 'crypto';\n\ninterface CrawlerKeySet {\n  keys: Record\u003Cstring, string>; // keyid -> base64 public key\n  expires: number;\n}\n\nlet cachedKeys: CrawlerKeySet | null = null;\n\nasync function fetchGoogleCrawlerKeys(): Promise\u003CCrawlerKeySet> {\n  if (cachedKeys && Date.now() \u003C cachedKeys.expires) {\n    return cachedKeys;\n  }\n\n  const res = await fetch('https://www.google.com/.well-known/crawler-keys.json', {\n    signal: AbortSignal.timeout(5000),\n  });\n\n  if (!res.ok) {\n    throw new Error(`Failed to fetch crawler keys: ${res.status}`);\n  }\n\n  const data = await res.json();\n  cachedKeys = {\n    keys: data.keys,\n    expires: Date.now() + 86_400_000, // Cache 24h\n  };\n\n  return cachedKeys;\n}\n\nfunction parseSignatureInput(input: string): {\n  keyid: string;\n  alg: string;\n  created: number;\n  components: string[];\n} | null {\n  // Parse selon RFC 9421 Structured Fields\n  const match = input.match(\n    /sig1=\\(([^)]+)\\);created=(\\d+);keyid=\"([^\"]+)\";alg=\"([^\"]+)\"/\n  );\n  if (!match) return null;\n\n  return {\n    components: match[1].split(' ').map(c => c.replace(/\"/g, '')),\n    created: parseInt(match[2]),\n    keyid: match[3],\n    alg: match[4],\n  };\n}\n\nexport async function botAuthMiddleware(\n  req: Request,\n  res: Response,\n  next: NextFunction\n) {\n  const ua = req.headers['user-agent'] || '';\n\n  // Ne vérifier que les bots qui déclarent être Googlebot\n  if (!ua.includes('Googlebot')) {\n    return next();\n  }\n\n  const signature = req.headers['signature'] as string;\n  const signatureInput = req.headers['signature-input'] as string;\n\n  if (!signature || !signatureInput) {\n    // Phase transitoire : loguer les Googlebot sans signature\n    console.warn(`[bot-auth] Unsigned Googlebot request from ${req.ip}`);\n    // Métriques pour dashboarding\n    metrics.increment('bot.googlebot.unsigned');\n    return next(); // Ne pas bloquer pendant la transition\n  }\n\n  try {\n    const parsed = parseSignatureInput(signatureInput);\n    if (!parsed || parsed.alg !== 'ed25519') {\n      console.error(`[bot-auth] Unsupported signature format from ${req.ip}`);\n      return res.status(403).send('Invalid bot signature');\n    }\n\n    // Vérifier la fraîcheur (5 minutes max)\n    const age = Math.floor(Date.now() / 1000) - parsed.created;\n    if (age > 300 || age \u003C -60) {\n      console.error(`[bot-auth] Stale signature (age: ${age}s) from ${req.ip}`);\n      return res.status(403).send('Stale bot signature');\n    }\n\n    const keys = await fetchGoogleCrawlerKeys();\n    const pubKeyB64 = keys.keys[parsed.keyid];\n\n    if (!pubKeyB64) {\n      console.error(`[bot-auth] Unknown keyid: ${parsed.keyid}`);\n      return res.status(403).send('Unknown bot key');\n    }\n\n    // Reconstruction de la signature base et vérification\n    const sigBase = buildSignatureBase(req, parsed.components, parsed);\n    const pubKey = Buffer.from(pubKeyB64, 'base64');\n    const sig = Buffer.from(\n      signature.replace('sig1=:', '').replace(':', ''),\n      'base64'\n    );\n\n    const valid = await verify(sig, Buffer.from(sigBase), pubKey);\n\n    if (!valid) {\n      console.error(`[bot-auth] INVALID signature from ${req.ip}`);\n      metrics.increment('bot.googlebot.spoofed');\n      return res.status(403).send('Invalid bot signature');\n    }\n\n    // Signature valide — enrichir la requête\n    (req as any).verifiedBot = {\n      provider: 'google',\n      product: parsed.keyid, // ex: \"google-search-2026\", \"google-ads-2026\"\n    };\n\n    metrics.increment('bot.googlebot.verified');\n    return next();\n  } catch (err) {\n    console.error(`[bot-auth] Verification error:`, err);\n    // En cas d'erreur, ne pas bloquer (fail open pendant la transition)\n    return next();\n  }\n}\n```\n\nLe point crucial dans cette implémentation : la stratégie **fail-open** pendant la phase de transition. Bloquer les requêtes non signées avant que Google n'ait déployé la signature sur 100 % de ses bots serait suicidaire pour votre indexation.\n\n## Impact sur le crawl budget et la gestion des bots\n\n### Un scénario réaliste : e-commerce de 15 000 pages\n\nPrenons un e-commerce mode avec 15 000 URLs indexables (3 000 catégories, 12 000 fiches produit). L'analyse des logs serveur sur 30 jours montre :\n\n| Métrique | Valeur actuelle |\n|---|---|\n| Hits totaux \"Googlebot\" (User-Agent) | 890 000/mois |\n| Hits vérifiés (reverse DNS) | 578 000/mois (65 %) |\n| Hits non vérifiables | 312 000/mois (35 %) |\n| Coût SSR moyen par hit | 45 ms CPU |\n| Coût mensuel SSR pour faux bots | ~14 000 secondes CPU = ~3.9 heures |\n\nAvec le standard d'authentification cryptographique, la vérification de signature Ed25519 prend environ 0.1 ms — soit 450x plus rapide qu'un aller-retour DNS. Mais surtout, le gain principal n'est pas la vitesse de vérification : c'est la **capacité à bloquer les 312 000 hits de faux bots** avec certitude.\n\nCela libère immédiatement :\n- De la bande passante serveur pour le vrai Googlebot\n- Des cycles de rendu SSR gaspillés\n- Une analyse de logs propre, sans bruit\n\nCe dernier point est sous-estimé. Si 35 % de vos hits \"Googlebot\" sont faux, votre analyse de crawl budget est faussée d'un tiers. Vous pourriez décider de désindexer des pages que Google ne crawle pas trop — c'est un scraper qui le fait.\n\n### Granularité par produit Google\n\nL'usage de `keyid` distincts par produit ouvre une possibilité inédite : appliquer des politiques différentes selon que c'est Googlebot Search, Google Ads, ou un crawler IA de Google.\n\nC'est particulièrement pertinent dans le contexte actuel où [l'activité de crawl des modèles IA explose](/blog/openai-crawl-activity-tripled-since-gpt-5-data-shows-via-sejournal-mattgsouthern). Si Google différencie ses crawlers IA de ses crawlers Search au niveau cryptographique, vous pourrez bloquer l'un sans affecter l'autre, avec une certitude impossible aujourd'hui.\n\nDans le robots.txt actuel, vous pouvez cibler `Google-Extended` pour les usages IA, mais rien ne garantit qu'un bot qui se déclare `Google-Extended` l'est réellement. L'authentification cryptographique résout ce problème.\n\n## Les implications pour l'écosystème : au-delà de Google\n\n### Un standard multi-moteurs ?\n\nSi Google pousse ce standard, Bing, Apple (Applebot), et les crawlers IA (OpenAI, Anthropic, Perplexity) seront sous pression pour l'adopter. Le draft RFC 9421 est suffisamment générique pour être utilisé par n'importe quel crawler.\n\nImaginez un fichier `.well-known/crawler-keys.json` standardisé par moteur :\n\n```json\n{\n  \"google\": {\n    \"keys_url\": \"https://www.google.com/.well-known/crawler-keys.json\"\n  },\n  \"bing\": {\n    \"keys_url\": \"https://www.bing.com/.well-known/crawler-keys.json\"\n  },\n  \"openai\": {\n    \"keys_url\": \"https://openai.com/.well-known/crawler-keys.json\"\n  }\n}\n```\n\nCe mécanisme permettrait aux éditeurs de vérifier l'identité de n'importe quel crawler de façon uniforme. C'est un gain énorme pour les sites qui doivent gérer des politiques de crawl différenciées — la quasi-totalité des sites au-dessus de 5 000 pages.\n\nGoogle a déjà indiqué aux développeurs de [construire pour les agents IA, pas seulement les humains](/blog/google-tells-developers-to-build-for-ai-agents-not-just-humans-via-sejournal-mattgsouthern). Un standard d'authentification des bots est la brique d'infrastructure manquante pour rendre cette cohabitation viable.\n\n### Le risque d'un web à deux vitesses\n\nIl y a un trade-off à ne pas ignorer. Si les bots authentifiés deviennent la norme, les petits crawlers légitimes (outils SEO indépendants, crawlers académiques, projets open source) qui n'ont pas les ressources pour implémenter la signature cryptographique pourraient se retrouver bloqués par défaut.\n\nLe risque est que les serveurs passent en mode \"deny all unsigned bots\", ce qui favorise mécaniquement les gros acteurs qui auront implémenté le standard en premier. C'est le même problème que le HTTPS obligatoire a posé aux petits sites en 2017 — sauf que cette fois, c'est côté client (le bot) que l'effort technique est requis.\n\nScreaming Frog, Ahrefs, Semrush et les autres outils de crawl devront implémenter ce standard s'ils veulent continuer à crawler les sites qui l'exigent. C'est un coût non négligeable pour l'écosystème.\n\n## Comment auditer votre exposition actuelle au bot spoofing\n\nAvant d'implémenter la vérification cryptographique, commencez par mesurer l'ampleur du problème sur votre site.\n\n### Étape 1 : extraire les hits Googlebot des logs\n\n```bash\n# Extraire les IPs uniques qui déclarent être Googlebot (format Combined Log)\ngrep -i \"googlebot\" /var/log/nginx/access.log \\\n  | awk '{print $1}' \\\n  | sort -u \\\n  > /tmp/googlebot_ips.txt\n\n# Compter les hits par IP\ngrep -i \"googlebot\" /var/log/nginx/access.log \\\n  | awk '{print $1}' \\\n  | sort \\\n  | uniq -c \\\n  | sort -rn \\\n  | head -20\n\n# Vérifier chaque IP par reverse DNS\nwhile read ip; do\n  result=$(host \"$ip\" 2>/dev/null | grep -o '[a-z0-9.-]*googlebot\\.com\\|[a-z0-9.-]*google\\.com' | head -1)\n  if [ -z \"$result\" ]; then\n    echo \"FAKE: $ip\"\n  else\n    echo \"LEGIT: $ip -> $result\"\n  fi\ndone \u003C /tmp/googlebot_ips.txt\n```\n\n### Étape 2 : croiser avec les plages IP officielles\n\nGoogle publie ses plages IP pour Googlebot dans un [fichier JSON dédié](https://developers.google.com/search/docs/crawling-indexing/verifying-googlebot) :\n\n```bash\n# Télécharger les plages IP officielles de Googlebot\ncurl -s https://developers.google.com/static/search/apis/ipranges/googlebot.json \\\n  | jq -r '.prefixes[].ipv4Prefix // .prefixes[].ipv6Prefix' \\\n  > /tmp/google_ranges.txt\n\n# Vérifier si une IP suspecte est dans les plages (avec grepcidr)\ngrepcidr -f /tmp/google_ranges.txt /tmp/googlebot_ips.txt\n```\n\n### Étape 3 : quantifier l'impact\n\nCroisez vos résultats avec le rapport de crawl dans Search Console. Si la GSC rapporte que Google crawle 8 000 pages/jour sur votre site, mais que vos logs montrent 15 000 hits/jour de \"Googlebot\", vous avez 7 000 hits fantômes quotidiens. Ce delta est votre exposition au bot spoofing.\n\nUn outil de monitoring comme Seogard peut automatiser cette détection en continu, en alertant dès qu'un pic de crawl \"Googlebot\" dans les logs ne corrèle pas avec l'activité reportée par Search Console — un signal classique de scraping déguisé.\n\n## Ce que ça change pour le SEO technique au quotidien\n\n### robots.txt : enfin exécutable\n\nLe robots.txt a toujours été un fichier de politesse. Rien n'oblige un bot à le respecter, et rien ne permet de vérifier qu'un bot qui prétend le respecter est bien celui qu'il dit être.\n\nAvec l'authentification cryptographique, le robots.txt devient potentiellement **exécutable** : vous pouvez vérifier l'identité du bot, et donc appliquer des règles avec certitude. Un `Disallow` pour `Google-Extended` signifie réellement quelque chose si vous pouvez prouver que le bot qui accède à la page n'est pas `Google-Extended` authentifié.\n\nCela rejoint l'enjeu du [web non-humain](/blog/the-fully-non-human-web-no-one-builds-the-page-no-one-visits-it-via-sejournal-slobodanmanic) : dans un écosystème où les bots représentent une part croissante du trafic, la capacité à distinguer et contrôler qui accède à quoi n'est plus un luxe. C'est une nécessité opérationnelle.\n\n### Monitoring : de nouvelles métriques à suivre\n\nL'arrivée de ce standard crée de nouvelles dimensions de monitoring :\n- **Taux de requêtes signées vs non signées** par bot déclaré\n- **Taux de signatures invalides** (indicateur de tentatives de spoofing avancé)\n- **Distribution par `keyid`** pour comprendre quels produits Google crawlent quoi\n- **Latence de vérification** pour s'assurer que le middleware n'impacte pas le TTFB\n\nCes métriques n'existent dans aucun outil aujourd'hui. Les premiers à les implémenter dans leurs dashboards de crawl auront un avantage significatif dans la compréhension de leur trafic bot.\n\n### Période de transition : la stratégie recommandée\n\nGoogle déploiera probablement ce standard progressivement. La stratégie optimale pour les 6 à 12 prochains mois :\n\n1. **Maintenant** : auditer votre exposition au bot spoofing (script ci-dessus). Quantifier le problème.\n2. **Dès la publication de la spec** : implémenter la vérification en mode **log-only** (pas de blocage). Collecter des données pendant 4 à 8 semaines.\n3. **Après validation** : passer en mode **warn** — envoyer un header `X-Bot-Auth: required` pour signaler aux bots non authentifiés qu'ils devront s'authentifier à terme.\n4. **Quand Google confirme 100 % de couverture** : passer en mode **enforce** — bloquer les requêtes \"Googlebot\" non signées.\n\nNe sautez pas les étapes. Un blocage prématuré d'un Googlebot légitime qui n'a pas encore la signature activée détruirait votre indexation.\n\n## Ce qu'il faut retenir\n\nL'authentification cryptographique des bots est le changement d'infrastructure le plus significatif dans la relation serveur/crawler depuis l'adoption de robots.txt en 1994. Le passage d'un modèle déclaratif (User-Agent) à un modèle prouvable (signature cryptographique) rendra le bot spoofing techniquement impossible pour les moteurs qui l'adoptent, et donnera aux éditeurs un contrôle réel — pas théorique — sur qui accède à leur contenu.\n\nCommencez dès aujourd'hui par auditer votre trafic bot avec les scripts fournis, et préparez votre stack serveur à intégrer un middleware de vérification de signatures. C'est le genre de changement que Seogard suivra de près — la détection automatique de bots non authentifiés qui prétendent être Googlebot est exactement le type de régression invisible qu'un monitoring continu doit attraper avant qu'elle ne fausse vos décisions SEO.","https://seogard.io/blog/google-is-testing-new-bot-authorization-standard-via-sejournal-martinibuster","Actualités SEO","2026-05-06T06:03:05.814Z","2026-05-06","Google teste un protocole cryptographique d'authentification des bots. Analyse technique, impact sur le crawl et implémentation côté serveur.","\u003Cp>Aujourd'hui, distinguer Googlebot d'un scraper qui usurpe son User-Agent repose sur un reverse DNS suivi d'un forward DNS — une méthode lente, fragile, et que personne n'automatise à l'échelle sur un parc de 20 000 pages. Google teste un nouveau standard d'autorisation cryptographique des bots qui pourrait rendre cette vérification instantanée et fiable. Voici ce que ça change techniquement, et comment anticiper l'implémentation.\u003C/p>\n\u003Ch2>Le problème actuel : l'identité des bots repose sur la confiance aveugle\u003C/h2>\n\u003Cp>Le User-Agent HTTP est une chaîne déclarative. N'importe quel script Python peut envoyer \u003Ccode>Googlebot/2.1 (+http://www.google.com/bot.html)\u003C/code> dans ses headers. C'est exactement ce que font les scrapers commerciaux, les outils de competitive intelligence, et les bots malveillants qui veulent bénéficier des règles permissives que vous accordez à Google dans votre robots.txt.\u003C/p>\n\u003Ch3>La vérification DNS : nécessaire mais insuffisante\u003C/h3>\n\u003Cp>La \u003Ca href=\"https://developers.google.com/search/docs/crawling-indexing/verifying-googlebot\">méthode officielle recommandée par Google\u003C/a> pour vérifier l'authenticité de Googlebot repose sur deux étapes :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Étape 1 : Reverse DNS sur l'IP du crawler\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">host\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 66.249.66.1\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Résultat attendu : crawl-66-249-66-1.googlebot.com\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Étape 2 : Forward DNS pour confirmer\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">host\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> crawl-66-249-66-1.googlebot.com\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Résultat attendu : 66.249.66.1\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Cette méthode fonctionne, mais elle présente des limites sérieuses en production :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>Latence\u003C/strong> : chaque requête DNS ajoute 20 à 100 ms. Sur un site e-commerce qui reçoit 50 000 hits de bots par jour, ça représente une charge non négligeable si vous vérifiez en temps réel.\u003C/li>\n\u003Cli>\u003Cstrong>Pas de standard machine-readable\u003C/strong> : la vérification est manuelle ou scriptée ad hoc. Aucun middleware serveur standard ne l'implémente nativement.\u003C/li>\n\u003Cli>\u003Cstrong>Faux négatifs\u003C/strong> : les IP de Google évoluent. Le fichier JSON des plages IP Googlebot est mis à jour régulièrement, mais si votre cache DNS est périmé, vous pouvez bloquer le vrai Googlebot.\u003C/li>\n\u003Cli>\u003Cstrong>Aucune granularité\u003C/strong> : vous savez que c'est \"un bot Google\", mais vous ne savez pas s'il crawle pour Search, pour Ads, pour Vertex AI, ou pour un autre produit. Pas de distinction de privilèges.\u003C/li>\n\u003C/ul>\n\u003Ch3>Le coût réel du bot spoofing\u003C/h3>\n\u003Cp>Prenons un cas concret. Un média en ligne avec 8 000 articles, 400 000 sessions organiques/mois. L'équipe SEO constate dans les logs serveur que \"Googlebot\" représente 180 000 hits/jour. Après analyse via reverse DNS, 35 % de ces hits proviennent d'IP qui ne résolvent pas vers \u003Ccode>*.googlebot.com\u003C/code> ou \u003Ccode>*.google.com\u003C/code>. Ce sont des scrapers déguisés.\u003C/p>\n\u003Cp>Conséquence directe : ces faux bots consomment de la bande passante, déclenchent des rendus SSR inutiles (coûteux en CPU sur un setup Next.js), et faussent complètement l'analyse du crawl budget dans les logs. L'équipe pense que Google crawle agressivement des pages de faible valeur alors que c'est un concurrent qui scrape le catalogue.\u003C/p>\n\u003Ch2>Ce que Google teste : une authentification cryptographique des bots\u003C/h2>\n\u003Cp>Le standard en test, \u003Ca href=\"https://www.searchenginejournal.com/google-is-testing-new-bot-authorization-standard/573957/\">rapporté par Search Engine Journal\u003C/a>, repose sur un principe de cryptographie asymétrique appliqué aux requêtes HTTP des crawlers. Le concept est analogue à ce que TLS fait pour les serveurs, mais inversé : c'est le \u003Cstrong>client\u003C/strong> (le bot) qui prouve son identité au serveur.\u003C/p>\n\u003Ch3>Le mécanisme probable : signatures HTTP\u003C/h3>\n\u003Cp>Sans que Google ait publié de spécification complète, le mécanisme s'aligne avec le draft IETF \u003Ca href=\"https://www.rfc-editor.org/rfc/rfc9421\">HTTP Message Signatures (RFC 9421)\u003C/a> qui standardise la signature cryptographique des requêtes HTTP. Le principe :\u003C/p>\n\u003Col>\n\u003Cli>Google publie une clé publique à une URL bien connue (probablement un endpoint \u003Ccode>.well-known\u003C/code>).\u003C/li>\n\u003Cli>Chaque requête de Googlebot inclut un header \u003Ccode>Signature\u003C/code> et un header \u003Ccode>Signature-Input\u003C/code> contenant une signature cryptographique générée avec la clé privée correspondante.\u003C/li>\n\u003Cli>Le serveur récupère la clé publique de Google (avec cache), vérifie la signature, et sait avec certitude cryptographique que la requête provient de Google.\u003C/li>\n\u003C/ol>\n\u003Cp>Voici à quoi pourrait ressembler une requête authentifiée :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">GET\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> /catalogue/chaussures-running HTTP/2\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">Host\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> www.example-ecommerce.fr\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">User-Agent\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> Googlebot/2.1 (+http://www.google.com/bot.html)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">Signature-Input\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> sig1=(\"@method\" \"@path\" \"@authority\" \"user-agent\");created=1714988400;keyid=\"google-crawl-2026\";alg=\"ed25519\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">Signature\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> sig1=:dGVzdCBzaWduYXR1cmUgYmFzZTY0IGVuY29kZWQ=:\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Les éléments clés :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>\u003Ccode>keyid\u003C/code>\u003C/strong> : identifie la clé publique à utiliser pour la vérification. Google pourrait utiliser des key IDs distincts par produit (Search, Ads, AI).\u003C/li>\n\u003Cli>\u003Cstrong>\u003Ccode>alg\u003C/code>\u003C/strong> : l'algorithme de signature. Ed25519 est compact, rapide, et résistant aux attaques par canal auxiliaire.\u003C/li>\n\u003Cli>\u003Cstrong>\u003Ccode>created\u003C/code>\u003C/strong> : timestamp qui empêche les attaques par rejeu (replay attacks).\u003C/li>\n\u003C/ul>\n\u003Ch3>Pourquoi c'est un changement fondamental\u003C/h3>\n\u003Cp>Ce n'est pas un simple ajustement technique. C'est un changement de paradigme dans la relation crawler/serveur :\u003C/p>\n\u003Cp>\u003Cstrong>Avant\u003C/strong> : le serveur fait confiance au User-Agent et vérifie éventuellement l'IP par DNS. Le modèle est \"présumé légitime sauf preuve contraire\".\u003C/p>\n\u003Cp>\u003Cstrong>Après\u003C/strong> : le bot prouve cryptographiquement son identité à chaque requête. Le modèle devient \"présumé illégitime sauf preuve cryptographique\". C'est exactement le même shift que le passage de HTTP à HTTPS, mais pour l'identité des clients.\u003C/p>\n\u003Cp>Ce standard s'inscrit dans une tendance plus large où Google structure ses interactions avec les éditeurs de façon plus formelle. La \u003Ca href=\"https://seogard.io/blog/google-s-robots-txt-docs-expand-deep-links-get-rules-eu-steps-in-seo-pulse-via-sejournal-mattgsouthern\">récente expansion de la documentation robots.txt\u003C/a> montre cette même direction : des protocoles plus explicites, plus vérifiables.\u003C/p>\n\u003Ch2>Implémentation côté serveur : comment se préparer\u003C/h2>\n\u003Cp>Même si le standard n'est pas finalisé, vous pouvez dès maintenant structurer votre infrastructure pour être prêt. Voici les implémentations probables selon votre stack.\u003C/p>\n\u003Ch3>Nginx : vérification via un module Lua\u003C/h3>\n\u003Cp>Nginx n'a pas de support natif des HTTP Message Signatures, mais avec \u003Ccode>lua-nginx-module\u003C/code> (inclus dans OpenResty), vous pouvez implémenter la vérification :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># /etc/nginx/conf.d/bot-auth.conf\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">lua_shared_dict\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> google_keys \u003C/span>\u003Cspan style=\"color:#79B8FF\">1m\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">server\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    listen \u003C/span>\u003Cspan style=\"color:#79B8FF\">443\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ssl http2;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    server_name \u003C/span>\u003Cspan style=\"color:#E1E4E8\">www.example-ecommerce.fr;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    # Vérification de la signature pour les requêtes Googlebot\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    access_by_lua_block\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        local\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> user_agent \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ngx.\u003C/span>\u003Cspan style=\"color:#B392F0\">var\u003C/span>\u003Cspan style=\"color:#E1E4E8\">.\u003C/span>\u003Cspan style=\"color:#B392F0\">http_user_agent\u003C/span>\u003Cspan style=\"color:#F97583\"> or\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">        -- Ne vérifier que les requêtes qui prétendent être Googlebot\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        if\u003C/span>\u003Cspan style=\"color:#F97583\"> not\u003C/span>\u003Cspan style=\"color:#79B8FF\"> string.find\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(user_agent, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"Googlebot\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) \u003C/span>\u003Cspan style=\"color:#F97583\">then\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">            return\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        end\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        local\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> sig \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ngx.\u003C/span>\u003Cspan style=\"color:#B392F0\">var\u003C/span>\u003Cspan style=\"color:#E1E4E8\">.\u003C/span>\u003Cspan style=\"color:#B392F0\">http_signature\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        local\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> sig_input \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ngx.\u003C/span>\u003Cspan style=\"color:#B392F0\">var\u003C/span>\u003Cspan style=\"color:#E1E4E8\">.\u003C/span>\u003Cspan style=\"color:#B392F0\">http_signature_input\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        if\u003C/span>\u003Cspan style=\"color:#F97583\"> not\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> sig \u003C/span>\u003Cspan style=\"color:#F97583\">or\u003C/span>\u003Cspan style=\"color:#F97583\"> not\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> sig_input \u003C/span>\u003Cspan style=\"color:#F97583\">then\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">            -- Googlebot sans signature : loguer et décider de la politique\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">            ngx.\u003C/span>\u003Cspan style=\"color:#79B8FF\">log\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(ngx.\u003C/span>\u003Cspan style=\"color:#B392F0\">WARN\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"Googlebot claim without signature from \"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, ngx.\u003C/span>\u003Cspan style=\"color:#B392F0\">var\u003C/span>\u003Cspan style=\"color:#E1E4E8\">.\u003C/span>\u003Cspan style=\"color:#B392F0\">remote_addr\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">            -- Phase transitoire : ne pas bloquer, juste loguer\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">            -- ngx.exit(403)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">            return\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        end\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">        -- Récupérer la clé publique Google (avec cache de 24h)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        local\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> keys \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ngx.\u003C/span>\u003Cspan style=\"color:#B392F0\">shared\u003C/span>\u003Cspan style=\"color:#E1E4E8\">.\u003C/span>\u003Cspan style=\"color:#B392F0\">google_keys\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        local\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> pub_key \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#B392F0\"> keys\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003Cspan style=\"color:#79B8FF\">get\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"google-crawl-2026\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        if\u003C/span>\u003Cspan style=\"color:#F97583\"> not\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> pub_key \u003C/span>\u003Cspan style=\"color:#F97583\">then\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">            -- Fetch depuis https://www.google.com/.well-known/crawler-keys.json\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">            -- (URL hypothétique basée sur les conventions .well-known)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">            local\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> http \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\"> require\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"resty.http\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">            local\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> httpc \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> http.\u003C/span>\u003Cspan style=\"color:#79B8FF\">new\u003C/span>\u003Cspan style=\"color:#E1E4E8\">()\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">            local\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> res, err \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#B392F0\"> httpc\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003Cspan style=\"color:#79B8FF\">request_uri\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                \"https://www.google.com/.well-known/crawler-keys.json\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">                { method \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"GET\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, ssl_verify \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\"> true\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">            )\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">            if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> res and res.status == \u003C/span>\u003Cspan style=\"color:#79B8FF\">200\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> then\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">                local\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> cjson = require(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"cjson\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">                local\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> key_data = cjson.decode(res.body)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">                pub_key\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> = key_data.keys[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"google-crawl-2026\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">                keys:set(\"google-crawl-2026\", pub_key, 86400) -- \u003C/span>\u003Cspan style=\"color:#F97583\">cache\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> 24h\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">            end\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        end\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        -- Vérification \u003C/span>\u003Cspan style=\"color:#F97583\">Ed25519\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> via lua-resty-openssl\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        -- (implémentation simplifiée — \u003C/span>\u003Cspan style=\"color:#F97583\">la\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> vraie nécessite le parsing de Signature-Input)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        local\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> openssl = require(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"resty.openssl\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        local\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> verified = verify_signature(pub_key, sig, sig_input, ngx.req)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> not verified then\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">            ngx.log(ngx.ERR, \"\u003C/span>\u003Cspan style=\"color:#F97583\">Invalid\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> Googlebot signature from \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\", ngx.var.remote_addr)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            ngx.exit(403)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        end\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">}\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Ce code est volontairement simplifié. En production, vous devrez :\u003C/p>\n\u003Cul>\n\u003Cli>Parser le \u003Ccode>Signature-Input\u003C/code> pour extraire les covered components\u003C/li>\n\u003Cli>Reconstruire la signature base selon RFC 9421 §2.5\u003C/li>\n\u003Cli>Gérer la rotation des clés (key rollover)\u003C/li>\n\u003Cli>Implémenter un fallback vers la vérification DNS pendant la période de transition\u003C/li>\n\u003C/ul>\n\u003Ch3>Node.js / Express : middleware de vérification\u003C/h3>\n\u003Cp>Pour les stacks JavaScript (Next.js, Nuxt, Express), un middleware est plus naturel :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">// middleware/bot-auth.ts\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> { verify } \u003C/span>\u003Cspan style=\"color:#F97583\">from\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '@noble/ed25519'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> { createHash } \u003C/span>\u003Cspan style=\"color:#F97583\">from\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'crypto'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">interface\u003C/span>\u003Cspan style=\"color:#B392F0\"> CrawlerKeySet\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">  keys\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#B392F0\"> Record\u003C/span>\u003Cspan style=\"color:#E1E4E8\">&#x3C;\u003C/span>\u003Cspan style=\"color:#79B8FF\">string\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#79B8FF\">string\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>; \u003C/span>\u003Cspan style=\"color:#6A737D\">// keyid -> base64 public key\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">  expires\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#79B8FF\"> number\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">let\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> cachedKeys\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#B392F0\"> CrawlerKeySet\u003C/span>\u003Cspan style=\"color:#F97583\"> |\u003C/span>\u003Cspan style=\"color:#79B8FF\"> null\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#79B8FF\"> null\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">async\u003C/span>\u003Cspan style=\"color:#F97583\"> function\u003C/span>\u003Cspan style=\"color:#B392F0\"> fetchGoogleCrawlerKeys\u003C/span>\u003Cspan style=\"color:#E1E4E8\">()\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#B392F0\"> Promise\u003C/span>\u003Cspan style=\"color:#E1E4E8\">&#x3C;\u003C/span>\u003Cspan style=\"color:#B392F0\">CrawlerKeySet\u003C/span>\u003Cspan style=\"color:#E1E4E8\">> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (cachedKeys \u003C/span>\u003Cspan style=\"color:#F97583\">&#x26;&#x26;\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> Date.\u003C/span>\u003Cspan style=\"color:#B392F0\">now\u003C/span>\u003Cspan style=\"color:#E1E4E8\">() \u003C/span>\u003Cspan style=\"color:#F97583\">&#x3C;\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> cachedKeys.expires) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> cachedKeys;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> res\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#F97583\"> await\u003C/span>\u003Cspan style=\"color:#B392F0\"> fetch\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'https://www.google.com/.well-known/crawler-keys.json'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    signal: AbortSignal.\u003C/span>\u003Cspan style=\"color:#B392F0\">timeout\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#79B8FF\">5000\u003C/span>\u003Cspan style=\"color:#E1E4E8\">),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  });\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (\u003C/span>\u003Cspan style=\"color:#F97583\">!\u003C/span>\u003Cspan style=\"color:#E1E4E8\">res.ok) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    throw\u003C/span>\u003Cspan style=\"color:#F97583\"> new\u003C/span>\u003Cspan style=\"color:#B392F0\"> Error\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">`Failed to fetch crawler keys: ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">res\u003C/span>\u003Cspan style=\"color:#9ECBFF\">.\u003C/span>\u003Cspan style=\"color:#E1E4E8\">status\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}`\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> data\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#F97583\"> await\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> res.\u003C/span>\u003Cspan style=\"color:#B392F0\">json\u003C/span>\u003Cspan style=\"color:#E1E4E8\">();\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  cachedKeys \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    keys: data.keys,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    expires: Date.\u003C/span>\u003Cspan style=\"color:#B392F0\">now\u003C/span>\u003Cspan style=\"color:#E1E4E8\">() \u003C/span>\u003Cspan style=\"color:#F97583\">+\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 86_400_000\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#6A737D\">// Cache 24h\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  };\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  return\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> cachedKeys;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">function\u003C/span>\u003Cspan style=\"color:#B392F0\"> parseSignatureInput\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#FFAB70\">input\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#79B8FF\"> string\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">  keyid\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#79B8FF\"> string\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">  alg\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#79B8FF\"> string\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">  created\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#79B8FF\"> number\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">  components\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#79B8FF\"> string\u003C/span>\u003Cspan style=\"color:#E1E4E8\">[];\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">} \u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#79B8FF\"> null\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">  // Parse selon RFC 9421 Structured Fields\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> match\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> input.\u003C/span>\u003Cspan style=\"color:#B392F0\">match\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    /\u003C/span>\u003Cspan style=\"color:#DBEDFF\">sig1=\u003C/span>\u003Cspan style=\"color:#85E89D;font-weight:bold\">\\(\u003C/span>\u003Cspan style=\"color:#DBEDFF\">(\u003C/span>\u003Cspan style=\"color:#79B8FF\">[\u003C/span>\u003Cspan style=\"color:#F97583\">^\u003C/span>\u003Cspan style=\"color:#79B8FF\">)]\u003C/span>\u003Cspan style=\"color:#F97583\">+\u003C/span>\u003Cspan style=\"color:#DBEDFF\">)\u003C/span>\u003Cspan style=\"color:#85E89D;font-weight:bold\">\\)\u003C/span>\u003Cspan style=\"color:#DBEDFF\">;created=(\u003C/span>\u003Cspan style=\"color:#79B8FF\">\\d\u003C/span>\u003Cspan style=\"color:#F97583\">+\u003C/span>\u003Cspan style=\"color:#DBEDFF\">);keyid=\"(\u003C/span>\u003Cspan style=\"color:#79B8FF\">[\u003C/span>\u003Cspan style=\"color:#F97583\">^\u003C/span>\u003Cspan style=\"color:#79B8FF\">\"]\u003C/span>\u003Cspan style=\"color:#F97583\">+\u003C/span>\u003Cspan style=\"color:#DBEDFF\">)\";alg=\"(\u003C/span>\u003Cspan style=\"color:#79B8FF\">[\u003C/span>\u003Cspan style=\"color:#F97583\">^\u003C/span>\u003Cspan style=\"color:#79B8FF\">\"]\u003C/span>\u003Cspan style=\"color:#F97583\">+\u003C/span>\u003Cspan style=\"color:#DBEDFF\">)\"\u003C/span>\u003Cspan style=\"color:#9ECBFF\">/\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  );\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (\u003C/span>\u003Cspan style=\"color:#F97583\">!\u003C/span>\u003Cspan style=\"color:#E1E4E8\">match) \u003C/span>\u003Cspan style=\"color:#F97583\">return\u003C/span>\u003Cspan style=\"color:#79B8FF\"> null\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  return\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    components: match[\u003C/span>\u003Cspan style=\"color:#79B8FF\">1\u003C/span>\u003Cspan style=\"color:#E1E4E8\">].\u003C/span>\u003Cspan style=\"color:#B392F0\">split\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">' '\u003C/span>\u003Cspan style=\"color:#E1E4E8\">).\u003C/span>\u003Cspan style=\"color:#B392F0\">map\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#FFAB70\">c\u003C/span>\u003Cspan style=\"color:#F97583\"> =>\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> c.\u003C/span>\u003Cspan style=\"color:#B392F0\">replace\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">/\u003C/span>\u003Cspan style=\"color:#DBEDFF\">\"\u003C/span>\u003Cspan style=\"color:#9ECBFF\">/\u003C/span>\u003Cspan style=\"color:#F97583\">g\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">''\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    created: \u003C/span>\u003Cspan style=\"color:#B392F0\">parseInt\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(match[\u003C/span>\u003Cspan style=\"color:#79B8FF\">2\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    keyid: match[\u003C/span>\u003Cspan style=\"color:#79B8FF\">3\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    alg: match[\u003C/span>\u003Cspan style=\"color:#79B8FF\">4\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  };\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">export\u003C/span>\u003Cspan style=\"color:#F97583\"> async\u003C/span>\u003Cspan style=\"color:#F97583\"> function\u003C/span>\u003Cspan style=\"color:#B392F0\"> botAuthMiddleware\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">  req\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#B392F0\"> Request\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">  res\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#B392F0\"> Response\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">  next\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#B392F0\"> NextFunction\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> ua\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> req.headers[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'user-agent'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">||\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> ''\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">  // Ne vérifier que les bots qui déclarent être Googlebot\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (\u003C/span>\u003Cspan style=\"color:#F97583\">!\u003C/span>\u003Cspan style=\"color:#E1E4E8\">ua.\u003C/span>\u003Cspan style=\"color:#B392F0\">includes\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Googlebot'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C/span>\u003Cspan style=\"color:#B392F0\"> next\u003C/span>\u003Cspan style=\"color:#E1E4E8\">();\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> signature\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> req.headers[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'signature'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">as\u003C/span>\u003Cspan style=\"color:#79B8FF\"> string\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> signatureInput\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> req.headers[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'signature-input'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">as\u003C/span>\u003Cspan style=\"color:#79B8FF\"> string\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (\u003C/span>\u003Cspan style=\"color:#F97583\">!\u003C/span>\u003Cspan style=\"color:#E1E4E8\">signature \u003C/span>\u003Cspan style=\"color:#F97583\">||\u003C/span>\u003Cspan style=\"color:#F97583\"> !\u003C/span>\u003Cspan style=\"color:#E1E4E8\">signatureInput) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    // Phase transitoire : loguer les Googlebot sans signature\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    console.\u003C/span>\u003Cspan style=\"color:#B392F0\">warn\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">`[bot-auth] Unsigned Googlebot request from ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">req\u003C/span>\u003Cspan style=\"color:#9ECBFF\">.\u003C/span>\u003Cspan style=\"color:#E1E4E8\">ip\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}`\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    // Métriques pour dashboarding\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    metrics.\u003C/span>\u003Cspan style=\"color:#B392F0\">increment\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'bot.googlebot.unsigned'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C/span>\u003Cspan style=\"color:#B392F0\"> next\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(); \u003C/span>\u003Cspan style=\"color:#6A737D\">// Ne pas bloquer pendant la transition\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  try\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> parsed\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#B392F0\"> parseSignatureInput\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(signatureInput);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (\u003C/span>\u003Cspan style=\"color:#F97583\">!\u003C/span>\u003Cspan style=\"color:#E1E4E8\">parsed \u003C/span>\u003Cspan style=\"color:#F97583\">||\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> parsed.alg \u003C/span>\u003Cspan style=\"color:#F97583\">!==\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'ed25519'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      console.\u003C/span>\u003Cspan style=\"color:#B392F0\">error\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">`[bot-auth] Unsupported signature format from ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">req\u003C/span>\u003Cspan style=\"color:#9ECBFF\">.\u003C/span>\u003Cspan style=\"color:#E1E4E8\">ip\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}`\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">      return\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> res.\u003C/span>\u003Cspan style=\"color:#B392F0\">status\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#79B8FF\">403\u003C/span>\u003Cspan style=\"color:#E1E4E8\">).\u003C/span>\u003Cspan style=\"color:#B392F0\">send\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Invalid bot signature'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    // Vérifier la fraîcheur (5 minutes max)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> age\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> Math.\u003C/span>\u003Cspan style=\"color:#B392F0\">floor\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(Date.\u003C/span>\u003Cspan style=\"color:#B392F0\">now\u003C/span>\u003Cspan style=\"color:#E1E4E8\">() \u003C/span>\u003Cspan style=\"color:#F97583\">/\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 1000\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) \u003C/span>\u003Cspan style=\"color:#F97583\">-\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> parsed.created;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (age \u003C/span>\u003Cspan style=\"color:#F97583\">>\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 300\u003C/span>\u003Cspan style=\"color:#F97583\"> ||\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> age \u003C/span>\u003Cspan style=\"color:#F97583\">&#x3C;\u003C/span>\u003Cspan style=\"color:#F97583\"> -\u003C/span>\u003Cspan style=\"color:#79B8FF\">60\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      console.\u003C/span>\u003Cspan style=\"color:#B392F0\">error\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">`[bot-auth] Stale signature (age: ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">age\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}s) from ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">req\u003C/span>\u003Cspan style=\"color:#9ECBFF\">.\u003C/span>\u003Cspan style=\"color:#E1E4E8\">ip\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}`\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">      return\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> res.\u003C/span>\u003Cspan style=\"color:#B392F0\">status\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#79B8FF\">403\u003C/span>\u003Cspan style=\"color:#E1E4E8\">).\u003C/span>\u003Cspan style=\"color:#B392F0\">send\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Stale bot signature'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> keys\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#F97583\"> await\u003C/span>\u003Cspan style=\"color:#B392F0\"> fetchGoogleCrawlerKeys\u003C/span>\u003Cspan style=\"color:#E1E4E8\">();\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> pubKeyB64\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> keys.keys[parsed.keyid];\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (\u003C/span>\u003Cspan style=\"color:#F97583\">!\u003C/span>\u003Cspan style=\"color:#E1E4E8\">pubKeyB64) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      console.\u003C/span>\u003Cspan style=\"color:#B392F0\">error\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">`[bot-auth] Unknown keyid: ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">parsed\u003C/span>\u003Cspan style=\"color:#9ECBFF\">.\u003C/span>\u003Cspan style=\"color:#E1E4E8\">keyid\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}`\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">      return\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> res.\u003C/span>\u003Cspan style=\"color:#B392F0\">status\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#79B8FF\">403\u003C/span>\u003Cspan style=\"color:#E1E4E8\">).\u003C/span>\u003Cspan style=\"color:#B392F0\">send\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Unknown bot key'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    // Reconstruction de la signature base et vérification\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> sigBase\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#B392F0\"> buildSignatureBase\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(req, parsed.components, parsed);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> pubKey\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> Buffer.\u003C/span>\u003Cspan style=\"color:#B392F0\">from\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(pubKeyB64, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'base64'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> sig\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> Buffer.\u003C/span>\u003Cspan style=\"color:#B392F0\">from\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      signature.\u003C/span>\u003Cspan style=\"color:#B392F0\">replace\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'sig1=:'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">''\u003C/span>\u003Cspan style=\"color:#E1E4E8\">).\u003C/span>\u003Cspan style=\"color:#B392F0\">replace\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">':'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">''\u003C/span>\u003Cspan style=\"color:#E1E4E8\">),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">      'base64'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    );\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    const\u003C/span>\u003Cspan style=\"color:#79B8FF\"> valid\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#F97583\"> await\u003C/span>\u003Cspan style=\"color:#B392F0\"> verify\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(sig, Buffer.\u003C/span>\u003Cspan style=\"color:#B392F0\">from\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(sigBase), pubKey);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (\u003C/span>\u003Cspan style=\"color:#F97583\">!\u003C/span>\u003Cspan style=\"color:#E1E4E8\">valid) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      console.\u003C/span>\u003Cspan style=\"color:#B392F0\">error\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">`[bot-auth] INVALID signature from ${\u003C/span>\u003Cspan style=\"color:#E1E4E8\">req\u003C/span>\u003Cspan style=\"color:#9ECBFF\">.\u003C/span>\u003Cspan style=\"color:#E1E4E8\">ip\u003C/span>\u003Cspan style=\"color:#9ECBFF\">}`\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      metrics.\u003C/span>\u003Cspan style=\"color:#B392F0\">increment\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'bot.googlebot.spoofed'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">      return\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> res.\u003C/span>\u003Cspan style=\"color:#B392F0\">status\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#79B8FF\">403\u003C/span>\u003Cspan style=\"color:#E1E4E8\">).\u003C/span>\u003Cspan style=\"color:#B392F0\">send\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Invalid bot signature'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    // Signature valide — enrichir la requête\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    (req \u003C/span>\u003Cspan style=\"color:#F97583\">as\u003C/span>\u003Cspan style=\"color:#79B8FF\"> any\u003C/span>\u003Cspan style=\"color:#E1E4E8\">).verifiedBot \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      provider: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'google'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      product: parsed.keyid, \u003C/span>\u003Cspan style=\"color:#6A737D\">// ex: \"google-search-2026\", \"google-ads-2026\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    };\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    metrics.\u003C/span>\u003Cspan style=\"color:#B392F0\">increment\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'bot.googlebot.verified'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C/span>\u003Cspan style=\"color:#B392F0\"> next\u003C/span>\u003Cspan style=\"color:#E1E4E8\">();\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  } \u003C/span>\u003Cspan style=\"color:#F97583\">catch\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (err) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    console.\u003C/span>\u003Cspan style=\"color:#B392F0\">error\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">`[bot-auth] Verification error:`\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, err);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    // En cas d'erreur, ne pas bloquer (fail open pendant la transition)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C/span>\u003Cspan style=\"color:#B392F0\"> next\u003C/span>\u003Cspan style=\"color:#E1E4E8\">();\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Le point crucial dans cette implémentation : la stratégie \u003Cstrong>fail-open\u003C/strong> pendant la phase de transition. Bloquer les requêtes non signées avant que Google n'ait déployé la signature sur 100 % de ses bots serait suicidaire pour votre indexation.\u003C/p>\n\u003Ch2>Impact sur le crawl budget et la gestion des bots\u003C/h2>\n\u003Ch3>Un scénario réaliste : e-commerce de 15 000 pages\u003C/h3>\n\u003Cp>Prenons un e-commerce mode avec 15 000 URLs indexables (3 000 catégories, 12 000 fiches produit). L'analyse des logs serveur sur 30 jours montre :\u003C/p>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Métrique\u003C/th>\n\u003Cth>Valeur actuelle\u003C/th>\n\u003C/tr>\n\u003C/thead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>Hits totaux \"Googlebot\" (User-Agent)\u003C/td>\n\u003Ctd>890 000/mois\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Hits vérifiés (reverse DNS)\u003C/td>\n\u003Ctd>578 000/mois (65 %)\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Hits non vérifiables\u003C/td>\n\u003Ctd>312 000/mois (35 %)\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Coût SSR moyen par hit\u003C/td>\n\u003Ctd>45 ms CPU\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Coût mensuel SSR pour faux bots\u003C/td>\n\u003Ctd>~14 000 secondes CPU = ~3.9 heures\u003C/td>\n\u003C/tr>\n\u003C/tbody>\n\u003C/table>\n\u003Cp>Avec le standard d'authentification cryptographique, la vérification de signature Ed25519 prend environ 0.1 ms — soit 450x plus rapide qu'un aller-retour DNS. Mais surtout, le gain principal n'est pas la vitesse de vérification : c'est la \u003Cstrong>capacité à bloquer les 312 000 hits de faux bots\u003C/strong> avec certitude.\u003C/p>\n\u003Cp>Cela libère immédiatement :\u003C/p>\n\u003Cul>\n\u003Cli>De la bande passante serveur pour le vrai Googlebot\u003C/li>\n\u003Cli>Des cycles de rendu SSR gaspillés\u003C/li>\n\u003Cli>Une analyse de logs propre, sans bruit\u003C/li>\n\u003C/ul>\n\u003Cp>Ce dernier point est sous-estimé. Si 35 % de vos hits \"Googlebot\" sont faux, votre analyse de crawl budget est faussée d'un tiers. Vous pourriez décider de désindexer des pages que Google ne crawle pas trop — c'est un scraper qui le fait.\u003C/p>\n\u003Ch3>Granularité par produit Google\u003C/h3>\n\u003Cp>L'usage de \u003Ccode>keyid\u003C/code> distincts par produit ouvre une possibilité inédite : appliquer des politiques différentes selon que c'est Googlebot Search, Google Ads, ou un crawler IA de Google.\u003C/p>\n\u003Cp>C'est particulièrement pertinent dans le contexte actuel où \u003Ca href=\"/blog/openai-crawl-activity-tripled-since-gpt-5-data-shows-via-sejournal-mattgsouthern\">l'activité de crawl des modèles IA explose\u003C/a>. Si Google différencie ses crawlers IA de ses crawlers Search au niveau cryptographique, vous pourrez bloquer l'un sans affecter l'autre, avec une certitude impossible aujourd'hui.\u003C/p>\n\u003Cp>Dans le robots.txt actuel, vous pouvez cibler \u003Ccode>Google-Extended\u003C/code> pour les usages IA, mais rien ne garantit qu'un bot qui se déclare \u003Ccode>Google-Extended\u003C/code> l'est réellement. L'authentification cryptographique résout ce problème.\u003C/p>\n\u003Ch2>Les implications pour l'écosystème : au-delà de Google\u003C/h2>\n\u003Ch3>Un standard multi-moteurs ?\u003C/h3>\n\u003Cp>Si Google pousse ce standard, Bing, Apple (Applebot), et les crawlers IA (OpenAI, Anthropic, Perplexity) seront sous pression pour l'adopter. Le draft RFC 9421 est suffisamment générique pour être utilisé par n'importe quel crawler.\u003C/p>\n\u003Cp>Imaginez un fichier \u003Ccode>.well-known/crawler-keys.json\u003C/code> standardisé par moteur :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">{\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  \"google\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">    \"keys_url\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"https://www.google.com/.well-known/crawler-keys.json\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  \"bing\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">    \"keys_url\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"https://www.bing.com/.well-known/crawler-keys.json\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  \"openai\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">    \"keys_url\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"https://openai.com/.well-known/crawler-keys.json\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Ce mécanisme permettrait aux éditeurs de vérifier l'identité de n'importe quel crawler de façon uniforme. C'est un gain énorme pour les sites qui doivent gérer des politiques de crawl différenciées — la quasi-totalité des sites au-dessus de 5 000 pages.\u003C/p>\n\u003Cp>Google a déjà indiqué aux développeurs de \u003Ca href=\"/blog/google-tells-developers-to-build-for-ai-agents-not-just-humans-via-sejournal-mattgsouthern\">construire pour les agents IA, pas seulement les humains\u003C/a>. Un standard d'authentification des bots est la brique d'infrastructure manquante pour rendre cette cohabitation viable.\u003C/p>\n\u003Ch3>Le risque d'un web à deux vitesses\u003C/h3>\n\u003Cp>Il y a un trade-off à ne pas ignorer. Si les bots authentifiés deviennent la norme, les petits crawlers légitimes (outils SEO indépendants, crawlers académiques, projets open source) qui n'ont pas les ressources pour implémenter la signature cryptographique pourraient se retrouver bloqués par défaut.\u003C/p>\n\u003Cp>Le risque est que les serveurs passent en mode \"deny all unsigned bots\", ce qui favorise mécaniquement les gros acteurs qui auront implémenté le standard en premier. C'est le même problème que le HTTPS obligatoire a posé aux petits sites en 2017 — sauf que cette fois, c'est côté client (le bot) que l'effort technique est requis.\u003C/p>\n\u003Cp>Screaming Frog, Ahrefs, Semrush et les autres outils de crawl devront implémenter ce standard s'ils veulent continuer à crawler les sites qui l'exigent. C'est un coût non négligeable pour l'écosystème.\u003C/p>\n\u003Ch2>Comment auditer votre exposition actuelle au bot spoofing\u003C/h2>\n\u003Cp>Avant d'implémenter la vérification cryptographique, commencez par mesurer l'ampleur du problème sur votre site.\u003C/p>\n\u003Ch3>Étape 1 : extraire les hits Googlebot des logs\u003C/h3>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Extraire les IPs uniques qui déclarent être Googlebot (format Combined Log)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -i\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"googlebot\"\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> /var/log/nginx/access.log\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  |\u003C/span>\u003Cspan style=\"color:#B392F0\"> awk\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '{print $1}'\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  |\u003C/span>\u003Cspan style=\"color:#B392F0\"> sort\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -u\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  >\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> /tmp/googlebot_ips.txt\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Compter les hits par IP\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -i\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"googlebot\"\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> /var/log/nginx/access.log\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  |\u003C/span>\u003Cspan style=\"color:#B392F0\"> awk\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '{print $1}'\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  |\u003C/span>\u003Cspan style=\"color:#B392F0\"> sort\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  |\u003C/span>\u003Cspan style=\"color:#B392F0\"> uniq\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -c\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  |\u003C/span>\u003Cspan style=\"color:#B392F0\"> sort\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -rn\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  |\u003C/span>\u003Cspan style=\"color:#B392F0\"> head\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -20\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Vérifier chaque IP par reverse DNS\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">while\u003C/span>\u003Cspan style=\"color:#79B8FF\"> read\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> ip\u003C/span>\u003Cspan style=\"color:#E1E4E8\">; \u003C/span>\u003Cspan style=\"color:#F97583\">do\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  result\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$(\u003C/span>\u003Cspan style=\"color:#B392F0\">host\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$ip\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#F97583\"> 2>\u003C/span>\u003Cspan style=\"color:#9ECBFF\">/dev/null\u003C/span>\u003Cspan style=\"color:#F97583\"> |\u003C/span>\u003Cspan style=\"color:#B392F0\"> grep\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -o\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '[a-z0-9.-]*googlebot\\.com\\|[a-z0-9.-]*google\\.com'\u003C/span>\u003Cspan style=\"color:#F97583\"> |\u003C/span>\u003Cspan style=\"color:#B392F0\"> head\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -1\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> [ \u003C/span>\u003Cspan style=\"color:#F97583\">-z\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">$result\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ]; \u003C/span>\u003Cspan style=\"color:#F97583\">then\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">    echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"FAKE: \u003C/span>\u003Cspan style=\"color:#E1E4E8\">$ip\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  else\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">    echo\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> \"LEGIT: \u003C/span>\u003Cspan style=\"color:#E1E4E8\">$ip\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> -> \u003C/span>\u003Cspan style=\"color:#E1E4E8\">$result\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  fi\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">done\u003C/span>\u003Cspan style=\"color:#F97583\"> &#x3C;\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> /tmp/googlebot_ips.txt\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Ch3>Étape 2 : croiser avec les plages IP officielles\u003C/h3>\n\u003Cp>Google publie ses plages IP pour Googlebot dans un \u003Ca href=\"https://developers.google.com/search/docs/crawling-indexing/verifying-googlebot\">fichier JSON dédié\u003C/a> :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Télécharger les plages IP officielles de Googlebot\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">curl\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -s\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> https://developers.google.com/static/search/apis/ipranges/googlebot.json\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  |\u003C/span>\u003Cspan style=\"color:#B392F0\"> jq\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -r\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '.prefixes[].ipv4Prefix // .prefixes[].ipv6Prefix'\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  >\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> /tmp/google_ranges.txt\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Vérifier si une IP suspecte est dans les plages (avec grepcidr)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">grepcidr\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -f\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> /tmp/google_ranges.txt\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> /tmp/googlebot_ips.txt\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Ch3>Étape 3 : quantifier l'impact\u003C/h3>\n\u003Cp>Croisez vos résultats avec le rapport de crawl dans Search Console. Si la GSC rapporte que Google crawle 8 000 pages/jour sur votre site, mais que vos logs montrent 15 000 hits/jour de \"Googlebot\", vous avez 7 000 hits fantômes quotidiens. Ce delta est votre exposition au bot spoofing.\u003C/p>\n\u003Cp>Un outil de monitoring comme Seogard peut automatiser cette détection en continu, en alertant dès qu'un pic de crawl \"Googlebot\" dans les logs ne corrèle pas avec l'activité reportée par Search Console — un signal classique de scraping déguisé.\u003C/p>\n\u003Ch2>Ce que ça change pour le SEO technique au quotidien\u003C/h2>\n\u003Ch3>robots.txt : enfin exécutable\u003C/h3>\n\u003Cp>Le robots.txt a toujours été un fichier de politesse. Rien n'oblige un bot à le respecter, et rien ne permet de vérifier qu'un bot qui prétend le respecter est bien celui qu'il dit être.\u003C/p>\n\u003Cp>Avec l'authentification cryptographique, le robots.txt devient potentiellement \u003Cstrong>exécutable\u003C/strong> : vous pouvez vérifier l'identité du bot, et donc appliquer des règles avec certitude. Un \u003Ccode>Disallow\u003C/code> pour \u003Ccode>Google-Extended\u003C/code> signifie réellement quelque chose si vous pouvez prouver que le bot qui accède à la page n'est pas \u003Ccode>Google-Extended\u003C/code> authentifié.\u003C/p>\n\u003Cp>Cela rejoint l'enjeu du \u003Ca href=\"/blog/the-fully-non-human-web-no-one-builds-the-page-no-one-visits-it-via-sejournal-slobodanmanic\">web non-humain\u003C/a> : dans un écosystème où les bots représentent une part croissante du trafic, la capacité à distinguer et contrôler qui accède à quoi n'est plus un luxe. C'est une nécessité opérationnelle.\u003C/p>\n\u003Ch3>Monitoring : de nouvelles métriques à suivre\u003C/h3>\n\u003Cp>L'arrivée de ce standard crée de nouvelles dimensions de monitoring :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>Taux de requêtes signées vs non signées\u003C/strong> par bot déclaré\u003C/li>\n\u003Cli>\u003Cstrong>Taux de signatures invalides\u003C/strong> (indicateur de tentatives de spoofing avancé)\u003C/li>\n\u003Cli>\u003Cstrong>Distribution par \u003Ccode>keyid\u003C/code>\u003C/strong> pour comprendre quels produits Google crawlent quoi\u003C/li>\n\u003Cli>\u003Cstrong>Latence de vérification\u003C/strong> pour s'assurer que le middleware n'impacte pas le TTFB\u003C/li>\n\u003C/ul>\n\u003Cp>Ces métriques n'existent dans aucun outil aujourd'hui. Les premiers à les implémenter dans leurs dashboards de crawl auront un avantage significatif dans la compréhension de leur trafic bot.\u003C/p>\n\u003Ch3>Période de transition : la stratégie recommandée\u003C/h3>\n\u003Cp>Google déploiera probablement ce standard progressivement. La stratégie optimale pour les 6 à 12 prochains mois :\u003C/p>\n\u003Col>\n\u003Cli>\u003Cstrong>Maintenant\u003C/strong> : auditer votre exposition au bot spoofing (script ci-dessus). Quantifier le problème.\u003C/li>\n\u003Cli>\u003Cstrong>Dès la publication de la spec\u003C/strong> : implémenter la vérification en mode \u003Cstrong>log-only\u003C/strong> (pas de blocage). Collecter des données pendant 4 à 8 semaines.\u003C/li>\n\u003Cli>\u003Cstrong>Après validation\u003C/strong> : passer en mode \u003Cstrong>warn\u003C/strong> — envoyer un header \u003Ccode>X-Bot-Auth: required\u003C/code> pour signaler aux bots non authentifiés qu'ils devront s'authentifier à terme.\u003C/li>\n\u003Cli>\u003Cstrong>Quand Google confirme 100 % de couverture\u003C/strong> : passer en mode \u003Cstrong>enforce\u003C/strong> — bloquer les requêtes \"Googlebot\" non signées.\u003C/li>\n\u003C/ol>\n\u003Cp>Ne sautez pas les étapes. Un blocage prématuré d'un Googlebot légitime qui n'a pas encore la signature activée détruirait votre indexation.\u003C/p>\n\u003Ch2>Ce qu'il faut retenir\u003C/h2>\n\u003Cp>L'authentification cryptographique des bots est le changement d'infrastructure le plus significatif dans la relation serveur/crawler depuis l'adoption de robots.txt en 1994. Le passage d'un modèle déclaratif (User-Agent) à un modèle prouvable (signature cryptographique) rendra le bot spoofing techniquement impossible pour les moteurs qui l'adoptent, et donnera aux éditeurs un contrôle réel — pas théorique — sur qui accède à leur contenu.\u003C/p>\n\u003Cp>Commencez dès aujourd'hui par auditer votre trafic bot avec les scripts fournis, et préparez votre stack serveur à intégrer un middleware de vérification de signatures. C'est le genre de changement que Seogard suivra de près — la détection automatique de bots non authentifiés qui prétendent être Googlebot est exactement le type de régression invisible qu'un monitoring continu doit attraper avant qu'elle ne fausse vos décisions SEO.\u003C/p>",null,12,[18,19,20,21,22],"google","bot authorization","crawl","sécurité","robots.txt","Bot Authorization Standard : ce que teste Google et comment s'y préparer","Wed May 06 2026 06:03:05 GMT+0000 (Coordinated Universal Time)",[26,41,57],{"_id":27,"slug":28,"__v":6,"author":7,"canonical":29,"category":10,"createdAt":30,"date":31,"description":32,"image":15,"imageAlt":15,"readingTime":16,"tags":33,"title":39,"updatedAt":40},"6a041412aa6b273b0c40f181","how-to-build-local-pages-that-win-in-ai-powered-search-via-sejournal-lorenbaker","https://seogard.io/blog/how-to-build-local-pages-that-win-in-ai-powered-search-via-sejournal-lorenbaker","2026-05-13T06:02:58.743Z","2026-05-13","Guide technique pour construire des pages locales qui performent dans les AI Overviews et AI Mode. Schema, SSR, contenu structuré.",[34,35,36,37,38],"local SEO","AI search","pages locales","schema markup","SSR","Pages locales pour l'AI Search : architecture technique","Wed May 13 2026 06:02:58 GMT+0000 (Coordinated Universal Time)",{"_id":42,"slug":43,"__v":6,"author":7,"canonical":44,"category":10,"createdAt":45,"date":46,"description":47,"image":15,"imageAlt":15,"readingTime":48,"tags":49,"title":55,"updatedAt":56},"6a02c291aa6b273b0c2a74f9","the-tech-seo-audit-for-the-ai-search-era-how-to-maximize-your-ai-visibility-via-sejournal-jetoctopus","https://seogard.io/blog/the-tech-seo-audit-for-the-ai-search-era-how-to-maximize-your-ai-visibility-via-sejournal-jetoctopus","2026-05-12T06:02:57.339Z","2026-05-12","Comment adapter votre audit technique SEO aux exigences des AI Overviews, du crawl par les LLMs et du grounding. Méthodes, code et scénarios concrets.",14,[50,51,52,53,54],"tech seo audit","ai search","ai visibility","crawl budget","structured data","Audit SEO technique pour l'ère AI Search : guide avancé","Tue May 12 2026 06:02:57 GMT+0000 (Coordinated Universal Time)",{"_id":58,"slug":59,"__v":6,"author":7,"canonical":60,"category":10,"createdAt":61,"date":46,"description":62,"image":15,"imageAlt":15,"readingTime":16,"tags":63,"title":68,"updatedAt":69},"6a02fac0aa6b273b0c58d096","the-consensus-gap-via-sejournal-kevin-indig","https://seogard.io/blog/the-consensus-gap-via-sejournal-kevin-indig","2026-05-12T10:02:40.519Z","Une marque peut dominer dans un dashboard AI agrégé et être absente de deux moteurs sur trois. Analyse technique du Consensus Gap et méthodes pour le détecter.",[64,35,65,66,67],"consensus gap","LLM visibility","GEO","multi-engine","The Consensus Gap : votre marque visible sur un LLM, invisible sur deux autres","Tue May 12 2026 10:02:40 GMT+0000 (Coordinated Universal Time)"]