Ir al contenido

Seguridad y operaciones de Artisan

El puente renderiza HTML potencialmente no confiable dentro de Chrome, detrás de dos barreras de red independientes y una política de contenido estricta. El sandbox del sistema operativo de Chrome es un control aparte y opcional, con límites declarados. Esta página documenta esa frontera; no afirma que sea absoluta.

Un renderizado equivale a ejecutar una solicitud del lado del servidor: la aplicación entrega HTML a un motor de navegador que, de forma predeterminada, puede obtener recursos. Una obtención saliente dirigida por entradas no confiables es una falsificación de solicitudes del lado del servidor: CWE-918 la define como un servidor que recupera el contenido de una URL suministrada sin asegurar suficientemente que la solicitud llegue al destino previsto. La SSRF (CWE-918) es una debilidad del CWE Top 25. OWASP ASVS exige controlar, y no dejar implícitas, las solicitudes salientes de los componentes del servidor. La OWASP SSRF Prevention Cheat Sheet considera que la denegación estricta en la capa de red de llamadas a destinos arbitrarios es un control fuerte. La postura de red de denegación predeterminada que se describe a continuación es la respuesta del puente a ese requisito. NIST SP 800-53 SC-7 describe el mismo principio de frontera: denegar todo y permitir por excepción, que el puente aplica en la capa de transporte.

El HTML que se pasa al puente se procesa íntegramente en el propio proceso y dentro de la instancia local de Chrome. El puente no realiza por sí mismo ninguna llamada de red saliente y bloquea cualquier intento de Chrome de hacerlas (consulta el modelo de red a continuación), por lo que el contenido de entrada no abandona el host a través del renderer. La PII presente en la entrada se renderiza en el PDF que produces: trata la salida con los mismos controles de residencia que la entrada. El puente no persiste la entrada ni la salida en disco; esa persistencia es responsabilidad de quien lo invoca.

Telemetría segura y registros de depuración

Sección titulada «Telemetría segura y registros de depuración»

ChromeHtmlRenderer y BrowserPool aceptan un LoggerInterface PSR-3 opcional. El puente solo registra metadatos operativos: longitud en bytes de la entrada, ancho y alto de destino, longitud en bytes de la salida, alto de contenido medido, lanzamiento del navegador con la ruta del binario configurada, avisos de reinicio con un recuento de renderizados y eventos de cierre. No registra el contenido HTML, los bytes renderizados ni el texto extraído. Esto coincide con la guía de NIST SP 800-92: registrar eventos operativos manteniendo las cargas útiles sensibles fuera de los registros. La ruta del binario se registra; trátala como metadato de despliegue no sensible. La forma de las llamadas de registro queda verificada por tests/Unit/Artisan/ChromeHtmlRendererTest.php::renderLogsDebugWithSizeWidthHeightAndPdfSize y tests/Unit/Artisan/BrowserPoolTest.php::getBrowserLogsInfoOnLaunchWithBinaryPath.

Modelo de aislamiento de red (defensa en profundidad)

Sección titulada «Modelo de aislamiento de red (defensa en profundidad)»

El puente aplica dos barreras independientes para que, si una se elude, el host no quede expuesto:

  1. Content-Security-Policy. Cada renderizado se envuelve mediante ChromeSecurityPolicy::wrapHtml() en un documento que incluye:

    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' deniega todos los orígenes de recursos. img-src data: permite únicamente imágenes en línea. navigate-to 'none' bloquea la navegación del lado del cliente. style-src 'unsafe-inline' es la única flexibilización necesaria para que printToPDF de Chrome aplique estilos en línea. Verificado en src/Artisan/ChromeSecurityPolicy.php y por ChromeSecurityPolicyTest::wrapHtmlIncludesNavigationCspDirectives.

  2. Bloqueo de transporte CDP. Antes de cargar el contenido, ChromeHtmlRenderer emite Network.enable y después Network.setBlockedURLs con el patrón ['*'], lo que bloquea todas las URL de subrecursos en la capa de transporte de Chrome DevTools Protocol, independientemente de la CSP. Verificado en src/Artisan/ChromeHtmlRenderer::blockAllNetworkRequests() y por ChromeHtmlRendererTest::renderAutoFitsHeightAndBlocksNetworkRequests (que comprueba el orden exacto de los métodos CDP y el parámetro ['urls' => ['*']]). Este es el bloqueo de capa de red que la guía OWASP SSRF recomienda como control más fuerte, y una denegación total en la capa de transporte coherente con NIST SP 800-53 SC-7.

