Skip to content

Mejores prácticas de seguridad

Esta guía proporciona recomendaciones de seguridad accionables para desplegar TCPDF-Next en entornos de producción. Seguir estas prácticas asegura que tu pipeline de generación de PDF cumpla con los estándares de seguridad empresarial.

Validación de entrada (sanitización HTML antes de writeHtml)

Al generar PDFs desde HTML proporcionado por el usuario, siempre sanitiza la entrada antes de pasarla al renderizador HTML. El HtmlRenderer de TCPDF-Next parsea y renderiza HTML fielmente, lo que significa que markup malicioso puede ser explotado si no se sanitiza.

php
use YeeeFang\TcpdfNext\Html\HtmlRenderer;

// PELIGROSO: Nunca pases entrada de usuario raw directamente
// $renderer->writeHtml($userInput);

// SEGURO: Sanitiza primero con una biblioteca dedicada
$clean = \HTMLPurifier::getInstance()->purify($userInput);
$renderer->writeHtml($clean);

Reglas clave:

  • Elimina los tags <script>, <iframe>, <object>, <embed> y <link> antes de renderizar.
  • Elimina los esquemas URI javascript: y data: de los atributos href y src.
  • Limita las propiedades CSS permitidas a las necesarias para el layout (sin position: fixed, sin url() en valores CSS).
  • Valida la codificación de caracteres — asegura que la entrada sea UTF-8 válida antes de pasarla al renderizador.

Gestión de certificados (almacenamiento seguro y rotación)

Jerarquía de almacenamiento

MétodoNivel de seguridadCaso de uso
Módulo de seguridad hardware (HSM)MáximoEntornos de producción, industrias reguladas
Cloud KMS (AWS KMS, Azure Key Vault, GCP KMS)AltoDespliegues cloud-native
Archivo PKCS#12 con frase de paso fuerteMedioDespliegues pequeños
Archivo PEM (cifrado)Medio-BajoDesarrollo, testing
Archivo PEM (sin cifrar)MínimoNunca en producción

Política de rotación

  • Renueva certificados al menos 30 días antes de la expiración.
  • Monitorea la expiración con alertas automatizadas en umbrales de 30, 14, 7 y 1 día.
  • Revoca certificados comprometidos inmediatamente a través de la CA emisora.
  • Mantén un log de auditoría firmado de todas las operaciones del ciclo de vida de certificados.
php
use YeeeFang\TcpdfNext\Certificate\CertificateStore;

$store = new CertificateStore();
$store->loadFromDirectory('/etc/tcpdf-next/certs/', '*.pem');

$activeCert = $store->getActiveCertificate('document-signing');

if ($activeCert->getExpirationDate() < new \DateTimeImmutable('+30 days')) {
    $logger->warning('Signing certificate expires soon', [
        'subject' => $activeCert->getSubject(),
        'expires' => $activeCert->getExpirationDate()->format('Y-m-d'),
    ]);
}

DANGER

Nunca almacenes claves privadas en repositorios de código fuente, archivos sin cifrar en sistemas de archivos compartidos, columnas de base de datos sin cifrado en reposo, o archivos de log.

Manejo de contraseñas (SASLprep y contraseñas fuertes)

Al configurar contraseñas de cifrado PDF, aplica normalización Unicode via SASLprep (RFC 4013) para asegurar procesamiento de contraseñas consistente entre plataformas:

php
// TCPDF-Next aplica SASLprep a las contraseñas automáticamente
$pdf->setEncryption()
    ->setAlgorithm(EncryptionAlgorithm::AES256)
    ->setUserPassword('pässwörd-with-ünïcöde')  // SASLprep normalizado internamente
    ->setOwnerPassword($strongOwnerPassword)
    ->apply();

Recomendaciones de política de contraseñas:

  • Mínimo 12 caracteres para contraseñas de usuario, 20 caracteres para contraseñas de propietario.
  • Usa un generador aleatorio criptográficamente seguro para contraseñas de propietario (random_bytes()).
  • Nunca codifiques contraseñas en el código fuente — cárgalas desde variables de entorno o gestores de secretos.
  • Limpia las contraseñas de memoria después de usarlas con sodium_memzero().

Prevención SSRF (validación de URL para imágenes, TSA, OCSP)

TCPDF-Next bloquea SSRF por defecto, pero debes configurar listas de permitidos para recursos externos legítimos:

php
use YeeeFang\TcpdfNext\Security\NetworkPolicy;

$networkPolicy = NetworkPolicy::create()
    ->denyPrivateNetworks()     // Bloquear 10.x, 172.16.x, 192.168.x
    ->denyLoopback()            // Bloquear 127.0.0.1
    ->denyLinkLocal()           // Bloquear 169.254.x
    ->allowDomain('cdn.yourcompany.com')       // Imágenes
    ->allowDomain('timestamp.digicert.com')    // TSA
    ->allowDomain('ocsp.digicert.com')         // OCSP
    ->setMaxRedirects(3)
    ->setRequestTimeout(10);

$pdf = PdfDocument::create()
    ->setNetworkPolicy($networkPolicy)
    ->build();

Checklist:

  • Valida todas las URLs antes de obtenerlas (esquema, host, puerto).
  • Incluye explícitamente en la lista de permitidos los dominios de TSA y OCSP responder.
  • Bloquea los esquemas file://, gopher://, ftp:// y otros no HTTP(S).
  • Registra todas las solicitudes bloqueadas para monitoreo de seguridad.

