Pular para o conteúdo

Segurança e operações do Artisan

A ponte renderiza HTML potencialmente não confiável no Chrome, por trás de duas barreiras de rede independentes e de uma política de conteúdo rigorosa. O sandbox do sistema operacional do Chrome é um controle separado e opcional, com limites explícitos. Esta página documenta essa fronteira. Ela não afirma que a fronteira seja absoluta.

Uma renderização executa uma requisição no lado do servidor: a aplicação entrega o HTML a um mecanismo de navegador que, por padrão, pode buscar recursos. Quando uma entrada não confiável aciona uma busca de saída, o risco é a falsificação de requisição no lado do servidor (SSRF): a entrada CWE-918 do Common Weakness Enumeration (CWE) define isso como um servidor que recupera o conteúdo de uma URL fornecida sem garantia suficiente de que a requisição chegue ao destino esperado. A SSRF (CWE-918) é uma fraqueza do CWE Top 25. O Application Security Verification Standard (ASVS) do Open Worldwide Application Security Project (OWASP) exige que você controle as requisições de saída dos componentes do servidor, em vez de deixá-las implícitas. O OWASP SSRF Prevention Cheat Sheet trata a negação, na camada de rede, de chamadas a destinos arbitrários como o controle forte. A postura de rede com negação por padrão apresentada abaixo é a resposta da ponte a esse requisito. A SC-7 do National Institute of Standards and Technology (NIST) Special Publication (SP) 800-53 descreve o mesmo princípio de fronteira de negar tudo e permitir por exceção que a ponte aplica na camada de transporte.

O HTML passado à ponte é processado inteiramente no processo e dentro da instância local do Chrome. A ponte não faz nenhuma chamada própria de rede de saída e impede que o Chrome faça qualquer uma (consulte o modelo de rede abaixo), de modo que o conteúdo de entrada não sai do host por meio do renderizador. As informações de identificação pessoal (PII) presentes na entrada são renderizadas na saída em Portable Document Format (PDF) que você produz; portanto, trate a saída com os mesmos controles de residência da entrada. A ponte não persiste a entrada nem a saída em disco; a persistência é responsabilidade de quem chama.

ChromeHtmlRenderer e BrowserPool aceitam um LoggerInterface opcional de PHP Standard Recommendation (PSR)-3. A ponte registra apenas metadados operacionais: o comprimento em bytes da entrada, a largura e a altura de destino, o comprimento em bytes da saída, a altura de conteúdo medida, a inicialização do navegador com o caminho do binário configurado, avisos de reinício com contagem de renderizações e eventos de fechamento. Ela não registra conteúdo HTML, bytes renderizados nem texto extraído. Isso está alinhado com a orientação da NIST SP 800-92 de registrar eventos operacionais mantendo cargas sensíveis fora dos logs. O caminho do binário é registrado. Trate-o como metadado de implantação não sensível. Os testes verificam os formatos das chamadas de log em tests/Unit/Artisan/ChromeHtmlRendererTest.php::renderLogsDebugWithSizeWidthHeightAndPdfSize e tests/Unit/Artisan/BrowserPoolTest.php::getBrowserLogsInfoOnLaunchWithBinaryPath.

Modelo de isolamento de rede (defesa em profundidade)

Seção intitulada “Modelo de isolamento de rede (defesa em profundidade)”

A ponte aplica duas barreiras independentes para que uma única fronteira não exponha o host:

  1. Content-Security-Policy. Toda renderização é envolvida por ChromeSecurityPolicy::wrapHtml() em um documento que carrega:

    default-src 'none'; style-src 'unsafe-inline'; img-src data:;
    base-uri 'none'; form-action 'none'; frame-ancestors 'none';
    navigate-to 'none';

    A diretiva default-src 'none' da Content Security Policy (CSP) nega todas as origens de recursos. img-src data: permite apenas imagens embutidas. navigate-to 'none' bloqueia a navegação no lado do cliente. style-src 'unsafe-inline' é a única flexibilização necessária para que o printToPDF do Chrome aplique estilos embutidos. Verificado em src/Artisan/ChromeSecurityPolicy.php e assegurado por ChromeSecurityPolicyTest::wrapHtmlIncludesNavigationCspDirectives.

  2. Bloqueio de transporte do Chrome DevTools Protocol (CDP). Antes de o conteúdo carregar, ChromeHtmlRenderer emite Network.enable e, em seguida, Network.setBlockedURLs com o padrão ['*']. Isso bloqueia todas as URLs de subrecursos na camada de transporte do Chrome DevTools Protocol, independentemente do CSP. Verificado em src/Artisan/ChromeHtmlRenderer::blockAllNetworkRequests() e assegurado por ChromeHtmlRendererTest::renderAutoFitsHeightAndBlocksNetworkRequests (que verifica a ordem exata dos métodos CDP e o parâmetro ['urls' => ['*']]). Esse é o bloqueio na camada de rede que a orientação de SSRF da OWASP recomenda como o controle mais forte, e é uma negação total no nível de transporte, consistente com a SC-7 da NIST SP 800-53.

