Salta ai contenuti

Sicurezza e operatività

Questo ponte invia l’HTML attraverso un confine di rete verso un motore browser. Questa pagina documenta ogni controllo che difende quel confine, descritto a partire dal codice sorgente. Quando un controllo cita uno standard, la citazione è quella dichiarata dal docblock del codice stesso. Questa pagina ribadisce l’affermazione del codice e non ricostruisce la formulazione normativa.

I docblock del pacchetto indicano le minacce da cui si difende:

  • XSS-to-PDF — markup ostile eseguito durante il rendering.
  • SSRF — markup o un URL di destinazione che indirizza una richiesta verso un indirizzo interno.
  • Esaurimento delle risorse — input sovradimensionato o una bomba di decompressione.
  • DNS rebinding — un nome host che supera la validazione, poi risolve a un indirizzo privato al momento della connessione.
  • Intercettazione TLS on-path — un certificato sostituito sul percorso verso il Worker.

Ciascuna è mitigata da un controllo specifico e verificabile più sotto.

Controlli sull’input (prima che la richiesta lasci PHP)

Sezione intitolata “Controlli sull’input (prima che la richiesta lasci PHP)”

CloudflareSecurityPolicy::validate() viene eseguito prima della costruzione di qualsiasi richiesta:

ControlloComportamentoOrigine del limite
Limite di dimensioneRifiuta HTML più grande di maxHtmlSizeCloudflareRendererConfig, predefinito 5000000 byte
Protezione dalla bomba di decompressione Base64Stima la dimensione decodificata di ogni URI data:…;base64,…; rifiuta valori pari o superiori al tetto massimoMAX_DATA_URI_BYTES = 13631488
Divieto di meta-refreshRifiuta qualsiasi <meta http-equiv="refresh">, senza distinzione tra maiuscole e minuscoleregex in CloudflareSecurityPolicy

Una violazione solleva RuntimeException con un messaggio che indica il valore incriminato e il limite. Il divieto di meta-refresh esiste perché una direttiva di refresh può avviare una navigazione dall’interno della pagina eseguita dal Worker — un vettore SSRF che risiede nel contenuto, non nell’URL.

La policy di sicurezza HTML di nextpdf/core (HtmlSecurityPolicyInterface, predefinita DefaultHtmlSecurityPolicy) opera a livello di parsing ed è complementare ai controlli di trasporto sopra indicati. Recuperarla con getHtmlSecurityPolicy(). Iniettarne una personalizzata attraverso il costruttore.

Controlli sulla destinazione (SSRF e DNS rebinding)

Sezione intitolata “Controlli sulla destinazione (SSRF e DNS rebinding)”

CloudflareSecurityPolicy::validateWorkerUrl():

  1. Rifiuta un URL che non si analizza o privo di schema/host (Invalid Worker URL).
  2. Rifiuta qualsiasi schema non HTTPS (Worker URL must use HTTPS).
  3. Per un host che è un IP letterale, rifiuta gli intervalli privati o riservati usando il FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE di PHP. In pratica vengono rifiutati lo spazio privato RFC 1918, il loopback e gli indirizzi link-local RFC 3927 — i test verificano esplicitamente il rifiuto di 192.168.x, 127.0.0.1 e 169.254.x. L’appartenenza all’intervallo è decisa dall’estensione filter di PHP, non da una clausola stabilita da questo pacchetto; RFC 1918 e RFC 3927 sono nominati qui in modo descrittivo come le definizioni ben note di quegli intervalli.
  4. Per un nome host, risolve tutti i record A e AAAA tramite dns_get_record() (non gethostbyname(), che restituisce solo la prima risposta) e rifiuta se uno qualsiasi degli indirizzi risolti è privato o riservato.

La risoluzione di tutti i record è deliberata ed è documentata nel docblock della classe come difesa contro un host che restituisce record diversi, dove una ricerca a record singolo potrebbe scegliere quello pubblico mentre la connessione successiva potrebbe sceglierne uno privato. Questo corrisponde all’OWASP SSRF Prevention Cheat Sheet, che indica a un’applicazione di recuperare tutti gli indirizzi IP dietro il nome di dominio (record A e AAAA) e di applicare il controllo sugli indirizzi non pubblici a ciascuno di essi.

validateWorkerUrl() restituisce l’insieme di IP verificati. Il renderer chiama quindi assertPinsStillValid() immediatamente prima dell’invio. Questa chiamata risolve di nuovo l’host e rifiuta se compare un nuovo IP dopo la validazione (Worker URL DNS answer changed since validation — possible DNS rebinding attack). Questo chiude la finestra time-of-check / time-of-use tra validazione e connessione.

