Sicurezza e operatività
In sintesi
Sezione intitolata “In sintesi”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.
Modello di minaccia
Sezione intitolata “Modello di minaccia”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:
| Controllo | Comportamento | Origine del limite |
|---|---|---|
| Limite di dimensione | Rifiuta HTML più grande di maxHtmlSize | CloudflareRendererConfig, predefinito 5000000 byte |
| Protezione dalla bomba di decompressione Base64 | Stima la dimensione decodificata di ogni URI data:…;base64,…; rifiuta valori pari o superiori al tetto massimo | MAX_DATA_URI_BYTES = 13631488 |
| Divieto di meta-refresh | Rifiuta qualsiasi <meta http-equiv="refresh">, senza distinzione tra maiuscole e minuscole | regex 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():
- Rifiuta un URL che non si analizza o privo di schema/host (
Invalid Worker URL). - Rifiuta qualsiasi schema non HTTPS (
Worker URL must use HTTPS). - Per un host che è un IP letterale, rifiuta gli intervalli privati o riservati usando
il
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGEdi 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 di192.168.x,127.0.0.1e169.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. - Per un nome host, risolve tutti i record A e AAAA tramite
dns_get_record()(nongethostbyname(), 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.
Controlli sul trasporto (PinnedCurlTransport)
Sezione intitolata “Controlli sul trasporto (PinnedCurlTransport)”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 pin —
CURLOPT_RESOLVEvincola 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 TLS —
CURLOPT_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 dasha256/<base64>alla formasha256//<base64>di cURL; un pin malformato sollevaInvalidSpkiPinException. - Verifica TLS attiva —
CURLOPT_SSL_VERIFYPEER => true,CURLOPT_SSL_VERIFYHOST => 2. - Nessun reindirizzamento automatico —
CURLOPT_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 rigido —
CURLOPT_TIMEOUTè impostato darenderTimeout(predefinito30secondi).
Un errore cURL o un corpo non di tipo stringa solleva CloudflareRenderException con il numero e il messaggio dell’errore cURL.
Guida operativa al pinning
Sezione intitolata “Guida operativa al pinning”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.
Autenticazione e gestione dei segreti
Sezione intitolata “Autenticazione e gestione dei segreti”- 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 unHEADHTTP. - Le chiavi di accesso R2 (
accessKeyId,secretAccessKey) sono#[SensitiveParameter]e vengono usate solo per derivare la chiave di firma AWS Signature V4. ApiKeyValidatorconfronta le chiavi conhash_equals()(a tempo costante) e supporta l’archiviazione di chiavi hashate con SHA-256 tramitevalidateHashed().- 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, nessuneval()/exec(), GitHub Actions fissate al SHA.
Ciò che questo pacchetto non asserisce
Sezione intitolata “Ciò che questo pacchetto non asserisce”- 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.
Runbook operativo
Sezione intitolata “Runbook operativo”| Sintomo | Primo controllo |
|---|---|
Worker URL must use HTTPS | Lo schema del workerUrl configurato. |
private or reserved IP | I record DNS del nome host del Worker; un record risolto nello spazio RFC 1918 / loopback / RFC 3927. |
DNS answer changed since validation | Instabilità del DNS o un tentativo di rebinding; risolvere nuovamente e ispezionare l’insieme dei record. |
cURL transport error | Il 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 certificato | Un insieme di pin senza un pin di backup corrispondente. Aggiungere il nuovo SPKI come backup prima di ruotare. |
is not installed / no LocalRendererFactoryInterface | Fallback abilitato ma nessuna factory collegata, oppure nextpdf/artisan assente. |
| Rifiuti del rate limit incoerenti tra i nodi | Il limitatore in memoria è per-processo; mettere davanti al limitatore uno store condiviso. |
Segnalazione di incidenti
Sezione intitolata “Segnalazione di incidenti”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.
Vedere anche
Sezione intitolata “Vedere anche”- /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.