Tous les articles

CORP vs COEP, deux faces de la même isolation cross-origin

CentralCSP Team ·

Dernière mise à jour:

Ces deux headers se ressemblent et on les confond sans arrêt, mais ils se tiennent aux deux extrémités opposées de la même requête. Cross-Origin-Resource-Policy (CORP) est quelque chose qu'une ressource envoie à propos d'elle-même. Cross-Origin-Embedder-Policy (COEP) est quelque chose que la page qui embarque envoie à propos de ce qu'elle acceptera. Une fois que vous savez de quel côté vous êtes, la confusion se dissipe.

En bref : CORP est un header de réponse qu'une ressource pose pour déclarer qui peut la charger. COEP est un header que la page qui embarque pose pour déclarer qu'elle ne charge que des ressources qui ont accepté. Ce sont deux moitiés du même mécanisme, pas des concurrents, et ils sont conçus pour être utilisés ensemble.

CORP vs COEP, la différence en une ligne

CORP est posé par la ressource et répond à « qui peut me charger ». COEP est posé par la page et dit « je ne charge que des ressources qui ont accepté ».

Tout le reste en découle. Un CDN, un hébergeur d'images, un serveur de polices, chacun d'eux est une ressource, donc c'est à lui d'envoyer CORP. La page qui tire ces ressources est l'embarqueur, donc c'est à l'embarqueur d'envoyer COEP. La ressource déclare sa propre accessibilité ; la page déclare sa propre règle d'admission.

Cross-Origin-Resource-Policy, posé par la ressource

CORP est un header de réponse que le navigateur lit quand une autre origine tente de charger votre ressource via une requête no-cors (un simple <img>, <script>, <audio>, ou une feuille de style sans crossorigin). Il a d'abord été proposé en 2012 sous la forme d'un header nommé From-Origin, puis relancé en 2018 après que Spectre a fait des données cross-origin dans le mauvais processus une fuite bien réelle. Il a exactement trois valeurs.

Cross-Origin-Resource-Policy: same-origin
  • same-origin : seule l'origine exactement identique (schéma, hôte et port) obtient la ressource. Le choix deny-par-défaut.
  • same-site : le même domaine enregistrable, donc les sous-domaines sont autorisés. C'est ce dont a besoin un cdn.example.com qui sert www.example.com ; same-origin le casserait. Une réserve : same-site sur une ressource HTTPS n'admet pas une page en HTTP simple du même site, donc une configuration à schémas mixtes échoue quand même au contrôle.
  • cross-origin : n'importe quelle origine peut la charger. Cela existe surtout pour que des assets réellement publics restent chargeables, y compris par des pages isolées par COEP.

Quand le header est absent, le navigateur se comporte comme si cross-origin était posé, donc n'importe quel site peut embarquer votre asset. Poser une valeur, c'est se désengager de cela. Si le navigateur voit une valeur plus stricte que ce que la requête autorise, le chargement échoue en erreur réseau. Notez que la requête part quand même et que le serveur répond quand même ; le navigateur jette le corps de la réponse au lieu de le remettre à la page. Dans Chromium, l'échec apparaît dans les DevTools comme net::ERR_BLOCKED_BY_RESPONSE.

CORP protège contre plusieurs choses distinctes. Il garde votre ressource hors du processus d'une page attaquante, ce qui est la motivation de canal auxiliaire de type Spectre pour laquelle il a été relancé. Il bloque l'inclusion de script cross-site (XSSI). Et dans les navigateurs qui l'appliquent, soit la grande majorité, il empêche vos assets d'être affichés sur d'autres sites. C'est un contrôle d'embarquement, pas une protection de bande passante : les octets transitent quand même avant que le navigateur ne les écarte, et les clients non-navigateurs ignorent complètement le header.

Cross-Origin-Embedder-Policy, posé par la page

COEP est l'autre moitié. Une page envoie require-corp pour dire qu'elle ne chargera une sous-ressource cross-origin que si cette ressource a explicitement accepté.

Cross-Origin-Embedder-Policy: require-corp