Quando è presente un insieme di IP verificati o un insieme di pin SPKI e viene fornito un ResponseFactory PSR-17, il renderer usa Transport\PinnedCurlTransport invece del client PSR-18 iniettato. Il trasporto applica, a livello di handle cURL:

  • DNS con pinCURLOPT_RESOLVE vincola host:porta all’insieme di IP verificati, così libcurl non esegue la propria ricerca al momento della connessione. Questo fa sì che il controllo DNS in userland vincoli effettivamente la connessione; senza di esso libcurl potrebbe risolvere un indirizzo diverso.
  • Pinning della chiave pubblica TLSCURLOPT_PINNEDPUBLICKEY è impostato dall’insieme di pin combinato. Questo segue l’RFC 7469 §2.6: una connessione con pin è accettata quando l’insieme di impronte SPKI presentate dal server interseca l’insieme di pin configurato, e il fallimento della validazione del pin è trattato come non recuperabile. Le stringhe di pin sono normalizzate da sha256/<base64> alla forma sha256//<base64> di cURL; un pin malformato solleva InvalidSpkiPinException.
  • Verifica TLS attivaCURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2.
  • Nessun reindirizzamento automaticoCURLOPT_FOLLOWLOCATION => false, CURLOPT_MAXREDIRS => 0. Una 3xx viene esposta al livello di policy invece di essere seguita da libcurl verso un host non verificato. Il docblock della classe afferma che questa è una scelta deliberata affinché i reindirizzamenti siano ri-validati, non seguiti silenziosamente.
  • Timeout rigidoCURLOPT_TIMEOUT è impostato da renderTimeout (predefinito 30 secondi).

Un errore cURL o un corpo non di tipo stringa solleva CloudflareRenderException con il numero e il messaggio dell’errore cURL.

La configurazione comprende pinnedPublicKeys e un campo backupPublicKeys separato. L’RFC 7469 §2.5 descrive un pin di backup — un’impronta per una coppia di chiavi secondaria, non ancora distribuita e mantenuta offline — come il modo principale per recuperare da un fallimento involontario della validazione del pin. Mantenere almeno un pin di backup, così che la rotazione del certificato non renda inutilizzabile l’endpoint, segue quella guida. Il campo separato consente di validare una rotazione in modo indipendente. Operativamente:

  • Applicare il pin all’SPKI della foglia o di un intermedio di cui si controlla la rotazione.
  • Configurare sempre un pin di backup per il certificato successivo prima di ruotare.
  • Un insieme di pin vuoto disabilita il pinning; usarlo solo con una catena di certificati stabile e nota. Il pinning è opzionale tramite configurazione.
  • La richiesta al Worker include Authorization: Bearer <apiToken>. apiToken è #[SensitiveParameter], quindi viene oscurato dalle tracce dello stack. La sonda di raggiungibilità invia la stessa intestazione bearer su un HEAD HTTP.
  • Le chiavi di accesso R2 (accessKeyId, secretAccessKey) sono #[SensitiveParameter] e vengono usate solo per derivare la chiave di firma AWS Signature V4.
  • ApiKeyValidator confronta le chiavi con hash_equals() (a tempo costante) e supporta l’archiviazione di chiavi hashate con SHA-256 tramite validateHashed().
  • Gli oggetti di configurazione sono final readonly — un segreto impostato una volta non può essere mutato.
  • Recuperare i segreti da variabili d’ambiente o da un gestore di segreti. Non eseguirne mai il commit. Il pacchetto segue la più ampia linea di base di sicurezza di NextPDF: PHPStan Level 10, declare(strict_types=1) su ogni file, nessun eval()/exec(), GitHub Actions fissate al SHA.
  • Non dichiara alcun limite della piattaforma Cloudflare (tempo CPU del Worker, memoria, tetto del corpo della richiesta o numero di sotto-richieste). Gli unici limiti di dimensione e tempo dichiarati da questa documentazione sono quelli applicati dal pacchetto stesso, elencati sopra e in /integrations/cloudflare/configuration/. Per i limiti della piattaforma, consultare la documentazione ufficiale di Cloudflare e l’implementazione del proprio Worker.
  • Non firma i PDF e non avanza alcuna affermazione di conformità della firma. Quando sono richieste firme, eseguire il rendering qui, poi firmare con il motore. NextPDF Pro fornisce solo la firma PAdES B-B; i profili di validazione a lungo termine sono una capacità Enterprise e sono fuori ambito per questo ponte.
  • Non certifica, non garantisce e non rende la pipeline «a prova di manomissione». Implementa i controlli specifici e verificabili nel sorgente descritti in questa pagina e nulla oltre a essi.
SintomoPrimo controllo
Worker URL must use HTTPSLo schema del workerUrl configurato.
private or reserved IPI record DNS del nome host del Worker; un record risolto nello spazio RFC 1918 / loopback / RFC 3927.
DNS answer changed since validationInstabilità del DNS o un tentativo di rebinding; risolvere nuovamente e ispezionare l’insieme dei record.
cURL transport errorIl percorso di rete, la catena TLS e — se sono impostati pin — se l’SPKI del certificato servito è ancora nell’insieme di pin.
Il rendering fallisce subito dopo una rotazione del certificatoUn insieme di pin senza un pin di backup corrispondente. Aggiungere il nuovo SPKI come backup prima di ruotare.
is not installed / no LocalRendererFactoryInterfaceFallback abilitato ma nessuna factory collegata, oppure nextpdf/artisan assente.
Rifiuti del rate limit incoerenti tra i nodiIl limitatore in memoria è per-processo; mettere davanti al limitatore uno store condiviso.

Segnalare le vulnerabilità tramite i GitHub Security Advisories o il contatto di sicurezza nel SECURITY.md del repository. Non aprire segnalazioni di sicurezza come issue pubbliche di GitHub.

  • /integrations/cloudflare/overview/ — perché il pacchetto è modellato attorno a questo confine.
  • /integrations/cloudflare/configuration/ — campi per l’insieme di pin e i limiti.
  • /integrations/cloudflare/troubleshooting/ — mappatura completa tra fallimenti ed eccezioni.