Avez-vous vraiment besoin de WordPress ? Migrez votre blog vers S3 + CloudFront avec Claude en moins d'une heure

Traduit de l'original en espagnol. Lire en espagnol

WordPress, Drupal, Joomla — ce sont des plateformes matures qui couvrent beaucoup de cas. Et oui, pour un site avec des utilisateurs, des formulaires, de l’e-commerce ou une équipe éditoriale, elles gardent tout leur sens. Mais si vous ne faites que publier des articles, vous payez cher pour des choses que vous n’utilisez pas : PHP, une base de données, des plugins à patcher et un panneau d’admin exposé sur internet.

L’alternative est ancienne et ennuyeuse : du HTML statique dans un bucket S3, servi par CloudFront. Ce qui a changé, c’est le coût de la migration. Avec Claude Code, la migration complète — contenu, infrastructure en stage et en prod, pipelines — m’a pris entre 30 et 50 minutes.

À titre de référence : j’estime qu’un senior dev qui connaît AWS, en faisant la même chose à la main pour un site de 50 posts, met entre 3 et 5 jours de travail — environ 20 à 35 heures. L’essentiel n’est pas l’infra, mais la conversion et le nettoyage du contenu post par post, et la reproduction du theme. S’il a déjà l’infra en Terraform ou CDK, on descend à un jour et demi ou deux. Ça reste une autre échelle.

Voici le pas à pas, avec les détails techniques et — surtout — le modèle de sécurité.

Una ingeniera frente a un monitor que muestra un panel de migración de un sitio web legacy a uno moderno, con un chip de IA en el centro


Avez-vous besoin d’un CMS ? Les trade-offs

Avant de migrer, il vaut mieux être honnête sur ce que vous perdez et ce que vous gagnez.

Ce que vous gagnez :

  • Surface d’attaque quasi nulle — pas de PHP, pas de base de données, pas de /wp-admin. Rien ne s’exécute côté serveur, donc il n’y a rien à exploiter.
  • Zéro maintenance — fini les updates du core, des plugins et du theme. Un site statique d’il y a cinq ans fonctionne toujours de la même façon.
  • Coût — pour un petit blog, quelques centimes par mois. CloudFront propose un free tier de 1 To de transfert mensuel.
  • Performance — tout est servi depuis l’edge de CloudFront. Pas de rendu par requête.
  • Un vrai versioning — tout le site vit dans Git. Chaque changement a son historique, sa review et son rollback.

Ce que vous perdez :

  • Les commentaires — on les remplace par un service externe (Giscus, par exemple) ou on les supprime. Je les ai supprimés.
  • Les formulaires — il vous faut un endpoint séparé (Lambda, Formspree). Ça ajoute une pièce mobile.
  • La recherche — uniquement côté client (Pagefind, Lunr). Ça fonctionne bien jusqu’à quelques milliers de posts.
  • L’éditeur visuel — vous écrivez en Markdown. Si ce n’est pas votre truc, je montre plus bas comment Claude règle cette partie.

Si la liste de ce que vous perdez ne vous fait pas mal, continuez la lecture.


L’architecture

Le design final est simple :

  • GitHub repo — le contenu en Markdown et le générateur de site statique (j’utilise Astro).
  • GitHub Actions — un pipeline qui build et publie en stage à chaque push sur main, et en prod après une approbation manuelle.
  • S3 — deux buckets privés, un par environnement. Pas de website hosting, pas d’accès public.
  • CloudFront — une distribution par environnement, avec Origin Access Control (OAC) pour lire le bucket, un certificat ACM et une CloudFront Function pour les URL.
  • IAM — deux identités distinctes, avec des permissions distinctes : une temporaire pour que Claude fasse la migration, et un role par environnement pour le pipeline, via OIDC.

Le modèle de sécurité, avant de toucher à quoi que ce soit