Validación de rutas de archivo (prevenir path traversal)

Al aceptar rutas de archivo proporcionadas por el usuario (ej., para archivos de fuentes, imágenes o destinos de salida):

php
use YeeeFang\TcpdfNext\Security\ResourcePolicy;

$resourcePolicy = ResourcePolicy::strict()
    ->allowLocalDirectory('/app/public/assets/')
    ->allowLocalDirectory('/app/storage/fonts/')
    ->denyAllRemote();

$pdf = PdfDocument::create()
    ->setResourcePolicy($resourcePolicy)
    ->build();

Reglas:

  • Nunca concatenes entrada de usuario directamente en rutas de archivo.
  • Resuelve las rutas a forma canónica absoluta y valida contra directorios permitidos.
  • Rechaza rutas que contengan .., bytes nulos o caracteres no imprimibles.
  • Usa ResourcePolicy::strict() en producción — deniega todo acceso por defecto.

Seguridad de despliegue (Docker y permisos de archivos)

Configuración Docker

dockerfile
FROM php:8.5-fpm-alpine

# Ejecutar como usuario no root
RUN addgroup -S tcpdf && adduser -S tcpdf -G tcpdf
USER tcpdf

# Deshabilitar funciones PHP peligrosas
RUN echo "disable_functions = exec,passthru,shell_exec,system,proc_open,popen" \
    >> /usr/local/etc/php/conf.d/security.ini

# Sistema de archivos de solo lectura (montar volúmenes escribibles explícitamente)
# docker run --read-only --tmpfs /tmp ...

Permisos de archivos

bash
# Directorio de certificados: legible solo por el usuario del servidor web
chown -R www-data:www-data /etc/tcpdf-next/certs/
chmod 700 /etc/tcpdf-next/certs/
chmod 600 /etc/tcpdf-next/certs/*.p12
chmod 600 /etc/tcpdf-next/certs/*.pem

# Directorio de salida: escribible solo por el usuario del servidor web
chown -R www-data:www-data /var/lib/tcpdf-next/output/
chmod 700 /var/lib/tcpdf-next/output/

# Directorio temporal: escribible, no legible por todos
chown -R www-data:www-data /tmp/tcpdf-next/
chmod 700 /tmp/tcpdf-next/

Content Security Policy para PDFs mostrados en navegador

Al servir PDFs inline en el navegador, configura los headers HTTP apropiados para prevenir ataques de embedding:

php
return response($pdf->toString(), 200, [
    'Content-Type' => 'application/pdf',
    'Content-Disposition' => 'inline; filename="document.pdf"',
    'Content-Security-Policy' => "default-src 'none'; plugin-types application/pdf",
    'X-Content-Type-Options' => 'nosniff',
    'X-Frame-Options' => 'DENY',
    'Cache-Control' => 'no-store, no-cache, must-revalidate',
]);

Para PDFs que contienen datos sensibles, prefiere Content-Disposition: attachment para forzar la descarga en lugar del renderizado en el navegador.

Recomendaciones de audit logging

Configura audit logging integral para todas las operaciones de PDF sensibles a la seguridad:

php
use YeeeFang\TcpdfNext\Security\AuditLogger;

AuditLogger::configure([
    'channel' => 'tcpdf-security',
    'log_signing' => true,
    'log_encryption' => true,
    'log_validation' => true,
    'log_key_access' => true,
    'log_tsa_requests' => true,
    'log_resource_access' => true,   // Registrar carga de imágenes/fuentes
    'log_blocked_requests' => true,  // Registrar bloqueos SSRF
    'redact_sensitive' => true,      // Redactar contraseñas/claves de los logs
]);

Monitorear:

  • Intentos fallidos de validación de firma (posible manipulación del documento).
  • Advertencias de expiración de certificados.
  • Fallos de comunicación con TSA.
  • Volumen de firma inusual (posible compromiso de clave).
  • Intentos SSRF bloqueados (posible sondeo de ataque).
  • Carga de recursos desde rutas inesperadas.

Principio de mínimo privilegio para permisos PDF

Al configurar permisos de documentos PDF, otorga solo el acceso mínimo requerido:

php
use YeeeFang\TcpdfNext\Encryption\Permissions;

// RESTRICTIVO: Documento de solo lectura
$pdf->setEncryption()
    ->setPermissions(Permissions::ACCESSIBILITY)  // Solo acceso para lectores de pantalla
    ->setUserPassword('reader')
    ->setOwnerPassword($strongOwnerPassword)
    ->apply();

// MODERADO: Documento imprimible
$pdf->setEncryption()
    ->setPermissions(
        Permissions::PRINT_HIGH_QUALITY
        | Permissions::ACCESSIBILITY
    )
    ->apply();

// EVITAR: Otorgar todos los permisos anula el propósito del cifrado
// Permissions::ALL está disponible pero raramente debería usarse

Pautas de permisos:

  • Nunca otorgues MODIFY_CONTENTS a menos que el destinatario necesite editar el documento.
  • Siempre otorga ACCESSIBILITY para compatibilidad con lectores de pantalla (requisito legal en muchas jurisdicciones).
  • Usa PRINT_HIGH_QUALITY en lugar de PRINT_LOW_QUALITY a menos que tengas una razón específica.
  • Documenta la justificación de cada permiso otorgado en tu código.

Lectura adicional

Distribuido bajo la licencia LGPL-3.0-or-later.