ADR-0044 — Mirror reconstruit une section, il ne copie pas une page
Etend le tableau des surfaces du stage de l'ADR 0032 : mirror est un sixieme mode de la scene, a cote de Preview, Worktree, Builder, Workflow et Studio. Il n'est ni un panneau du Builder, ni une couche de la vitrine.
Statut
Accepté · 2026-09-26
Piliers : ai-platform
Etend le tableau des surfaces du stage de l'ADR 0032 :
mirrorest un sixieme mode de la scene, a cote de Preview, Worktree, Builder, Workflow et Studio. Il n'est ni un panneau du Builder, ni une couche de la vitrine.
Contexte
Un marchand voit sur un autre site une section qui lui plait (un hero, une grille de cartes, un bandeau de reassurance) et veut « la meme » sur sa boutique. Deux risques, de nature differente, rendent la reponse evidente dangereuse :
- Securite. Afficher la page d'un tiers dans le tableau de bord, c'est
executer du balisage choisi par un inconnu sur l'origine qui porte la
session. Le proxy de la Preview (
/api/preview/[storeId]) tourne volontairement sans sandbox, parce que la vitrine du marchand a besoin de ses cookies ; il ne peut pas servir de modele pour une page tierce. Unsrcdocn'est pas une issue non plus : il herite de la CSP du tableau de bord, donc les scripts de capture n'ont pas le nonce, les images et polices tierces sont bloquees en production et tout fonctionne en developpement (CSP en report-only), ce qui est le pire des deux mondes. - Droit. Copier le HTML, le CSS, les images, les polices ou le texte d'un site tiers, c'est reproduire une oeuvre protegee (droit d'auteur sur le texte et les visuels, licence des polices, parfois la marque). L'outil deviendrait un outil de contrefacon, et c'est BoostEcom qui l'aurait ecrit, heberge et facture.
Décision
Mirror mesure une section et la reconstruit en section Shopify OS 2.0 a la marque de la boutique ; il n'exporte jamais un fichier du site source.
- Isolation par l'en-tete. Le snapshot est servi par
GET /api/stores/[storeId]/mirror/frameavec une CSP d'EN-TETE (jamais<meta>, jamais report-only) qui commence parsandbox allow-scripts: origine opaque, donc aucun acces aux cookies, au stockage ni au DOM du tableau de bord, etOrigin: nullrefuse parcheckStateChangingOrigin.script-srcne porte que le nonce de la reponse : le seul script qui tourne estcapture-script.ts.frame-ancestors 'self',connect-src 'none',form-action 'none',base-uri 'none'. L'iframe cote client porte en plussandbox="allow-scripts", jamaisallow-same-origin. - Lecture sous garde. L'URL passe
validateScanUrlau saut 0 puissafeFetch(SSRF revalide a chaque redirection),text/htmlseulement, corps lu en flux sousMIRROR_MAX_HTML_BYTES. Le HTML est assaini avant d'etre servi : scripts, gestionnaireson*, frames, objets, formulaires etjavascript:retires, URLs absolutisees, liens rendus inertes. - Protocole par identite, pas par origine. Le cadre poste vers l'origine
explicite de l'application ; le parent n'accepte un message que si
event.source === iframe.contentWindowET que le jeton de chargement correspond. Le parent commande le cadre avec la cible*et n'y met jamais de secret. La capture est validee par zod (capture-schema.ts) avant tout calcul : elle a ete fabriquee dans une page que nous ne controlons pas. - Rien du tiers ne sort. Le compilateur n'emet ni image, ni SVG, ni
police, ni feuille de style, ni script du site source : les images
deviennent des reglages
image_pickervides, les icones des glyphes integres, les fondsurl()un degrade de marque. Le texte devient un placeholder de meme longueur, sauf si le marchand coche « je detiens les droits sur ce texte » (copy: "keep"exigerightsAttested: true, refuse en 422 sinon). Les couleurs sont reexprimees comme melanges des trois roles de marque de la boutique (fond, texte, accent), exposes en reglagescolormodifiables ; les polices restent celles du theme. - Themes non publies seulement. L'installation passe
decideThemeWritesansallowLiveTheme: un theme live repond 409, sans echappatoire. Un fichier existant n'est jamais ecrase (le handle porte un hash). Le theme cible est choisi parmi les brouillons ; aucun theme n'est cree en silence. - Plan payant. Les deux routes lisent
resolveEntitlement; le mode est verrouille pour Free parPaidCanvasOverlaycomme les autres etapes.
Alternatives écartées
| Option | Pourquoi non |
|---|---|
| Afficher la page par le proxy de la Preview | Il tourne sans sandbox et avec les cookies de session : du balisage tiers y aurait l'origine du tableau de bord (le trou decrit en tete de api/preview/[storeId]/route.ts). |
<iframe srcdoc sandbox="allow-scripts"> | Herite de la CSP du parent : pas de nonce pour notre script, images et polices tierces bloquees en production, violations envoyees en masse a /api/security/csp-report, et un comportement different en developpement. |
Elargir buildCsp (img-src https:) | Casse la garde csp-creatives.test.ts et elargit la politique de TOUT le tableau de bord pour une seule surface. |
| Exporter le HTML/CSS assaini tel quel | C'est une copie : droits d'auteur, licences de polices, images hotlinkees. Et un fichier Liquid illisible pour le marchand. |
| Un panneau dans le Builder (Onlook) | Code vendore // @ts-nocheck epingle par des tests ; ses cadres rendent des documents de theme, pas une page tierce opaque. |
| Ecrire sur le theme live « avec confirmation » | La regle du depot est qu'une ecriture live exige un drapeau explicite ; Mirror n'a aucune raison de le porter. Le marchand publie son brouillon lui-meme. |
Conséquences
- Fidelite partielle, assumee : ce qui sort est une structure reconstruite
(grille, espacements, hierarchie, roles de couleur), pas un pixel perfect.
Une police tierce, une animation ou une video n'y sont pas, et le rapport
le dit (
warnings). - Un site qui refuse les requetes sans cookie, ou qui ne rend rien sans
JavaScript, donne un snapshot vide :
mirror:readyporteemptyBodyet l'ecran le dit. Rendre la page dans Browserbase serait la suite, avec son cout par session. - Les polices tierces d'un CDN qui ne repond pas
Access-Control-Allow-Origin: *retombent sur une police systeme dans le snapshot (requeteOrigin: null). C'est l'affichage de la source qui en patit, pas la section produite. - Signal pour revisiter : un marchand qui doit systematiquement « garder le texte » pour obtenir une section utilisable, ou des sections installees que personne n'ajoute a un template.