L’idée de départ était de donner une access key à Claude, de créer le repo, de créer les pipelines et c’est tout. Ça marche. Mais en y regardant de plus près, trois problèmes apparaissent, et je les ai corrigés avant de commencer :

  • Claude ne peut pas avoir de permissions IAM. Si le user de Claude peut créer des roles et des policies, il peut s’octroyer n’importe quelle permission — c’est de l’escalade de privilèges par conception. L’OIDC provider et les roles du pipeline, c’est vous qui les créez.
  • Le pipeline n’utilise pas d’access keys. Pas de AWS_ACCESS_KEY_ID dans les GitHub Secrets. GitHub Actions s’authentifie auprès d’AWS via OIDC et reçoit des credentials temporaires, et la trust policy restreint quel repo et quel environnement peut assumer chaque role.
  • Claude ne peut pas modifier le pipeline. Si l’agent peut éditer .github/workflows/, il peut changer où l’on publie et avec quelles permissions. Les workflows, c’est vous qui les commitez, et le token de Claude n’a pas le droit d’y toucher.

La règle que j’applique : l’agent opère dans les limites ; il ne définit jamais les limites.


Pas à pas

1. Créez l’IAM user pour Claude

Dans la console IAM, créez un user sans accès à la console — par exemple claude-migration. Attribuez-lui cette policy inline. Remplacez mi-blog par le préfixe de vos buckets :

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "SiteBucketsOnly",
      "Effect": "Allow",
      "Action": [
        "s3:CreateBucket",
        "s3:PutBucketPolicy",
        "s3:GetBucketPolicy",
        "s3:PutBucketPublicAccessBlock",
        "s3:GetBucketPublicAccessBlock",
        "s3:PutBucketOwnershipControls",
        "s3:PutEncryptionConfiguration",
        "s3:ListBucket",
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Resource": [
        "arn:aws:s3:::mi-blog-*",
        "arn:aws:s3:::mi-blog-*/*"
      ]
    },
    {
      "Sid": "CloudFront",
      "Effect": "Allow",
      "Action": [
        "cloudfront:CreateDistribution",
        "cloudfront:UpdateDistribution",
        "cloudfront:GetDistribution",
        "cloudfront:GetDistributionConfig",
        "cloudfront:ListDistributions",
        "cloudfront:CreateOriginAccessControl",
        "cloudfront:GetOriginAccessControl",
        "cloudfront:ListOriginAccessControls",
        "cloudfront:CreateFunction",
        "cloudfront:UpdateFunction",
        "cloudfront:DescribeFunction",
        "cloudfront:TestFunction",
        "cloudfront:PublishFunction",
        "cloudfront:CreateResponseHeadersPolicy",
        "cloudfront:ListResponseHeadersPolicies",
        "cloudfront:ListCachePolicies",
        "cloudfront:CreateInvalidation",
        "cloudfront:GetInvalidation"
      ],
      "Resource": "*"
    },
    {
      "Sid": "Certificates",
      "Effect": "Allow",
      "Action": [
        "acm:RequestCertificate",
        "acm:DescribeCertificate",
        "acm:ListCertificates"
      ],
      "Resource": "*"
    }
  ]
}

Trois points à noter :

  • S3 est restreint par ARN — Claude ne peut toucher qu’aux buckets qui commencent par votre préfixe. Pas de s3:DeleteBucket : supprimer un bucket, c’est votre décision.
  • CloudFront utilise "Resource": "*" — la création de distributions n’admet pas de restriction par ressource. C’est le point le plus large de la policy, et c’est la raison pour laquelle la key n’existe que pendant la migration.
  • Aucune action IAM. C’est voulu.

Si un AccessDenied apparaît pendant la migration, Claude vous dira quelle action manque. Ajoutez-la, et seulement elle. Ne mettez pas cloudfront:* pour débloquer.

Créez l’access key du user et configurez-la dans un profile dédié, pas dans le default :

aws configure --profile claude-migration
export AWS_PROFILE=claude-migration
aws sts get-caller-identity

Ne collez jamais la key dans le chat avec Claude. L’agent utilise le profile ; il n’a pas besoin de voir le secret.