Voici la partie qui piège les gens. Sous require-corp, un header CORP manquant ou malformé est traité comme same-origin. Un asset cross-origin qui n'envoie aucun CORP retombe par défaut sur same-origin du point de vue de l'embarqueur, donc la page ne peut pas le charger. Ce défaut est exactement ce qui bloque les assets tiers non étiquetés une fois que vous activez COEP. Chromium le suit même comme une raison de blocage à part entière, distincte d'un simple décalage CORP : la console affiche ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep. Une ressource accepte soit en envoyant Cross-Origin-Resource-Policy: cross-origin, soit en étant récupérée via une vraie requête CORS (l'attribut crossorigin sur la balise).

COEP s'applique aussi aux iframes, au-delà des simples sous-ressources. Sous require-corp, un document cross-origin embarqué doit envoyer son propre header COEP compatible, sinon le cadre est bloqué. CORP sur la réponse du cadre ne suffit pas ; le document encadré doit énoncer sa propre politique d'embedder.

Comment ils se combinent pour l'isolation cross-origin

La raison de se soucier des deux à la fois est l'isolation cross-origin. Quand une page pose Cross-Origin-Opener-Policy: same-origin et Cross-Origin-Embedder-Policy: require-corp ensemble, le navigateur la marque cross-origin isolée. Cet état, lisible en JavaScript via self.crossOriginIsolated, est la porte d'accès à SharedArrayBuffer et aux timers haute résolution non bridés. COOP gère le côté fenêtre (il place la page dans son propre groupe de contextes de navigation et coupe les liens window.opener vers les pages cross-origin), et COEP gère le côté sous-ressources. Les deux sont requis.

Cette porte est aussi la raison pour laquelle la plupart des sites qui envoient CORP ont commencé à le faire. Les navigateurs ont désactivé SharedArrayBuffer début 2018 après Spectre. Firefox l'a réintroduit en 2020, conditionné à l'isolation cross-origin ; Chrome l'avait réactivé plus tôt sur desktop sans cette condition, puis a exigé l'isolation sur toutes les plateformes en 2021. Tout ce qui a besoin de mémoire partagée aujourd'hui (threads wasm, ffmpeg.wasm, éditeurs vidéo dans le navigateur) doit envoyer COEP, et COEP force à son tour chaque asset cross-origin chargé par la page à porter CORP ou à passer par CORS. Vous ne pouvez pas atteindre l'état isolé si vos propres assets et votre CDN restent silencieux. Le guide COOP et COEP de l'isolation cross-origin parcourt la configuration complète.

Le piège Access-Control-Allow-Origin

Un correctif erroné courant consiste à ajouter Access-Control-Allow-Origin à une ressource en s'attendant à satisfaire require-corp. Ce n'est pas le cas. Envoyer Access-Control-Allow-Origin sur une réponse no-cors ne fait rien pour COEP. Pour satisfaire require-corp, l'élément doit émettre une vraie requête CORS (l'attribut crossorigin), ou la ressource doit envoyer un header CORP. Un header de réponse CORS sur une requête qui n'a jamais été faite en mode CORS est ignoré.

credentialless, l'option à moindre friction

Cross-Origin-Embedder-Policy: credentialless est une alternative plus souple. Au lieu d'exiger que chaque ressource cross-origin envoie CORP, elle retire les credentials (pas de cookies, pas de certificats client) des requêtes de sous-ressources no-cors cross-origin, de sorte que les réponses ne peuvent pas être des données personnalisées dignes de protection. Elle atteint tout de même l'isolation cross-origin. Deux limites : elle ne change que les sous-ressources no-cors, donc les requêtes en mode CORS suivent les règles de require-corp et les iframes cross-origin ont toujours besoin de leur propre header COEP. Et la prise en charge est partielle : credentialless fonctionne dans les navigateurs Chromium et Firefox mais pas Safari, donc require-corp reste le choix interopérable.

Testez avec Report-Only avant de forcer

COEP a un mode répétition. Envoyez Cross-Origin-Embedder-Policy-Report-Only: require-corp avec un paramètre report-to pointant vers un endpoint Reporting-Endpoints, et le navigateur émet un report coep pour chaque chargement que le mode strict aurait bloqué, sans rien bloquer. Le corps du report vous dit si l'échec venait d'un décalage corp, d'une navigation ou d'une worker initialization, plus l'URL bloquée. Laissez-le tourner un moment, corrigez les ressources signalées, puis basculez vers le header en mode strict. CentralCSP ingère ces reports aux côtés des violations CSP, donc vous voyez chaque asset qui aurait été bloqué sur du trafic réel avant vos utilisateurs.

