Introduction
Un bucket privé peut tout de même divulguer des fichiers si un Worker public les renvoie à chaque appelant. Vous allez reproduire ce défaut avec seulement deux exports synthétiques, ajouter une autorisation applicative limitée au lecteur et vérifier que les téléchargements privés restent protégés tout en conservant l’accès public à l’état de santé.
Terminez d’abord les leçons sur l’intégration des documents Worker et les accès temporaires. Cette VM neuve fournit un gestionnaire volontairement non sécurisé, des fichiers synthétiques, Node.js 22.22.0, Wrangler 4.131.1 et Miniflare 4.20260730.0. Vous allez créer un nouveau bucket privé et un Worker temporaire. R2 doit être actif et votre compte d’apprentissage doit disposer des autorisations correspondantes ; consultez la tarification. Aucun fichier réel, aucune donnée client réelle ni aucun domaine personnalisé ne sont nécessaires. Nettoyez la démonstration exposée et ses identifiants avant de quitter l’environnement.
Connecter le bucket de l’application
Dans cette étape, vous autorisez cette VM et créez un bucket privé indépendant pour l’application. L’autorisation de l’appareil confirme votre compte d’apprentissage. La gestion des buckets R2 utilise un jeton API distinct, limité à ce compte.
Lancez Bash pour utiliser la syntaxe des commandes ci-dessous, puis placez-vous dans le projet préparé et vérifiez ses outils. Gardez ce même terminal ouvert afin que les variables contenant les noms de ressources restent disponibles :
bash
cd /home/labex/project/r2-lab
export PATH="$PWD/.tools/node-v22.22.0-linux-x64/bin:$PATH"
node --version
npx wrangler --version
Autorisez le code d’appareil affiché dans votre propre navigateur. Vérifiez le compte d’apprentissage ainsi que les autorisations de lecture du compte et de l’utilisateur demandées avant d’accorder votre consentement :
npx wrangler login --device --browser=false --scopes account:read user:read workers_scripts:write workers_kv:write
npx wrangler whoami --json
La valeur loggedIn doit être true. Lisez le nom du compte même si un seul compte est affiché. Remplacez YOUR_ACCOUNT_ID ci-dessous par l’ID réel de 32 caractères de ce compte. openssl rand -hex 6 génère douze caractères hexadécimaux aléatoires afin d’éviter toute collision avec une exécution précédente. Le document here-document écrit un fichier de configuration standard ; le shell y remplace vos variables.
ACCOUNT_ID=YOUR_ACCOUNT_ID
RUN_ID=$(openssl rand -hex 6)
NAME="labex-c05-r08-$RUN_ID"
BUCKET="$NAME-docs"
cat > wrangler.jsonc <<JSON
{"name":"$NAME","account_id":"$ACCOUNT_ID","main":"src/index.js","workers_dev":true,"compatibility_date":"2026-07-30","r2_buckets":[{"binding":"DOCUMENTS","bucket_name":"$BUCKET"}]}
JSON
Pour gérer le bucket, ouvrez la page API Tokens de votre profil Cloudflare et créez un jeton personnalisé portant un nom lié à ce laboratoire. Accordez l’autorisation Account → Workers R2 Storage → Edit, puis limitez Account Resources au compte d’apprentissage dont vous avez enregistré l’ID. Définissez une courte durée d’expiration. N’incluez aucun autre compte ni aucune autorisation sans rapport avec ce laboratoire. Ce jeton de gestion sert à administrer les buckets, notamment à les créer et à les supprimer. Dans ce lab, le Worker accède aux objets R2 via son binding DOCUMENTS.
Copiez une seule fois le jeton dans cette invite masquée de la VM. umask 077 limite les droits du fichier à votre utilisateur ; read -s masque la saisie. Le fichier utilise la variable de jeton standard de Wrangler et est exclu de Git.
umask 077
read -r -s -p 'R2 management API token: ' R2_MANAGEMENT_TOKEN; printf '\n'
printf 'CLOUDFLARE_API_TOKEN=%s\n' "$R2_MANAGEMENT_TOKEN" > .env.management
unset R2_MANAGEMENT_TOKEN
Utilisez --env-file=.env.management uniquement pour les commandes de gestion R2 ; whoami continue à vérifier l’autorisation de l’appareil de la VM.
Placez --env-file à la fin de chaque commande Wrangler pour que sa liste de fichiers ne soit pas étendue au nom de la commande. Après la création de chaque compartiment, si Wrangler propose d’ajouter une liaison à la configuration, saisissez n et appuyez sur Entrée. La configuration contient déjà la liaison prévue.
npx wrangler r2 bucket create "$BUCKET" --env-file=.env.management
Listez vos buckets et recherchez le nom généré exact. Les autres buckets appartiennent à d’autres travaux ; ne les modifiez pas.
npx wrangler r2 bucket list --env-file=.env.management
Dans le Dashboard, ouvrez Storage & databases → R2 → Overview, sélectionnez ce bucket précis et vérifiez que sa liste d’objets est vide. Dans ses paramètres, laissez désactivés l’URL de développement publique et les domaines personnalisés. Le nom du bucket dans le Dashboard confirme son identité ; les vérifications de téléchargement ultérieures confirmeront les octets stockés.
L’autorisation du script Worker permet le déploiement. L’autorisation KV permet à Wrangler d’assurer le suivi des suppressions ; ce laboratoire ne crée aucun espace de noms KV. Le jeton de gestion R2 reste un identifiant distinct limité au compte.
Créez deux nouveaux identifiants applicatifs pour les lecteurs synthétiques. Ce ne sont pas des jetons API Cloudflare. Initialisez les deux objets appartenant aux lecteurs et publiez le gestionnaire fourni, volontairement exposé :
umask 077
printf "BLUE_TOKEN=%s\nGREEN_TOKEN=%s\n" "$(openssl rand -hex 24)" "$(openssl rand -hex 24)" > .dev.vars
npx wrangler r2 object put "$BUCKET/exports/blue/report.txt" --remote --file blue.txt --content-type text/plain --env-file=.env.management
npx wrangler r2 object put "$BUCKET/exports/green/report.txt" --remote --file green.txt --content-type text/plain --env-file=.env.management
npx wrangler deploy
npx wrangler secret bulk .dev.vars
Ce déploiement volontairement exposé contient uniquement ces deux fichiers synthétiques. N’utilisez pas d’exports réels et ne le laissez pas actif après l’exercice.
Observer le téléchargement public involontaire
Dans cette étape, vous reproduisez l’exposition par l’intermédiaire du Worker alors que le bucket sous-jacent reste privé. Un binding permet au code côté serveur d’accéder au bucket ; R2 ne détermine pas automatiquement quels appelants HTTP ce code doit autoriser.
Copiez l’URL exacte du déploiement et demandez l’export bleu sans identifiants :
BASE_URL=https://YOUR_WORKER.YOUR_SUBDOMAIN.workers.dev
curl -i "$BASE_URL/exports/blue/report.txt"
Le résultat doit être HTTP 200 et contenir Synthetic blue export.. Si le nouveau déploiement est encore en cours de propagation, réessayez la lecture pendant une minute au maximum. Cette réponse anonyme réussie constitue le défaut à corriger.
Examinez le gestionnaire fourni :
cat src/index.js
Il lit directement une clé R2 depuis l’URL et renvoie son contenu sans vérifier que l’appelant possède ce fichier. Dans le Dashboard, examinez les paramètres du bucket : l’URL de développement publique doit être désactivée et aucun domaine personnalisé ne doit être configuré. Ces paramètres ne ferment pas la route distincte du Worker. Exécutez la vérification de l’exposition avant de remplacer le gestionnaire.
Ces paramètres réels montrent l’absence de domaine personnalisé et la désactivation de l’URL publique de développement. Un Worker lié à R2 peut encore exposer les données par sa propre route.