2. Créez le repo et limitez ce que Claude peut faire

Créez le repo sur GitHub (privé, même si le site est public — inutile d’exposer les drafts). Générez ensuite un fine-grained personal access token :

  • Repository access — ce repo uniquement.
  • Contents — read and write.
  • Pull requests — read and write.
  • Workflows — aucun accès. Ainsi, Claude ne peut pas pousser de changements dans .github/workflows/.
  • Expiration — 7 jours.

Côté Claude Code, ajoutez un .claude/settings.json au repo avec des règles de permissions :

{
  "permissions": {
    "deny": [
      "Read(~/.aws/**)",
      "Bash(aws iam:*)",
      "Bash(aws s3 rb:*)"
    ],
    "ask": [
      "Bash(aws cloudfront create-distribution:*)",
      "Bash(aws cloudfront update-distribution:*)",
      "Bash(aws s3 sync:*)",
      "Bash(git push:*)"
    ]
  }
}

Gardez à l’esprit qu’il s’agit d’une deuxième couche, pas de la principale. Un deny sur Read n’empêche pas un cat depuis Bash. Le vrai contrôle, c’est la policy IAM de l’étape précédente. Les règles de Claude Code servent à faire passer les opérations importantes par votre approbation.

3. Migrez le contenu : Claude lit WordPress via MCP

Ici, pas d’export, pas de XML, pas de scripts de conversion. J’installe le plugin AI Engine (de Meow Apps) sur le WordPress, qui expose un MCP server, et je connecte Claude directement au site. Claude lit tous les posts, catégories et tags, télécharge les images et construit le nouveau site. Claude fait tout.

Pour l’authentification, j’utilise OAuth, pas un Bearer Token statique. La différence compte :

  • Pas de secret partagé — pas de token à copier, coller dans une commande, et qui finit dans l’historique du shell ou dans un fichier de config.
  • Une vraie identité — chaque autorisation est liée à un utilisateur WordPress, avec un login normal et un écran de consentement qui indique quelle app demande l’accès et avec quel rôle.
  • Révocation en un clic — le panneau Connected Apps d’AI Engine liste chaque autorisation active.

Dans WordPress, AI Engine → Settings → MCP : activez le MCP server. Avec OAuth, pas besoin de définir de Bearer Token — AI Engine publie les endpoints de discovery (/.well-known/oauth-*) et le client s’enregistre tout seul via Dynamic Client Registration. Depuis le repo :

claude mcp add --transport http wp-origen https://mi-sitio.com/wp-json/mcp/v1/http

Ensuite, dans Claude Code, lancez /mcp, choisissez wp-origen puis Authenticate. Le navigateur s’ouvre, vous vous connectez à WordPress et vous approuvez. Le token est conservé dans le stockage sécurisé de Claude Code, pas dans un fichier du repo.

