Aller au contenu

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.

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.

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 :

  1. 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 que printToPDF de Chrome applique les styles en ligne. Le tout est vérifié dans src/Artisan/ChromeSecurityPolicy.php et garanti par ChromeSecurityPolicyTest::wrapHtmlIncludesNavigationCspDirectives.

  2. Blocage du transport CDP. Avant le chargement du contenu, ChromeHtmlRenderer émet Network.enable puis Network.setBlockedURLs avec 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é dans src/Artisan/ChromeHtmlRenderer::blockAllNetworkRequests() et garanti par ChromeHtmlRendererTest::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/core sur writeHtmlChrome() 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. Le ChromeSecurityPolicy d’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.

ChromeSecurityPolicy::validate() s’exécute avant que Chrome ne soit contacté et rejette :

ContrôleLimiteJustification
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 base64groupe de capture >= 13_000_000 octetsBorne 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, noSandbox vaut false, 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: true lance 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 noSandbox uniquement là où c’est inévitable, et traite un déploiement --no-sandbox comme 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/.

Ils sont énumérés à partir de src/Artisan/Exception/ et du code de rendu/transport :

ConditionExposée sous la formeSource
chrome-php/chrome (bibliothèque) absenteChromeNotAvailableException (avec la commande d’installation)BrowserPool::getBrowser()
Le HTML dépasse maxHtmlSizeRuntimeException (« exceeds maximum allowed size »)ChromeSecurityPolicy::validate()
URI de données base64 surdimensionnéeRuntimeException (« oversized base64 data URI »)ChromeSecurityPolicy::validate()
Meta-refresh interditRuntimeException (« forbidden meta refresh redirect »)ChromeSecurityPolicy::validate()
Lancement / délai d’expiration / plantage de ChromeChromeRenderException (encapsulant la cause)ChromeHtmlRenderer::render()
Chrome a renvoyé un PDF videChromeRenderException (« returned empty data »)ChromeHtmlRenderer::render()
La page n’a aucun flux de contenuPdfParseExceptionPageImporter::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.

  • 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 : BrowserPool redémarre Chrome tous les 100 rendus pour borner la croissance de la mémoire et ferme le processus lors de close() / 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.

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.

AffirmationRéférenceclause_idreference_id
Les requêtes sortantes des composants serveur doivent être contrôléesOWASP ASVS 5.0§ (SSRF / contrôle du trafic sortant)
SSRF = le serveur récupère une URL fournie sans valider la destinationCWE Top 25 2025 (CWE-918)cwe_top25_2025#x28.x2.p2
La SSRF (CWE-918) est une faiblesse du CWE Top 25CWE Top 25 2025cwe_top25_2025#x1.p73
La consommation incontrôlée de ressources est une faiblesse du CWE Top 25CWE 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-7SC-7
Le refus, au niveau réseau, des appels vers des destinations arbitraires est le contrôle fort contre la SSRFOWASP Cheat Sheet Series (SSRF Prevention §Network layer)owasp_cheatsheet_series#x132.x2
Protéger les composants qui récupèrent des URL contre la SSRFOWASP Cheat Sheet Series§ (prévention de la SSRF, outils de récupération d’URL)
Isoler le rendu de contenu non fiable, moindre privilègeOWASP ASVS 5.0§ (bac à sable / moindre privilège)
Journaliser les événements d’exploitation ; garder les charges utiles hors des journauxNIST 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.

MenaceContrôleRisque résiduel
SSRF via une sous-ressource distanteCSP 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-refreshLa validation avant Chrome rejette la baliseUn nouveau vecteur de navigation non capturé par le motif
Épuisement des ressourcesTaille de l’entrée + plafonds base64 + délai d’expiration + redémarrage tous les 100 rendusPas de quota par hôte ; à associer à un cgroup/ulimit
Compromission du processus de renduBac à 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 scriptsInjection par un futur vecteur non filtré

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.

  • /integrations/artisan/configuration/
  • /integrations/artisan/chrome-renderer-setup/
  • /integrations/artisan/troubleshooting/
  • /integrations/artisan/production-usage/
  • /integrations/artisan/overview/