Cette requête réelle dans le terminal de la VM renvoie HTTP 200 et le rapport bleu synthétique sans identifiants. Le nom d’utilisateur, le préfixe et l’URL sont des exemples ; utilisez votre propre URL de déploiement.

Authentifier le lecteur avant de sélectionner la clé
Dans cette étape, vous reliez l’authentification (le lecteur possède-t-il un identifiant valide ?) à l’autorisation (ce lecteur peut-il télécharger cet objet ?). Un jeton bleu valide ne doit pas permettre de récupérer un export vert. L’identité authentifiée fournit le propriétaire de la clé, et le propriétaire indiqué dans l’URL doit lui correspondre avant toute requête vers R2.
Remplacez le gestionnaire. Les deux identifiants synthétiques représentent les lecteurs de ce petit exemple ; dans une véritable application, vous utiliseriez sa session ou son fournisseur d’identité ainsi que des enregistrements d’autorisations faisant foi. N’acceptez jamais un nom fourni par l’appelant comme preuve d’identité.
cat > src/index.js <<'JS'
async function matches(actual, secret) {
if (!secret) return false;
const expected = `Bearer ${secret}`;
const a = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(actual));
const b = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(expected));
return crypto.subtle.timingSafeEqual(a, b);
}
export default {
async fetch(request, env) {
const path = new URL(request.url).pathname;
if (path === "/health" && request.method === "GET") return new Response("ok");
const actual = request.headers.get("Authorization") || "";
const owner = await matches(actual, env.BLUE_TOKEN) ? "blue" : await matches(actual, env.GREEN_TOKEN) ? "green" : null;
if (!owner) return new Response("Unauthorized", { status: 401 });
if (request.method !== "GET") return new Response("Method not allowed", { status: 405 });
const route = /^\/exports\/(blue|green)\/([a-z0-9-]+\.txt)$/.exec(path);
if (!route) return new Response("Not found", { status: 404 });
if (route[1] !== owner) return new Response("Forbidden", { status: 403 });
// The authenticated owner and validated path determine the storage key.
const key = `exports/${owner}/${route[2]}`;
const object = await env.DOCUMENTS.get(key);
if (!object) return new Response("Not found", { status: 404 });
const headers = new Headers({ "Cache-Control": "private, no-store" });
object.writeHttpMetadata(headers);
headers.set("ETag", object.httpEtag);
return new Response(object.body, { headers });
}
};
JS
Un secret absent ne peut pas correspondre accidentellement à un appelant. La comparaison des condensés utilise l’opération d’égalité résistante aux attaques temporelles du runtime. La route d’état de santé reste en dehors de la route protégée, et les réponses privées ne peuvent pas être mises en cache par un cache partagé. Un paramètre de requête key ne peut pas remplacer le chemin de stockage déterminé par l’authentification.
Déployez la correction :
npx wrangler deploy
La vérification de la plateforme commence par exécuter le code dans un runtime local distinct, avec des identifiants et des octets d’objets synthétiques aléatoires. Une vérification locale réussie est utile avant l’évaluation de la correction déployée.
Prouver l’isolation des lecteurs et conserver l’état de santé
Dans cette étape, vous testez les chemins autorisés et refusés. Chargez les identifiants synthétiques sans les afficher, puis téléchargez le fichier de chaque propriétaire :
set -a
source .dev.vars
set +a
curl -fsS -H "Authorization: Bearer $BLUE_TOKEN" "$BASE_URL/exports/blue/report.txt" -o blue-download.txt
cmp blue.txt blue-download.txt
curl -fsS -H "Authorization: Bearer $GREEN_TOKEN" "$BASE_URL/exports/green/report.txt" -o green-download.txt
cmp green.txt green-download.txt
Les octets doivent être exactement identiques. Testez maintenant l’accès anonyme, le lecteur qui tente d’accéder au chemin de l’autre propriétaire, une clé inexistante appartenant au lecteur et l’état de santé public :
curl -i "$BASE_URL/exports/blue/report.txt"
curl -i -H "Authorization: Bearer $BLUE_TOKEN" "$BASE_URL/exports/green/report.txt"
curl -i -H "Authorization: Bearer $BLUE_TOKEN" "$BASE_URL/exports/blue/missing.txt"
curl -i "$BASE_URL/health"
Les résultats attendus sont respectivement 401 Unauthorized, 403 Forbidden, 404 Not found et 200 ok. Le code d’état et le corps de la réponse doivent correspondre. La vérification de la plateforme répète le test d’isolation des lecteurs à distance et vérifie que le compte sélectionné possède le Worker ainsi que son binding R2 privé.
La même URL d’exemple renvoie maintenant HTTP 401 Unauthorized à une requête anonyme dans le terminal. Il s’agit de résultats HTTP du terminal ; les comparaisons d’octets authentifiées et les contrôles indépendants prouvent l’isolation.