El resultado: si la entrada contiene una URL remota en un <img>, una hoja de estilos, una fuente, un script o un iframe, no se carga. El puente no implementa una lista de dominios permitidos ni un filtro de IP privadas porque no los necesita: no permite ninguna obtención saliente de subrecursos.

Nota de divergencia: el docblock de nextpdf/core en writeHtmlChrome() afirma que Chrome «obtendrá recursos externos» y recomienda configurar una política para «bloquear rangos de IP privadas y limitar los dominios permitidos». Eso describe un modelo de lista de permitidos configurable. El ChromeSecurityPolicy de Artisan que se distribuye no expone una lista de permitidos y, en su lugar, bloquea todas las solicitudes de subrecursos de forma incondicional. El código, no el docblock del core, es la autoridad. Esta divergencia queda registrada para el equipo de documentación del core.

ChromeSecurityPolicy::validate() se ejecuta antes de contactar con Chrome y rechaza:

ComprobaciónLímiteJustificación
Tamaño del HTML> maxHtmlSize (predeterminado 5 MB)Límite frente al agotamiento de recursos (consumo de recursos no controlado del CWE Top 25)
URI de datos en Base64grupo de captura de >= 13_000_000 bytesLímite frente a bombas de descompresión
<meta http-equiv="refresh">cualquiera (sin distinción de mayúsculas, comillas simples/dobles)Bloquea la redirección del lado del cliente hacia endpoints internos: un vector de navegación de SSRF

El bloqueo de meta-refresh es un endurecimiento explícito frente a SSRF: sin él, un HTML malicioso podría redirigir Chrome a un endpoint de metadatos en la nube antes de printToPDF. El comportamiento en los límites se verifica en ChromeSecurityPolicyTest (validateThrowsOnOversizedHtml, validateRejectsMetaRefreshRedirect, validateRejectsMetaRefreshCaseInsensitive, validateRejectsMetaRefreshWithSingleQuotes, validateRejectsOversizedBase64DataUri, validateRejectsBase64DataUriAtExactThreshold).

Además, ChromeSecurityPolicy::wrapHtml() elimina </style> de defaultCss antes de la inyección para impedir que el bloque de estilos escape al contexto de script (verificado por ChromeSecurityPolicyTest::wrapHtmlStripsStyleClosingTagsFromDefaultCss).

La frontera del sandbox de Chrome, declarada de forma explícita

Sección titulada «La frontera del sandbox de Chrome, declarada de forma explícita»

El sandbox del sistema operativo de Chrome es un control aparte de las barreras de red anteriores, y el puente no lo garantiza.

  • De forma predeterminada, noSandbox es false, por lo que Chrome se lanza con su propio sandbox habilitado. El puente no implementa ese sandbox; depende del sandbox del binario de Chrome, que a su vez depende del soporte del kernel del host.
  • Establecer noSandbox: true lanza Chrome con --no-sandbox. Esto elimina el sandbox de aislamiento de procesos de Chrome. Se proporciona para contenedores donde el sandbox no puede inicializarse. Es una reducción real del aislamiento: un renderer comprometido ya no queda contenido por el sandbox de Chrome.
  • Las barreras de red del puente (CSP + bloqueo CDP) siguen en vigor con el sandbox habilitado o deshabilitado, pero no sustituyen al aislamiento de procesos. Se aplica la guía de mínimo privilegio de OWASP ASVS: ejecuta Chrome como un usuario sin privilegios de root, en un contenedor restringido, con noSandbox solo cuando sea inevitable, y trata un despliegue con --no-sandbox como un requisito de mayor confianza en la entrada.

Esta documentación no afirma que el puente sea «seguro de forma predeterminada», «a prueba de manipulaciones» ni que deshabilitar el sandbox sea seguro. Declara los controles que existen y dónde terminan. El aprovisionamiento de un contenedor con sandbox se trata en la página /integrations/artisan/chrome-renderer-setup/.

La lista se basa en src/Artisan/Exception/ y en el código de render/transport:

CondiciónSe manifiesta comoOrigen
Biblioteca chrome-php/chrome ausenteChromeNotAvailableException (con comando de instalación)BrowserPool::getBrowser()
El HTML supera maxHtmlSizeRuntimeException («exceeds maximum allowed size»)ChromeSecurityPolicy::validate()
URI de datos en Base64 sobredimensionadoRuntimeException («oversized base64 data URI»)ChromeSecurityPolicy::validate()
Meta-refresh prohibidoRuntimeException («forbidden meta refresh redirect»)ChromeSecurityPolicy::validate()
Lanzamiento / tiempo de espera / caída de ChromeChromeRenderException (que envuelve la causa)ChromeHtmlRenderer::render()
Chrome devolvió un PDF vacíoChromeRenderException («returned empty data»)ChromeHtmlRenderer::render()
La página no tiene flujo de contenidoPdfParseExceptionPageImporter::import()

