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 de la même requête. Cross-Origin-Resource-Policy (CORP) est ce qu'une ressource déclare sur elle-même. Cross-Origin-Embedder-Policy (COEP) est ce que la page qui embarque déclare sur 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 les charge est l'embarqueur, c'est donc à elle 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, quand Spectre a transformé la présence de données cross-origin dans le mauvais processus en 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 du refus 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 sortir de ce défaut. 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 et que le serveur répond quand même ; le navigateur jette simplement 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, et c'est précisément cette menace de canal auxiliaire façon Spectre qui a motivé sa relance. Il bloque l'inclusion de script cross-site (XSSI). Et dans les navigateurs qui l'appliquent, c'est-à-dire 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

C'est là que ça coince. 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. C'est ce comportement par défaut qui bloque les assets tiers non étiquetés dès que vous activez COEP. Chromium en fait même 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 l'iframe est bloquée. CORP sur la réponse de l'iframe ne suffit pas ; le document embarqué doit déclarer sa propre politique d'embedder.

Comment ils se combinent pour l'isolation cross-origin

On se soucie des deux à la fois à cause de 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.

C'est aussi cette porte qui a poussé la plupart des sites qui envoient CORP à s'y mettre. 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

Une erreur courante consiste à ajouter Access-Control-Allow-Origin à une ressource en espérant satisfaire require-corp. Cela ne marche pas. 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 qu'une réponse ne peut pas contenir de données personnalisées qui mériteraient d'être protégées. 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 d'essai. 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 que vos utilisateurs ne le rencontrent.

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 envisageable 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 déclarent 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 travaillent dans des sens opposés. 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