Supprimer l’application distante et le bucket
Dans cette étape, vous supprimez uniquement le Worker et les objets de ce laboratoire, alors que vous disposez encore des autorisations nécessaires. Le bucket privé ne disparaît pas lorsque son Worker est supprimé.
npx wrangler delete
Confirmez le nom exact du Worker généré. Supprimez explicitement l’objet importé, puis supprimez le bucket :
BUCKET=$(node -p "JSON.parse(require('fs').readFileSync('wrangler.jsonc')).r2_buckets[0].bucket_name")
npx wrangler r2 object delete "$BUCKET/exports/blue/report.txt" --remote --env-file=.env.management
npx wrangler r2 object delete "$BUCKET/exports/green/report.txt" --remote --env-file=.env.management
npx wrangler r2 bucket delete "$BUCKET" --env-file=.env.management
Seules les clés de rapport bleue et verte ont été créées. Supprimez ces clés exactes et conservez les ressources sans rapport avec ce laboratoire du compte.
Actualisez les listes des Workers et des buckets dans le Dashboard, puis exécutez la vérification de nettoyage de la plateforme. Les échecs d’authentification ou de réseau ne permettent pas de conclure à une suppression réussie.
Révoquer les identifiants restants
Dans cette étape, vous révoquez le jeton de gestion de ce laboratoire sur la page API Tokens de votre profil, supprimez le secret applicatif local et fermez l’autorisation de la VM. Faites-le uniquement après la réussite de la vérification de nettoyage précédente.
rm .env.management .dev.vars
unset BLUE_TOKEN GREEN_TOKEN
npx wrangler logout
npx wrangler whoami --json || true
La valeur loggedIn doit être false. La révocation du jeton de gestion est une étape manuelle distincte dans le Dashboard ; supprimer uniquement le fichier local ne le révoque pas. Ne modifiez pas la connexion Dashboard habituelle ni les jetons des autres laboratoires.
Résumé
Fermer l’exposition d’un fichier par un Worker synthétique, associer l’identité du lecteur à la propriété de l’objet, conserver l’accès à l’état de santé et vérifier le nettoyage du bucket privé.



