Segurança e operações
Visão geral
Seção intitulada “Visão geral”Esta ponte envia o Hypertext Markup Language (HTML) por uma fronteira de rede até um motor de navegador. Esta página documenta os controles que protegem essa fronteira, usando o código-fonte como fonte da verdade. Quando um controle cita um padrão, a citação é aquela declarada no docblock do código. Esta página reafirma a asserção do código; ela não reconstrói o texto normativo.
Modelo de ameaças
Seção intitulada “Modelo de ameaças”Os docblocks do pacote nomeiam as ameaças contra as quais ele se defende:
- XSS-to-PDF — Cross-site scripting (XSS) por meio de markup hostil executado durante a renderização do Portable Document Format (PDF).
- SSRF — Server-side request forgery (SSRF) causado por markup ou por um Uniform Resource Locator (URL) de destino que envia uma requisição para um endereço interno.
- Esgotamento de recursos — Entrada superdimensionada ou uma bomba de descompressão.
- DNS rebinding — Rebinding de Domain Name System (DNS), em que um nome de host passa na validação e depois resolve para um endereço privado no momento da conexão.
- Interceptação de TLS on-path — Interceptação on-path de Transport Layer Security (TLS) por meio de um certificado substituído no caminho até o Worker.
Cada ameaça tem um controle específico e testável abaixo.
Controles de entrada (antes de a requisição sair do PHP)
Seção intitulada “Controles de entrada (antes de a requisição sair do PHP)”CloudflareSecurityPolicy::validate() é executado antes de qualquer requisição ser construída:
| Controle | Comportamento | Origem do limite |
|---|---|---|
| Limite de tamanho | Rejeita HTML maior que maxHtmlSize | CloudflareRendererConfig, padrão 5000000 bytes |
| Proteção contra bomba de descompressão em Base64 | Estima o tamanho decodificado de cada URI data:…;base64,…; rejeita valores iguais ou superiores ao teto | MAX_DATA_URI_BYTES = 13631488 |
| Bloqueio de meta-refresh | Rejeita qualquer <meta http-equiv="refresh">, sem diferenciar maiúsculas de minúsculas | regex em CloudflareSecurityPolicy |
Uma violação lança RuntimeException com uma mensagem que identifica o valor infrator e o limite. O bloqueio de meta-refresh existe porque uma diretiva de refresh pode iniciar uma navegação dentro da página renderizada pelo Worker — um vetor de SSRF que reside no conteúdo, não na URL.
A política de segurança de HTML de nextpdf/core (HtmlSecurityPolicyInterface, padrão DefaultHtmlSecurityPolicy) é executada na camada de parsing e complementa as verificações da camada de transporte acima. Recupere-a com getHtmlSecurityPolicy(). Injete uma política personalizada pelo construtor.
Controles de destino (SSRF e DNS rebinding)
Seção intitulada “Controles de destino (SSRF e DNS rebinding)”CloudflareSecurityPolicy::validateWorkerUrl():
- Rejeita uma URL que falha ao ser analisada ou que não tem scheme/host (
Invalid Worker URL). - Rejeita qualquer scheme diferente de HTTPS (
Worker URL must use HTTPS). - Para um host com IP literal, rejeita faixas privadas ou reservadas com
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGEdo PHP. Na prática, isso rejeita o espaço privado da RFC 1918, loopback e os endereços link-local da RFC 3927. Os testes cobrem explicitamente as rejeições de192.168.x,127.0.0.1e169.254.x. A extensão filter do PHP decide a pertinência à faixa; este pacote não vincula essa decisão a uma cláusula. A RFC 1918 e a RFC 3927 são citadas aqui de forma descritiva, como as definições conhecidas dessas faixas. - Para um nome de host, resolve todos os registros A e AAAA com
dns_get_record()(nãogethostbyname(), que retorna apenas a primeira resposta) e rejeita o host se qualquer endereço resolvido for privado ou reservado.
O uso da resolução de todos os registros é deliberado. O docblock da classe documenta isso como uma defesa contra um host que retorna vários registros, em que uma consulta a um único registro poderia escolher o endereço público enquanto a conexão posterior escolhe um privado. Isso está alinhado ao OWASP SSRF Prevention Cheat Sheet: resolver as respostas A e AAAA do domínio e aplicar a verificação de endereço não público a todo o conjunto de resultados.
validateWorkerUrl() retorna o conjunto de IPs verificados. Imediatamente antes do envio, o renderizador chama assertPinsStillValid(). Essa chamada resolve o host novamente e rejeita um IP recém-visto (Worker URL DNS answer changed since validation — possible DNS rebinding attack). Isso fecha a janela de time-of-check / time-of-use entre a validação e a conexão.
Controles de transporte (PinnedCurlTransport)
Seção intitulada “Controles de transporte (PinnedCurlTransport)”Quando um conjunto de IPs verificados ou um conjunto de pins de Subject Public Key Info (SPKI) está presente e um ResponseFactory de PHP Standards Recommendation 17 (PSR-17) foi fornecido, o renderizador usa Transport\PinnedCurlTransport em vez do cliente PHP Standards Recommendation 18 (PSR-18) injetado. O transporte aplica estes controles na camada do handle do cURL:
- DNS fixado —
CURLOPT_RESOLVEvincula o host:port ao conjunto de IPs verificados, de modo que o libcurl não faça a própria consulta no momento da conexão. Esse vínculo faz com que a verificação de DNS em userland se aplique à conexão real; sem ele, o libcurl poderia resolver um endereço diferente. - Fixação de chave pública TLS —
CURLOPT_PINNEDPUBLICKEYé definido a partir do conjunto de pins combinado. Isso segue a RFC 7469 §2.6: uma conexão fixada é aceita quando o conjunto de fingerprints SPKI apresentado pelo servidor intersecta o conjunto de pins configurado, e uma falha de validação de pin é irrecuperável. As strings de pin são normalizadas desha256/<base64>para a formasha256//<base64>do cURL; um pin malformado lançaInvalidSpkiPinException. - Verificação TLS ativada —
CURLOPT_SSL_VERIFYPEER => true,CURLOPT_SSL_VERIFYHOST => 2. - Sem redirecionamentos automáticos —
CURLOPT_FOLLOWLOCATION => false,CURLOPT_MAXREDIRS => 0. Uma resposta 3xx é exposta à camada de política em vez de ser seguida pelo libcurl até um host não verificado. O docblock da classe afirma que isso é deliberado, para que os redirecionamentos sejam revalidados em vez de seguidos em silêncio. - Timeout rígido —
CURLOPT_TIMEOUTé definido a partir derenderTimeout(padrão30segundos).
Um erro cURL ou um corpo que não seja string lança CloudflareRenderException com o número e a mensagem do erro cURL.
Orientação operacional de fixação
Seção intitulada “Orientação operacional de fixação”A configuração carrega pinnedPublicKeys e um backupPublicKeys separado. A RFC 7469 §2.5 descreve um pin de backup como uma fingerprint de um par de chaves secundário, ainda não implantado, mantido offline, e o trata como o principal caminho de recuperação para uma falha inadvertida de validação de pin. Mantenha pelo menos um pin de backup para que a rotação de certificado não inutilize o endpoint. O campo separado permite que você valide uma rotação de forma independente. Operacionalmente:
- Fixe o SPKI do certificado folha ou de um intermediário cuja rotação você controla.
- Sempre configure um pin de backup para o próximo certificado antes de rotacionar.
- Um conjunto de pins vazio desativa a fixação; use isso apenas com uma cadeia de certificados estável e conhecida. A fixação é opcional, ativada por configuração.
Autenticação e tratamento de segredos
Seção intitulada “Autenticação e tratamento de segredos”- A requisição ao Worker carrega
Authorization: Bearer <apiToken>.apiTokené#[SensitiveParameter], de modo que stack traces o ocultem. A sondagem de acessibilidade envia o mesmo cabeçalho bearer em umHEADde Hypertext Transfer Protocol (HTTP). - As chaves de acesso do Cloudflare R2 (
accessKeyId,secretAccessKey) são#[SensitiveParameter]e usadas apenas para derivar a chave de assinatura Amazon Web Services (AWS) Signature V4. ApiKeyValidatorcompara chaves comhash_equals()(seguro contra timing) e suporta armazenamento de chaves com hash Secure Hash Algorithm 256 (SHA-256) viavalidateHashed().- Os objetos de configuração são
final readonly— um segredo definido uma vez não pode ser modificado. - Obtenha os segredos a partir de variáveis de ambiente ou de um gerenciador de segredos. Nunca faça commit deles. O pacote segue a linha de base de segurança mais ampla do NextPDF: PHPStan Level 10,
declare(strict_types=1)em todos os arquivos, semeval()/exec(), GitHub Actions fixadas por SHA.
O que este pacote não afirma
Seção intitulada “O que este pacote não afirma”- Ele não declara nenhum limite da plataforma Cloudflare (tempo de CPU do Worker, memória, teto do corpo da requisição ou contagem de subrequisições). Os únicos limites de tamanho e tempo que esta documentação declara são aqueles que o próprio pacote aplica, listados acima e em /integrations/cloudflare/configuration/. Para os limites da plataforma, consulte a documentação oficial da Cloudflare e a própria implementação do Worker.
- Ele não assina PDFs e não faz nenhuma afirmação de conformidade de assinatura. Quando assinaturas forem necessárias, renderize aqui e depois assine com o engine. O NextPDF Pro fornece apenas assinatura PDF Advanced Electronic Signatures (PAdES) B-B; os perfis de validação de longo prazo são um recurso Enterprise e ficam fora do escopo desta ponte.
- Ele não certifica, garante nem torna o pipeline “à prova de adulteração”. Ele implementa apenas os controles específicos, verificáveis no código-fonte, descritos nesta página.
Runbook operacional
Seção intitulada “Runbook operacional”| Sintoma | Primeira verificação |
|---|---|
Worker URL must use HTTPS | Verifique o scheme de workerUrl configurado. |
private or reserved IP | Os registros DNS do nome de host do Worker; procure um registro que resolva para o espaço da RFC 1918 / loopback / RFC 3927. |
DNS answer changed since validation | Instabilidade de DNS ou uma tentativa de rebinding; resolva novamente e inspecione o conjunto completo de registros. |
cURL transport error | O caminho de rede, a cadeia TLS e — se houver pins definidos — se o SPKI do certificado servido ainda está no conjunto de pins. |
| As renderizações falham logo após uma rotação de certificado | Um conjunto de pins sem um pin de backup correspondente. Adicione o novo SPKI como backup antes de rotacionar. |
is not installed / no LocalRendererFactoryInterface | O fallback está habilitado, mas nenhuma factory está conectada, ou nextpdf/artisan está ausente. |
| Negações por limite de taxa inconsistentes entre os nós | O limitador em memória é por processo; coloque-o atrás de um store compartilhado. |
Relato de incidentes
Seção intitulada “Relato de incidentes”Relate vulnerabilidades por meio de GitHub Security Advisories ou do contato de segurança no SECURITY.md do repositório. Não registre problemas de segurança como issues públicas no GitHub.
Veja também
Seção intitulada “Veja também”- /integrations/cloudflare/overview/ — por que este pacote é moldado em torno da fronteira.
- /integrations/cloudflare/configuration/ — campos de conjunto de pins e limites.
- /integrations/cloudflare/troubleshooting/ — mapeamento completo de falha para exceção.