Si se lanza una ChromeRenderException dentro del renderizado, se vuelve a lanzar sin cambios. Cualquier otro Throwable se envuelve como ChromeRenderException, conservando la excepción previa (verificado por ChromeHtmlRendererTest::renderRethrowsChromeRenderExceptionWithoutWrapping y ::renderWrapsUnexpectedThrowablesWithChromeRenderException). La página de Chrome siempre se cierra en un bloque finally, incluso si se produce un fallo.

  • Tamaño de la entrada: maxHtmlSize (predeterminado 5 MB) y el límite de 13 MB para URI de datos en Base64.
  • Tiempo: renderTimeout segundos limita tanto la carga del contenido como las llamadas síncronas de CDP. Los comandos de control de CDP usan un tiempo de espera fijo de 5 segundos.
  • Proceso: BrowserPool reinicia Chrome cada 100 renderizados para limitar el crecimiento de memoria y cierra el proceso al llamar a close() / al destruirse.

Estos son límites, no cuotas. Para cualquier ruta expuesta a entradas no confiables, se sigue recomendando un límite de recursos a nivel del host (cgroup, ulimit, presupuesto de solicitudes), coherente con la guía de consumo de recursos del CWE Top 25.

Inyecta un logger PSR-3 para capturar: inicio del renderizado (tamaño, ancho, alto), renderizado completo (tamaño de salida, alto de contenido), lanzamiento del navegador (ruta del binario), reinicio del navegador (recuento de renderizados) y cierre del navegador (recuento de renderizados). Estos son los únicos eventos emitidos y no transportan contenido de la carga útil. Úsalos para SLO de latencia y alertas sobre la tasa de reinicios.

AfirmaciónReferenciaclause_idreference_id
Las solicitudes salientes de los componentes del servidor deben controlarseOWASP ASVS 5.0§ (SSRF/control de salientes)
SSRF = el servidor recupera una URL suministrada sin validar el destinoCWE Top 25 2025 (CWE-918)cwe_top25_2025#x28.x2.p2
La SSRF (CWE-918) es una debilidad del CWE Top 25CWE Top 25 2025cwe_top25_2025#x1.p73
El consumo de recursos no controlado es una debilidad del CWE Top 25CWE Top 25 2025 (CWE-400)cwe_top25_2025#x19.x2.p2
Protección de frontera de denegación predeterminada (permitir por excepción)NIST SP 800-53 Rev 5 SC-7SC-7
La denegación en la capa de red de las llamadas a destinos arbitrarios es el control fuerte frente a SSRFOWASP Cheat Sheet Series (SSRF Prevention §capa de red)owasp_cheatsheet_series#x132.x2
Proteger frente a SSRF los componentes que obtienen URLOWASP Cheat Sheet Series§ (prevención de SSRF, herramientas de obtención de URL)
Aislar el renderizado de contenido no confiable, mínimo privilegioOWASP ASVS 5.0§ (sandbox / mínimo privilegio)
Registrar eventos operativos; mantener las cargas útiles fuera de los registrosNIST SP 800-92§ (guía sobre el contenido de los registros)

Las citas se recuperaron mediante el motor de conformidad de NextPDF (manifiesto del corpus 1d05b7c4…d790b6); el texto de las cláusulas se parafrasea, nunca se cita.

AmenazaControlRiesgo residual
SSRF a través de un subrecurso remotoCSP default-src 'none' + CDP setBlockedURLs('*')Un fallo del motor de Chrome que eluda ambas barreras (la defensa en profundidad reduce el riesgo, no lo elimina)
SSRF a través de navegación por meta-refreshLa validación previa a Chrome rechaza la etiquetaUn nuevo vector de navegación no detectado por el patrón
Agotamiento de recursosLímites de tamaño de entrada + límites de base64 + tiempo de espera + reinicio cada 100 renderizadosSin cuota por host; combínalo con cgroup/ulimit
Compromiso del proceso rendererSandbox de Chrome cuando está habilitadonoSandbox: true elimina este control por completo
Fuga de estilos / inyección</style> eliminado en defaultCss; la CSP bloquea el scriptInyección a través de un futuro vector no eliminado

El puente no realiza ninguna operación criptográfica. Produce bytes de PDF mediante Chrome y los incrusta. La firma, el cifrado y el comportamiento en modo FIPS son asuntos del core/Premium; Artisan no los afecta.

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