CORP vs COEP côte à côte

Cross-Origin-Resource-PolicyCross-Origin-Embedder-Policy
Qui le poseLa ressource (image, script, police, média)La page qui embarque
Ce qu'il déclareQui peut me chargerJe ne charge que des ressources qui ont accepté
Valeurssame-origin, same-site, cross-originrequire-corp, credentialless
Défaut quand absentTraité comme cross-origin, n'importe qui peut embarquer (mais bascule sur same-origin sous le require-corp d'un embarqueur)Aucune politique, isolation cross-origin désactivée
Mode d'échecUn chargement bloqué est une erreur réseau, le corps n'est jamais livréLes sous-ressources et cadres qui n'ont pas accepté sont bloqués

Recommandation

Traitez les deux ensemble. Posez CORP sur vos propres ressources au niveau dont elles ont besoin : same-origin pour les assets privés, same-site là où des sous-domaines les consomment, et cross-origin uniquement sur les assets réellement publics. La cheat sheet OWASP sur les headers HTTP recommande same-site comme référence générale, avec require-corp sur les pages. Quand vous activez require-corp, assurez-vous que chaque asset que cette page charge, vos propres fichiers et ceux de votre CDN, envoie un header CORP que l'embarqueur acceptera, ou est récupéré avec crossorigin. Recourez à credentialless quand changer chaque tierce partie n'est pas praticable et que vous n'avez pas besoin de Safari, et utilisez le header Report-Only pour trouver la casse avant de forcer.

Un scanner est le moyen rapide de voir lesquelles de vos réponses portent réellement CORP et quelles pages affirment COEP. Le scanner de headers de sécurité vérifie les deux sur tout votre site pour que vous repériez les manques avant qu'une page isolée ne se mette à échouer à charger ses assets.

FAQ

Ai-je besoin de CORP si j'ai déjà COEP ?

Oui, ils couvrent des directions opposées. COEP sur votre page ne fait rien pour protéger vos propres ressources contre le chargement par d'autres sites ; seul CORP sur ces réponses le fait. Et dès que votre page envoie COEP: require-corp, chaque asset cross-origin qu'elle charge, y compris votre propre sous-domaine CDN, doit envoyer CORP ou être récupéré avec CORS, parce qu'un header CORP manquant est traité comme same-origin et bloqué.

COEP require-corp bloque-t-il les images ?

Oui. Un simple <img> pointant vers un hôte cross-origin est un chargement no-cors, et sous require-corp il est bloqué (Chromium affiche net::ERR_BLOCKED_BY_RESPONSE) sauf si le serveur d'images envoie Cross-Origin-Resource-Policy: cross-origin (ou un same-site correspondant), ou si la balise utilise crossorigin et que le serveur répond avec des headers CORS valides. Si vous ne contrôlez ni l'un ni l'autre, faites transiter l'image par votre propre origine ou utilisez COEP: credentialless.

Quelle est la différence entre CORP et CORS ?

Ils vont dans des directions opposées. CORS est un opt-in côté serveur qui accorde un accès en lecture cross-origin aux réponses des requêtes faites en mode CORS. CORP est une restriction côté serveur qui retire l'embarquabilité par défaut des chargements no-cors (images, scripts, médias que n'importe quelle page pouvait historiquement inclure). CORP n'affecte que les requêtes no-cors, et les headers CORS sur une réponse no-cors sont ignorés, ce qui explique pourquoi Access-Control-Allow-Origin seul ne satisfait jamais require-corp.

Dois-je utiliser credentialless plutôt que require-corp ?

Une valeur COEP qui atteint l'isolation cross-origin sans exiger CORP de chaque tierce partie. Les requêtes de sous-ressources no-cors cross-origin partent sans credentials (pas de cookies, pas de certificats client), donc les réponses ne peuvent pas porter de données personnalisées. Les requêtes en mode CORS suivent toujours les règles de require-corp, et les iframes cross-origin ont toujours besoin de leur propre header COEP. Elle fonctionne dans les navigateurs Chromium et Firefox mais pas Safari.

Articles liés

Sources