Et voici la distinction la plus souvent négligée : avec OAuth, Claude a les permissions de l’utilisateur avec lequel vous vous connectez. Si vous approuvez en tant qu’admin, Claude est admin — il peut supprimer des posts, modifier des settings, installer des plugins si le groupe est exposé. OAuth améliore l’authentification ; il ne réduit pas l’autorisation. C’est pourquoi :

  • Créez un utilisateur dédié — par exemple claude-migracion, avec le rôle le plus bas qui lui permet de lire ce dont vous avez besoin. N’approuvez pas avec votre compte admin.
  • N’exposez que le nécessaire — sur l’écran MCP, laissez uniquement le groupe WordPress actif (posts, media, taxonomies). Plugins, Themes et Dynamic REST, désactivés.
  • Vérifiez le WAF — certains hébergeurs bloquent les POST vers /wp-json/mcp/v1/oauth/register et l’enregistrement échoue. Dans ce cas, il faut autoriser /.well-known/oauth-* et /wp-json/mcp/v1/*.

Si votre version d’AI Engine exige un utilisateur admin pour le MCP, le trade-off change : un Bearer Token avec un access level Read-Only est restreint côté serveur, et il peut alors être la plus sûre des deux options. Vérifiez avant de choisir.

En deuxième couche, bloquez les tools d’écriture côté Claude Code. Ajoutez au .claude/settings.json :

{
  "permissions": {
    "deny": [
      "mcp__wp-origen__wp_create_post",
      "mcp__wp-origen__wp_update_post",
      "mcp__wp-origen__wp_delete_post",
      "mcp__wp-origen__wp_upload_media",
      "mcp__wp-origen__wp_update_option"
    ]
  }
}

Les noms exacts des tools dépendent de la version du plugin — /mcp vous en donne la liste. Bloquez tout ce qui écrit. Claude n’a besoin que des tools de lecture.

Une fois le MCP connecté, le prompt :

Conectate al MCP wp-origen y migrá el sitio a Astro en este repo.

- Leé todos los posts publicados, con sus categorías, tags y excerpt.
- Convertí cada post a Markdown en una content collection, con front
  matter: title, date, slug, categories, tags, description.
- Mantené exactamente las mismas URLs que en WordPress.
- Bajá todas las imágenes referenciadas (incluida la featured image)
  a public/images/ y reescribí las rutas.
- Convertí los shortcodes y bloques de Gutenberg a HTML limpio.
- Ignorá comentarios, drafts y páginas de sistema.
- No escribas nada en WordPress: sólo lectura.
- Antes de escribir código, armá un plan en PLAN.md y esperá mi OK.

Relisez le plan. Attentivement. Ce qui casse le plus à cette étape, ce sont les URL : si elles changent, vous perdez le SEO accumulé et tous les liens externes. Dans Astro, la route est définie par la structure de src/pages/ — par exemple src/pages/[year]/[month]/[slug].astro si votre WordPress utilise /%year%/%monthnum%/%postname%/. Si la structure correspond, il n’y a pas de redirects à maintenir.

Laissez le build.format d’Astro sur directory (la valeur par défaut) : il génère /mi-post/index.html, ce que résout ensuite la CloudFront Function de l’étape 4. Et créez src/pages/404.astro — le build le transforme en 404.html.

Pour prévisualiser en local :

npm run dev

Et voici un risque qu’on néglige souvent : le contenu que Claude lit est untrusted (non fiable). De vieux posts, du HTML collé depuis d’autres sites, un commentaire de spam qui s’est glissé. Un texte contenant des instructions cachées est un vecteur de prompt injection — et avec un MCP capable d’écrire sur votre site en production, les dégâts possibles ne sont plus seulement locaux. C’est pour ça que les tools d’écriture vont dans deny, pas dans ask, et que les permissions AWS sont minimales.

L’alternative sans MCP est l’export natif (Tools → Export, format WXR) plus une copie de wp-content/uploads. Ça ne touche pas au site en production, mais c’est à vous de télécharger et de ranger les fichiers. Pour un site avec peu de posts, le MCP est nettement plus rapide.

4. Demandez l’infrastructure à Claude

Une fois que le site build en local, demandez à Claude de créer l’infra pour stage et prod. Voici ce qui doit être en place, et ce qu’il vaut la peine de vérifier :

  • Buckets privés — Block Public Access activé sur les quatre flags, pas de static website hosting. CloudFront lit via l’endpoint REST du bucket, pas via l’endpoint website.
  • Origin Access Control — pas le legacy OAI. Le bucket n’accepte s3:GetObject que depuis cette distribution précise.
  • Certificat ACM dans us-east-1 — CloudFront n’accepte que des certificats de cette région, peu importe où se trouvent les buckets. La validation se fait par DNS.
  • Response headers policy — HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy. La managed policy SecurityHeadersPolicy couvre l’essentiel.
  • TLS minimum — TLSv1.2_2021 et redirection de HTTP vers HTTPS.

La bucket policy qui doit être en place en prod :

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontOAC",
      "Effect": "Allow",
      "Principal": { "Service": "cloudfront.amazonaws.com" },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::mi-blog-prod/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::ACCOUNT_ID:distribution/DISTRIBUTION_ID"
        }
      }
    }
  ]
}

5. Connectez GitHub à AWS via OIDC (c’est à vous de le faire)

C’est l’étape que je ne délègue pas à Claude, parce qu’elle exige des permissions IAM. D’abord, l’identity provider (une fois par compte) :

aws iam create-open-id-connect-provider \
  --url https://token.actions.githubusercontent.com \
  --client-id-list sts.amazonaws.com

Ensuite, un role par environnement. La trust policy du role de prod :

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
          "token.actions.githubusercontent.com:sub": "repo:TU_USUARIO/TU_REPO:environment:production"
        }
      }
    }
  ]
}

La condition sur sub est celle qui garantit « seul ce repo peut publier ». Et elle va plus loin : seul un job qui tourne dans l’environnement production de ce repo peut assumer le role. Utilisez StringEquals, pas StringLike avec des wildcards — un repo:TU_USUARIO/* ouvre l’accès à tous vos repos.

Le role de stage est identique, avec environment:staging et pointé sur le bucket de stage.

Les permissions du role de prod — seulement ce dont le deploy a besoin :

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::mi-blog-prod"
    },
    {
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:DeleteObject", "s3:GetObject"],
      "Resource": "arn:aws:s3:::mi-blog-prod/*"
    },
    {
      "Effect": "Allow",
      "Action": "cloudfront:CreateInvalidation",
      "Resource": "arn:aws:cloudfront::ACCOUNT_ID:distribution/DISTRIBUTION_ID"
    }
  ]
}

Ici, CreateInvalidation admet bien une restriction par ARN. Le pipeline peut publier dans un bucket et invalider une distribution. Rien de plus.

6. Les pipelines de stage et de prod

Dans le repo, Settings → Environments, créez deux environments :

  • staging — deployment branch : main uniquement. Variables : AWS_ROLE_ARN, BUCKET, DISTRIBUTION_ID, SITE_URL.
  • production — deployment branch : main uniquement, required reviewer : vous. Les mêmes variables, avec les valeurs de prod.

Ce sont des variables, pas des secrets. Un role ARN n’est pas un secret — sans le token OIDC de votre repo, il ne sert à rien.

Le workflow, dans .github/workflows/deploy.yml, comporte trois jobs :

  • check — tourne à chaque PR. Installe les dépendances avec npm ci et build. Il ne demande pas de credentials AWS.
  • deploy-stage — tourne à chaque push sur main, dans l’environnement staging. Il build, assume le role de stage via OIDC, envoie le site avec aws s3 sync --delete et invalide le cache CloudFront.
  • deploy-prod — dépend de stage et tourne dans l’environnement production. Les mêmes étapes, avec le role et les variables de prod.

Deux détails qui font la différence : la permission id-token: write n’est déclarée que sur les jobs de deploy, pas au niveau du workflow, et les trois jobs lisent tout depuis les variables de l’environnement — le même code publie en stage ou en prod selon l’endroit où il tourne.

Le flux devient : une PR ne fait que builder — sans credentials AWS, donc une PR malveillante ne peut rien publier. Un merge sur main publie automatiquement en stage. La prod attend votre approbation dans l’onglet Actions. Vous approuvez, et c’est publié.

À propos de l’invalidation : /* compte comme un seul path, et les 1 000 premiers paths par mois sont gratuits. Pour un blog, tout invalider à chaque deploy est l’option la plus simple et ne coûte rien.

Builder deux fois (une par environnement) est voulu : l’URL du site change, et Astro l’intègre dans le HTML — canonical, sitemap, RSS. Dans astro.config.mjs :

export default defineConfig({
  site: process.env.SITE_URL,
  build: { format: 'directory' },
});

Dans le pipeline, SITE_URL provient de la variable de l’environnement. Le coût : quelques secondes de build en plus.

7. Protégez le pipeline

Le pipeline est désormais le seul chemin vers la prod. C’est donc l’actif à protéger :

  • Branch protection sur main — PR obligatoire, status check check au vert, pas de force push.
  • CODEOWNERS sur .github/ — toute modification des workflows exige votre review.
  • Required reviewer sur production — déjà configuré à l’étape précédente. C’est le dernier contrôle humain avant la publication.

8. Cutover

Une fois la prod publiée et vérifiée sur l’URL CloudFront (dxxxx.cloudfront.net) :

  1. Baissez le TTL de l’enregistrement DNS à 300 secondes, la veille.
  2. Pointez le domaine vers la distribution : un alias record dans Route 53, ou un CNAME chez votre fournisseur (pour l’apex, utilisez ALIAS/ANAME s’il le prend en charge).
  3. Vérifiez que les anciennes URL répondent. Un simple script qui parcourt le sitemap de WordPress et compare les codes HTTP suffit.
  4. Gardez le WordPress éteint — pas supprimé — pendant deux ou trois semaines, au cas où quelque chose aurait échappé.

9. Fermez tout ce que vous avez ouvert pour la migration

D’abord, la key AWS :

aws iam update-access-key \
  --user-name claude-migration \
  --access-key-id AKIAXXXXXXXXXXXXXXXX \
  --status Inactive

Et retirez-la de ~/.aws/credentials. Une key inactive sur le disque reste une key qui attend que quelqu’un la réactive.

Ma recommandation, en fait, va un cran plus loin : la supprimer. Quand vous en aurez besoin, vous en créerez une nouvelle — ça prend dix secondes et ça garantit qu’aucune vieille key ne reste oubliée. Révoquez aussi le token GitHub, ou laissez-le expirer.

Ensuite, le MCP. Si le WordPress reste en ligne pendant la période de transition, révoquez l’autorisation dans Connected Apps, désactivez le MCP dans AI Engine et supprimez l’utilisateur claude-migracion. Si le plugin n’était là que pour la migration, désinstallez-le. Et côté Claude Code :

claude mcp remove wp-origen

Un MCP avec des permissions d’écriture sur un site que vous n’utilisez plus, c’est exactement le genre de chose qu’on oublie et qui reste ouverte.


Claude est votre éditeur « visuel »

Voici ce qui convainc les gens qui ne veulent entendre parler ni de Markdown ni de Git. Le panneau WordPress disparaît, et l’éditeur devient une conversation. Vous ouvrez Claude Code dans le repo et vous lui dites :

Creá un post nuevo. Este es mi draft:

[tu texto, bla bla]

Categoría Tecnología.
Levantá npm run dev para que lo revise.
Cuando te dé el OK, creá una branch, commiteá y abrí un PR.

Vous relisez sur localhost:4321, vous demandez des ajustements — « raccourcis l’intro », « déplace l’image sous le premier paragraphe », « change le titre » — et vous approuvez. Claude ouvre la PR, le check passe, vous mergez, stage se met à jour tout seul, vous approuvez la prod. Terminé.

Pour ça, pas besoin de credentials AWS. Claude n’a besoin que du token GitHub — c’est le pipeline qui fait le deploy. C’est la configuration que je garde en permanence.

Un skill avec les règles de votre blog

Le prompt ci-dessus fonctionne, mais il laisse beaucoup à ce que Claude déduit des posts existants. Pour fixer les choses, il y a le skill : un fichier avec les règles de ce que doit être un post, que Claude charge chaque fois que vous en demandez un. C’est ce que fait un éditeur — connaître la charte éditoriale sans que vous ayez à la répéter.

Il vit dans le repo, dans .claude/skills/nuevo-post/SKILL.md :

---
name: nuevo-post
description: Crea o edita posts del blog. Usar siempre que se pida
  escribir, corregir o publicar un post.
---

# Nuevo post

## Front matter (obligatorio)
- title, date (ISO), slug en kebab-case, description (máx. 160 caracteres)
- categories: sólo Tecnología, Seguridad o AI
- tags: entre 2 y 5

## Estilo
- Español, primera persona, frases cortas.
- Términos técnicos en inglés, sin traducir.
- Apertura con el problema, no con una definición.
- Un H2 por sección. Nada de H1 en el cuerpo.

## Imágenes
- En public/images/[slug]/, formato webp, máx. 1600px de ancho.
- alt obligatorio y descriptivo.
- La principal va debajo de la intro.

## Flujo
1. Crear el archivo en src/content/blog/[slug].md como draft: true.
2. Levantar npm run dev y pasar la URL local.
3. Aplicar los cambios que pida.
4. Con el OK: draft: false, branch post/[slug], commit y PR.
   Nunca pushear directo a main.

Avec le skill, le prompt se résume à « crée un post à partir de ce draft ». Le reste — format, images, catégories, le flux jusqu’à la PR — est déjà défini.


La validation de sécurité, en résumé

Voici les risques que j’ai passés en revue, et où en est chacun :

  • Access key de longue durée — atténué. Elle n’existe que pendant la migration, dans un profile séparé, et elle est supprimée ensuite. Risque résiduel : la fenêtre de la migration, moins d’une heure.
  • Escalade de privilèges via IAM — éliminé. Le user de Claude n’a aucune action IAM ; les roles du pipeline, c’est vous qui les créez.
  • Policy CloudFront avec Resource: * — accepté. C’est une limitation de l’API. Pendant la migration, Claude pourrait modifier d’autres distributions du compte. Si vous avez d’autres distributions en production, faites la migration dans un compte séparé d’AWS Organizations.
  • Credentials dans GitHub — éliminé. OIDC avec des credentials temporaires, pas de secrets.
  • Un autre repo qui publie dans votre bucket — éliminé. La trust policy exige le repo et l’environnement exacts.
  • Agent qui modifie le pipeline — atténué. Le token n’a pas la permission Workflows, et CODEOWNERS exige votre review.
  • Prompt injection via le contenu importé — atténué, pas éliminé. Le blast radius est borné par les permissions, et les commandes sensibles exigent votre approbation.
  • Bucket exposé — éliminé. Block Public Access, pas de website hosting, OAC lié à une distribution.
  • s3 sync --delete qui supprime trop — accepté. Si un build sort vide, il efface le site. Activez le versioning sur le bucket de prod : vous récupérez n’importe quelle version précédente pour quelques centimes.
  • MCP avec écriture sur le WordPress en production — atténué. OAuth avec un utilisateur dédié à rôle bas, seul le groupe WordPress exposé, tools d’écriture en deny, et autorisation révoquée et MCP désactivé à la fin. Risque résiduel : l’endpoint reste exposé sur internet tant qu’il est actif, et les permissions réelles sont celles du rôle de l’utilisateur.
  • Supply chain — atténué. Actions pinnées par SHA, npm ci avec lockfile commité.

Moins d’une heure ? Ça dépend

Pour un blog de quelques dizaines de posts, oui. Ces 30 à 50 minutes, c’est mon temps de relecture et d’approbation ; une bonne partie n’est pas du travail de Claude, mais de l’attente : la validation du certificat ACM, le déploiement initial de CloudFront (plusieurs minutes) et la propagation DNS. Si le site compte des centaines de posts avec des shortcodes custom de plugins, prévoyez plus de temps de relecture — la conversion nécessitera plus d’une itération.

C’est une façon de faire. Les composants sont interchangeables — Astro contre Hugo, AI Engine contre l’export natif, GitHub Actions contre un autre CI, Route 53 contre votre DNS actuel. Ce que je ne changerais pas, c’est le modèle de permissions : l’agent avec le minimum et pour une durée limitée, le pipeline sans keys, et un humain qui approuve ce qui arrive en prod.

Maximiliano Díaz Doglia

AI Platform Engineer & Full-Stack Developer
Building Enterprise Integrations & Automations