Sécurité et exploitation d'Artisan
Le pont effectue le rendu, dans Chrome, de HTML potentiellement non fiable, protégé par deux barrières réseau indépendantes et une politique de contenu stricte. Le bac à sable du système d’exploitation de Chrome est un contrôle distinct et facultatif, dont les limites sont explicitées. Cette page documente la limite ; elle ne prétend pas que cette limite est absolue.
Vue d’ensemble conceptuelle
Section intitulée « Vue d’ensemble conceptuelle »Une opération de rendu équivaut à l’exécution d’une requête côté serveur : l’application transmet du HTML à un moteur de navigateur qui peut, par défaut, récupérer des ressources. Une récupération sortante pilotée par une entrée non fiable constitue une falsification de requête côté serveur : la CWE-918 la définit comme un serveur qui récupère le contenu d’une URL fournie sans garantir suffisamment que la requête atteint la destination attendue. La SSRF (CWE-918) est une faiblesse du CWE Top 25. L’OWASP ASVS exige que les requêtes sortantes des composants serveur soient contrôlées plutôt qu’implicites. L’OWASP SSRF Prevention Cheat Sheet considère que le refus au niveau réseau des appels vers des destinations arbitraires constitue le contrôle fort. La posture réseau de refus par défaut décrite ci-dessous est la réponse du pont à cette exigence. La norme NIST SP 800-53 SC-7 décrit le même principe de limite « tout refuser, autoriser par exception » que le pont applique à la couche de transport.
Résidence des données et mesures d’atténuation des PII
Section intitulée « Résidence des données et mesures d’atténuation des PII »Le HTML transmis au pont est traité entièrement en mémoire, au sein du processus et dans l’instance Chrome locale. Le pont n’effectue lui-même aucun appel réseau sortant et empêche Chrome d’en émettre (voir le modèle réseau ci-dessous), de sorte que le contenu d’entrée ne quitte pas l’hôte par le moteur de rendu. Les PII présentes dans l’entrée sont rendues dans le PDF que tu produis — applique à la sortie les mêmes contrôles de résidence qu’à l’entrée. Le pont ne conserve ni l’entrée ni la sortie sur disque ; la persistance incombe à l’appelant.
Télémétrie sûre et nettoyage des journaux
Section intitulée « Télémétrie sûre et nettoyage des journaux »ChromeHtmlRenderer et BrowserPool acceptent un LoggerInterface PSR-3 facultatif. Le pont journalise uniquement des métadonnées d’exploitation : longueur en octets de l’entrée, largeur et hauteur cibles, longueur en octets de la sortie, hauteur de contenu mesurée, lancement du navigateur avec le chemin du binaire configuré, notification de redémarrage avec un compteur de rendus, et événements de fermeture. Il ne journalise pas le contenu HTML, les octets du rendu ni le texte extrait. Cela correspond aux recommandations de la norme NIST SP 800-92 : journaliser les événements d’exploitation tout en gardant les charges utiles sensibles hors des journaux. Le chemin du binaire est journalisé ; traite-le comme une métadonnée de déploiement non sensible. La forme des appels de journalisation est vérifiée par tests/Unit/Artisan/ChromeHtmlRendererTest.php::renderLogsDebugWithSizeWidthHeightAndPdfSize et tests/Unit/Artisan/BrowserPoolTest.php::getBrowserLogsInfoOnLaunchWithBinaryPath.
Modèle d’isolation réseau (défense en profondeur)
Section intitulée « Modèle d’isolation réseau (défense en profondeur) »Le pont applique deux barrières indépendantes, de sorte que le contournement de l’une d’elles n’expose pas, à lui seul, l’hôte :
-
Content-Security-Policy. Chaque rendu est encapsulé par
ChromeSecurityPolicy::wrapHtml()dans un document qui contient :default-src 'none'; style-src 'unsafe-inline'; img-src data:;base-uri 'none'; form-action 'none'; frame-ancestors 'none';navigate-to 'none';default-src 'none'refuse toutes les origines de ressources.img-src data:n’autorise que les images en ligne.navigate-to 'none'bloque la navigation côté client.style-src 'unsafe-inline'est le seul assouplissement nécessaire pour queprintToPDFde Chrome applique les styles en ligne. Le tout est vérifié danssrc/Artisan/ChromeSecurityPolicy.phpet garanti parChromeSecurityPolicyTest::wrapHtmlIncludesNavigationCspDirectives. -
Blocage du transport CDP. Avant le chargement du contenu,
ChromeHtmlRendererémetNetwork.enablepuisNetwork.setBlockedURLsavec le motif['*'], ce qui bloque toute URL de sous-ressource au niveau de la couche de transport du Chrome DevTools Protocol, indépendamment de la CSP. C’est vérifié danssrc/Artisan/ChromeHtmlRenderer::blockAllNetworkRequests()et garanti parChromeHtmlRendererTest::renderAutoFitsHeightAndBlocksNetworkRequests(qui vérifie l’ordre exact des méthodes CDP et le paramètre['urls' => ['*']]). Il s’agit du blocage au niveau réseau que les recommandations OWASP sur la SSRF préconisent comme le contrôle le plus fort, et d’un refus global au niveau du transport, cohérent avec la norme NIST SP 800-53 SC-7.
Résultat : dans l’entrée, une URL distante d’<img>, de feuille de style, de police, de script ou d’iframe ne se charge pas. Le pont n’implémente ni liste d’autorisation de domaines ni filtre d’IP privées, parce qu’il n’en a pas besoin — il ne permet aucune récupération de sous-ressource sortante.
Note de divergence : le docblock de
nextpdf/coresurwriteHtmlChrome()indique que Chrome « va récupérer des ressources externes » et conseille de configurer une politique pour « bloquer les plages d’IP privées et limiter les domaines autorisés. » Cela décrit un modèle de liste d’autorisation configurable. LeChromeSecurityPolicyd’Artisan effectivement livré n’expose aucune liste d’autorisation et bloque au contraire toutes les requêtes de sous-ressources de façon inconditionnelle. C’est le code, et non le docblock du cœur, qui fait autorité. Cette divergence est consignée à l’intention de l’équipe de documentation du cœur.
Validation des entrées (avant Chrome)
Section intitulée « Validation des entrées (avant Chrome) »ChromeSecurityPolicy::validate() s’exécute avant que Chrome ne soit contacté et rejette :
| Contrôle | Limite | Justification |
|---|---|---|
| Taille du HTML | > maxHtmlSize (5 Mo par défaut) | Borne contre l’épuisement des ressources (consommation incontrôlée de ressources, CWE Top 25) |
| URI de données base64 | groupe de capture >= 13_000_000 octets | Borne contre les bombes de décompression |
<meta http-equiv="refresh"> | toute (insensible à la casse, guillemets simples/doubles) | Bloque la redirection côté client vers des points de terminaison internes — un vecteur de navigation SSRF |
Le blocage du meta-refresh constitue un durcissement SSRF explicite : sans lui, du HTML malveillant pourrait rediriger Chrome vers un point de terminaison de métadonnées cloud avant printToPDF. Le comportement aux limites est garanti par ChromeSecurityPolicyTest (validateThrowsOnOversizedHtml, validateRejectsMetaRefreshRedirect, validateRejectsMetaRefreshCaseInsensitive, validateRejectsMetaRefreshWithSingleQuotes, validateRejectsOversizedBase64DataUri, validateRejectsBase64DataUriAtExactThreshold).
De plus, ChromeSecurityPolicy::wrapHtml() retire </style> de defaultCss avant l’injection, afin d’empêcher une évasion du bloc de style vers le contexte de script (garanti par ChromeSecurityPolicyTest::wrapHtmlStripsStyleClosingTagsFromDefaultCss).
La limite du bac à sable de Chrome — énoncée explicitement
Section intitulée « La limite du bac à sable de Chrome — énoncée explicitement »Le bac à sable du système d’exploitation de Chrome est un contrôle distinct des barrières réseau ci-dessus, et le pont ne le garantit pas.
- Par défaut,
noSandboxvautfalse, si bien que Chrome démarre avec son propre bac à sable activé. Le pont n’implémente pas le bac à sable ; il s’appuie sur celui du binaire Chrome, qui dépend de la prise en charge par le noyau de l’hôte. - Définir
noSandbox: truelance Chrome avec--no-sandbox. Cela supprime le bac à sable d’isolation de processus de Chrome. Cette option est prévue pour les conteneurs où le bac à sable ne peut pas s’initialiser. C’est une réduction réelle de l’isolation : une compromission du moteur de rendu n’est plus contenue par le bac à sable de Chrome. - Les barrières réseau du pont (CSP + blocage CDP) restent en vigueur que le bac à sable soit activé ou non, mais elles ne remplacent pas l’isolation de processus. Les recommandations OWASP ASVS sur le moindre privilège s’appliquent : exécute Chrome en tant qu’utilisateur non root, dans un conteneur restreint, avec
noSandboxuniquement là où c’est inévitable, et traite un déploiement--no-sandboxcomme une exigence de confiance plus élevée sur l’entrée.
Cette documentation ne prétend pas que le pont est « sécurisé par défaut », « inviolable », ni que désactiver le bac à sable est sûr. Elle énonce les contrôles qui existent et l’endroit où ils s’arrêtent. La préparation d’un conteneur compatible avec le bac à sable est traitée sur la page /integrations/artisan/chrome-renderer-setup/.
Modes de défaillance
Section intitulée « Modes de défaillance »Ils sont énumérés à partir de src/Artisan/Exception/ et du code de rendu/transport :
| Condition | Exposée sous la forme | Source |
|---|---|---|
chrome-php/chrome (bibliothèque) absente | ChromeNotAvailableException (avec la commande d’installation) | BrowserPool::getBrowser() |
Le HTML dépasse maxHtmlSize | RuntimeException (« exceeds maximum allowed size ») | ChromeSecurityPolicy::validate() |
| URI de données base64 surdimensionnée | RuntimeException (« oversized base64 data URI ») | ChromeSecurityPolicy::validate() |
| Meta-refresh interdit | RuntimeException (« forbidden meta refresh redirect ») | ChromeSecurityPolicy::validate() |
| Lancement / délai d’expiration / plantage de Chrome | ChromeRenderException (encapsulant la cause) | ChromeHtmlRenderer::render() |
| Chrome a renvoyé un PDF vide | ChromeRenderException (« returned empty data ») | ChromeHtmlRenderer::render() |
| La page n’a aucun flux de contenu | PdfParseException | PageImporter::import() |
Une ChromeRenderException levée pendant le rendu est relancée telle quelle. Tout autre Throwable est encapsulé en ChromeRenderException en conservant l’exception précédente (garanti par ChromeHtmlRendererTest::renderRethrowsChromeRenderExceptionWithoutWrapping et ::renderWrapsUnexpectedThrowablesWithChromeRenderException). La page Chrome est toujours fermée dans un bloc finally, même en cas d’échec.
Limites de ressources
Section intitulée « Limites de ressources »- Taille de l’entrée :
maxHtmlSize(5 Mo par défaut) et le plafond de 13 Mo pour les URI de données base64. - Temps : la valeur
renderTimeout, en secondes, borne à la fois le chargement du contenu et les appels CDP synchrones. Les commandes de contrôle CDP utilisent un délai d’expiration fixe de 5 secondes. - Processus :
BrowserPoolredémarre Chrome tous les 100 rendus pour borner la croissance de la mémoire et ferme le processus lors declose()/ de la destruction.
Ce sont des bornes, pas des quotas. Une limite de ressources à l’échelle de l’hôte (cgroup, ulimit, budget de requêtes) reste recommandée pour tout chemin exposé à une entrée non fiable, conformément aux recommandations du CWE Top 25 sur la consommation de ressources.
Points d’observabilité
Section intitulée « Points d’observabilité »Injecte un logger PSR-3 pour capturer : le début du rendu (taille, largeur, hauteur), la fin du rendu (taille de sortie, hauteur de contenu), le lancement du navigateur (chemin du binaire), le redémarrage du navigateur (compteur de rendus) et la fermeture du navigateur (compteur de rendus). Ce sont les seuls événements émis et ils ne portent aucun contenu de charge utile. Utilise-les pour les SLO de latence et les alertes sur le taux de redémarrage.
Conformité
Section intitulée « Conformité »| Affirmation | Référence | clause_id | reference_id |
|---|---|---|---|
| Les requêtes sortantes des composants serveur doivent être contrôlées | OWASP ASVS 5.0 | § (SSRF / contrôle du trafic sortant) | |
| SSRF = le serveur récupère une URL fournie sans valider la destination | CWE Top 25 2025 (CWE-918) | cwe_top25_2025#x28.x2.p2 | |
| La SSRF (CWE-918) est une faiblesse du CWE Top 25 | CWE Top 25 2025 | cwe_top25_2025#x1.p73 | |
| La consommation incontrôlée de ressources est une faiblesse du CWE Top 25 | CWE Top 25 2025 (CWE-400) | cwe_top25_2025#x19.x2.p2 | |
| Protection de limite par refus par défaut (autorisation par exception) | NIST SP 800-53 Rev 5 SC-7 | SC-7 | |
| Le refus, au niveau réseau, des appels vers des destinations arbitraires est le contrôle fort contre la SSRF | OWASP Cheat Sheet Series (SSRF Prevention §Network layer) | owasp_cheatsheet_series#x132.x2 | |
| Protéger les composants qui récupèrent des URL contre la SSRF | OWASP Cheat Sheet Series | § (prévention de la SSRF, outils de récupération d’URL) | |
| Isoler le rendu de contenu non fiable, moindre privilège | OWASP ASVS 5.0 | § (bac à sable / moindre privilège) | |
| Journaliser les événements d’exploitation ; garder les charges utiles hors des journaux | NIST SP 800-92 | § (recommandations sur le contenu des journaux) |
Les citations ont été récupérées via le moteur de conformité NextPDF (manifeste de corpus 1d05b7c4…d790b6) ; le texte des clauses est paraphrasé, jamais cité textuellement.
Modèle de menace
Section intitulée « Modèle de menace »| Menace | Contrôle | Risque résiduel |
|---|---|---|
| SSRF via une sous-ressource distante | CSP default-src 'none' + CDP setBlockedURLs('*') | Un bug du moteur Chrome qui contournerait les deux barrières (la défense en profondeur réduit le risque, sans l’éliminer) |
| SSRF via une navigation par meta-refresh | La validation avant Chrome rejette la balise | Un nouveau vecteur de navigation non capturé par le motif |
| Épuisement des ressources | Taille de l’entrée + plafonds base64 + délai d’expiration + redémarrage tous les 100 rendus | Pas de quota par hôte ; à associer à un cgroup/ulimit |
| Compromission du processus de rendu | Bac à sable de Chrome lorsqu’il est activé | noSandbox: true supprime entièrement ce contrôle |
| Évasion de style / injection | </style> retiré de defaultCss ; la CSP bloque les scripts | Injection par un futur vecteur non filtré |
Comportement en mode FIPS
Section intitulée « Comportement en mode FIPS »Le pont n’effectue aucune opération cryptographique. Il produit des octets PDF via Chrome et les intègre. La signature, le chiffrement et le comportement en mode FIPS relèvent du cœur et de Premium, et ne sont pas affectés par Artisan.
Voir aussi
Section intitulée « Voir aussi »- /integrations/artisan/configuration/
- /integrations/artisan/chrome-renderer-setup/
- /integrations/artisan/troubleshooting/
- /integrations/artisan/production-usage/
- /integrations/artisan/overview/