O resultado: uma URL remota de <img>, folha de estilo, fonte, script ou iframe presente na entrada não é carregada. A ponte não implementa lista de domínios permitidos nem filtro de IP privado porque não precisa de nenhum deles: ela não permite nenhuma busca de subrecurso de saída.

Nota de divergência: o docblock do nextpdf/core em writeHtmlChrome() diz que o Chrome “buscará recursos externos” e recomenda configurar uma política para “bloquear faixas de IP privadas e limitar os domínios permitidos.” Isso descreve um modelo configurável de lista de permitidos. O ChromeSecurityPolicy do Artisan distribuído não expõe uma lista de permitidos; ele bloqueia todas as requisições de subrecurso incondicionalmente. O código, e não o docblock do core, é a fonte autoritativa. Essa divergência está registrada com a equipe de documentação do core.

ChromeSecurityPolicy::validate() é executado antes de a ponte contatar o Chrome e rejeita:

VerificaçãoLimiteJustificativa
Tamanho do HTML> maxHtmlSize (padrão 5 MB)Limite contra exaustão de recursos (consumo descontrolado de recursos do CWE Top 25)
URI de dados base64grupo de captura de >= 13_000_000 bytesLimite contra bomba de descompressão
<meta http-equiv="refresh">qualquer (sem distinção de maiúsculas/minúsculas, aspas single/double)Bloqueia redirecionamentos no lado do cliente para endpoints internos, um vetor de navegação SSRF

O bloqueio de meta-refresh é um endurecimento explícito contra SSRF. Sem ele, um HTML controlado por um invasor poderia redirecionar o Chrome para um endpoint de metadados de nuvem antes do printToPDF. O comportamento da fronteira é assegurado em ChromeSecurityPolicyTest (validateThrowsOnOversizedHtml, validateRejectsMetaRefreshRedirect, validateRejectsMetaRefreshCaseInsensitive, validateRejectsMetaRefreshWithSingleQuotes, validateRejectsOversizedBase64DataUri, validateRejectsBase64DataUriAtExactThreshold).

Além disso, ChromeSecurityPolicy::wrapHtml() remove </style> de defaultCss antes da injeção, para evitar uma saída do bloco de estilo para o contexto de script (assegurado por ChromeSecurityPolicyTest::wrapHtmlStripsStyleClosingTagsFromDefaultCss).

A fronteira do sandbox do Chrome — declarada explicitamente

Seção intitulada “A fronteira do sandbox do Chrome — declarada explicitamente”

O sandbox do sistema operacional do Chrome é um controle separado das barreiras de rede acima, e a ponte não oferece garantia sobre ele.

  • Por padrão, noSandbox é false, de modo que o Chrome é iniciado com seu próprio sandbox ativado. A ponte não implementa esse sandbox; ela depende do sandbox do binário do Chrome, que por sua vez depende do suporte do kernel do host.
  • Definir noSandbox: true inicia o Chrome com --no-sandbox. Isso remove o sandbox de isolamento de processos do Chrome. A opção existe para contêineres em que o sandbox não consegue inicializar. É uma redução real de isolamento: um comprometimento do renderizador deixa de ser contido pelo sandbox do Chrome.
  • As barreiras de rede da ponte (CSP + bloqueio CDP) permanecem em vigor, esteja o sandbox ativado ou não, mas não substituem o isolamento de processos. A orientação de menor privilégio do OWASP ASVS se aplica: execute o Chrome como um usuário não root, em um contêiner restrito, use noSandbox somente quando for inevitável e trate uma implantação com --no-sandbox como um requisito de maior confiança na entrada.

Esta documentação não afirma que a ponte é “segura por padrão” ou “à prova de adulteração”. Ela também não afirma que desativar o sandbox seja seguro. Ela declara os controles existentes e onde eles param. O provisionamento de um contêiner com suporte a sandbox é abordado na página /integrations/artisan/chrome-renderer-setup/.

Estes modos de falha são enumerados a partir de src/Artisan/Exception/ e do código de render/transport:

CondiçãoApresentado comoOrigem
chrome-php/chrome (biblioteca) ausenteChromeNotAvailableException (com comando de instalação)BrowserPool::getBrowser()
HTML excede maxHtmlSizeRuntimeException (“exceeds maximum allowed size”)ChromeSecurityPolicy::validate()
URI de dados base64 com tamanho excessivoRuntimeException (“oversized base64 data URI”)ChromeSecurityPolicy::validate()
Meta-refresh proibidoRuntimeException (“forbidden meta refresh redirect”)ChromeSecurityPolicy::validate()
Inicialização / tempo limite / falha do ChromeChromeRenderException (envolvendo a causa)ChromeHtmlRenderer::render()
Chrome retornou PDF vazioChromeRenderException (“returned empty data”)ChromeHtmlRenderer::render()
Página sem fluxo (stream) de conteúdoPdfParseExceptionPageImporter::import()

Se ChromeRenderException for lançada durante a renderização, ela é relançada sem alterações. Qualquer outro Throwable é envolvido como ChromeRenderException, preservando a exceção anterior (assegurado por ChromeHtmlRendererTest::renderRethrowsChromeRenderExceptionWithoutWrapping e ::renderWrapsUnexpectedThrowablesWithChromeRenderException). A página do Chrome é sempre fechada em um bloco finally, mesmo em caso de falha.

  • Tamanho da entrada: maxHtmlSize (padrão 5 MB) e o limite de 13 MB para URI de dados base64.
  • Tempo: renderTimeout segundos limitam tanto o carregamento de conteúdo quanto as chamadas síncronas do CDP. Os comandos de controle do CDP usam um tempo limite fixo de 5 segundos.
  • Processo: BrowserPool reinicia o Chrome a cada 100 renderizações para limitar o crescimento de memória e encerra o processo em close() / destruição.

Esses são limites, não cotas. Para qualquer caminho exposto a entrada não confiável, ainda assim use limites de recursos no nível do host (cgroup, ulimit, orçamento de requisições), de forma consistente com a orientação de consumo de recursos do CWE Top 25.

Injete um logger PSR-3 para capturar o início da renderização (tamanho, largura, altura), a conclusão da renderização (tamanho da saída, altura do conteúdo), a inicialização do navegador (caminho do binário), o reinício do navegador (contagem de renderizações) e o fechamento do navegador (contagem de renderizações). Esses são os únicos eventos emitidos, e eles não carregam nenhum conteúdo de payload. Use-os para objetivos de nível de serviço (SLOs) de latência e para alertas de taxa de reinício.

AfirmaçãoReferênciaclause_idreference_id
As requisições de saída dos componentes do servidor devem ser controladasOWASP ASVS 5.0§ (SSRF/controle de saída)
SSRF = o servidor recupera uma URL fornecida sem validar o destinoCWE Top 25 2025 (CWE-918)cwe_top25_2025#x28.x2.p2
A SSRF (CWE-918) é uma fraqueza do CWE Top 25CWE Top 25 2025cwe_top25_2025#x1.p73
O consumo descontrolado de recursos é uma fraqueza do CWE Top 25CWE Top 25 2025 (CWE-400)cwe_top25_2025#x19.x2.p2
Proteção de fronteira com negação por padrão (permitir por exceção)NIST SP 800-53 Rev 5 SC-7SC-7
A negação na camada de rede de chamadas a destinos arbitrários é o controle forte de SSRFOWASP Cheat Sheet Series (SSRF Prevention §Network layer)owasp_cheatsheet_series#x132.x2
Proteja contra SSRF os componentes que buscam URLsOWASP Cheat Sheet Series§ (prevenção de SSRF, ferramentas de busca de URL)
Isole a renderização de conteúdo não confiável, menor privilégioOWASP ASVS 5.0§ (sandbox / menor privilégio)
Registre eventos operacionais; mantenha as cargas fora dos logsNIST SP 800-92§ (orientação sobre conteúdo de log)

As citações foram obtidas por meio do mecanismo de conformidade do NextPDF (manifesto do corpus 1d05b7c4…d790b6); o texto das cláusulas é parafraseado, nunca citado literalmente.

AmeaçaControleRisco residual
SSRF via subrecurso remotoCSP default-src 'none' + CDP setBlockedURLs('*')Um bug no mecanismo do Chrome que contorne as duas barreiras (a defesa em profundidade reduz o risco, mas não o elimina)
SSRF via navegação por meta-refreshA validação pré-Chrome rejeita a tagUm novo vetor de navegação não correspondido pelo padrão
Exaustão de recursosLimites de tamanho da entrada + de base64 + tempo limite + reinício a cada 100 renderizaçõesSem cota por host; combine com cgroup/ulimit
Comprometimento do processo do renderizadorSandbox do Chrome quando ativadonoSandbox: true remove esse controle por completo
Fuga de estilo / injeção</style> removido em defaultCss; o CSP bloqueia scriptInjeção por meio de um vetor futuro que não seja removido

A ponte não realiza nenhuma operação criptográfica. Ela produz bytes de PDF por meio do Chrome e os incorpora. Assinatura, criptografia e comportamento no modo Federal Information Processing Standards (FIPS) são questões do core/Premium e não são afetados pelo Artisan.

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