Безопасность и эксплуатация
Этот мост отправляет ваш HTML через сетевую границу в браузерный движок. На этой странице описаны средства контроля, защищающие эту границу; источником истины остаётся исходный код. Если средство контроля ссылается на стандарт, используется та же ссылка, что указана в docblock кода. Страница повторяет утверждения кода, но не воспроизводит нормативные формулировки.
Модель угроз
Заголовок раздела «Модель угроз»В docblock пакета перечислены угрозы, от которых он защищает:
- XSS-to-PDF — межсайтовый скриптинг (XSS) через вредоносную разметку, выполняемую во время отрисовки формата переносимых документов (PDF).
- SSRF — подделка запросов на стороне сервера (SSRF), вызванная разметкой или целевым унифицированным указателем ресурса (URL), из-за которого запрос уходит на внутренний адрес.
- Исчерпание ресурсов — слишком большой ввод или бомба декомпрессии.
- Перепривязка DNS — перепривязка системы доменных имён (DNS), при которой имя узла проходит проверку, а во время подключения разрешается в частный адрес.
- Перехват TLS на пути следования — перехват защиты транспортного уровня (TLS) через подменённый сертификат на маршруте к Worker.
Для каждой угрозы ниже указано конкретное проверяемое средство контроля.
Контроль ввода (до того как запрос покинет PHP)
Заголовок раздела «Контроль ввода (до того как запрос покинет PHP)»CloudflareSecurityPolicy::validate() выполняется до формирования любого запроса:
| Средство контроля | Поведение | Источник ограничения |
|---|---|---|
| Ограничение размера | Отклоняет HTML больше, чем maxHtmlSize | CloudflareRendererConfig, по умолчанию 5000000 байт |
| Защита от бомбы декомпрессии в Base64 | Оценивает декодированный размер каждого URI data:…;base64,…; отклоняет значения, равные верхнему пределу или превышающие его | MAX_DATA_URI_BYTES = 13631488 |
| Запрет meta-refresh | Отклоняет любой <meta http-equiv="refresh"> без учёта регистра | регулярное выражение в CloudflareSecurityPolicy |
При нарушении возникает RuntimeException с сообщением, где указаны нарушающее значение и ограничение. Запрет meta-refresh нужен потому, что директива refresh может запустить навигацию внутри страницы, которую отрисовывает Worker: это вектор SSRF в содержимом, а не в URL.
Политика безопасности HTML из nextpdf/core (HtmlSecurityPolicyInterface, по умолчанию DefaultHtmlSecurityPolicy) применяется на уровне разбора и дополняет проверки транспортного уровня, описанные выше. Получите её через getHtmlSecurityPolicy(). Собственную политику передайте через конструктор.
Контроль назначения (SSRF и перепривязка DNS)
Заголовок раздела «Контроль назначения (SSRF и перепривязка DNS)»CloudflareSecurityPolicy::validateWorkerUrl():
- Отклоняет URL, который не удаётся разобрать или в котором нет scheme/host (
Invalid Worker URL). - Отклоняет любую схему, кроме HTTPS (
Worker URL must use HTTPS). - Для узла, заданного IP-литералом, отклоняет частные или зарезервированные диапазоны с помощью
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGEиз PHP. На практике это отклоняет частное пространство RFC 1918, петлевые адреса и канально-локальные адреса RFC 3927. Тесты явно проверяют отклонение192.168.x,127.0.0.1и169.254.x. Принадлежность к диапазону определяет расширение filter в PHP; пакет не привязывает это решение к какой-либо статье. RFC 1918 и RFC 3927 приведены здесь описательно, как общеизвестные определения этих диапазонов. - Для имени узла разрешает все записи A и AAAA с помощью
dns_get_record()(а неgethostbyname(), который возвращает только первый ответ) и отклоняет узел, если любой разрешённый адрес является частным или зарезервированным.
Разрешение всех записей используется намеренно. В docblock класса это описано как защита от узла, который возвращает несколько записей: при разрешении одной записи может быть выбран публичный адрес, а при последующем подключении — частный. Это соответствует OWASP SSRF Prevention Cheat Sheet: разрешать для домена и записи A, и записи AAAA, а проверку на непубличный адрес применять ко всему набору результатов.
validateWorkerUrl() возвращает проверенный набор IP-адресов. Непосредственно перед отправкой средство отрисовки вызывает assertPinsStillValid(). Этот вызов повторно разрешает узел и отклоняет вновь появившийся IP-адрес (Worker URL DNS answer changed since validation — possible DNS rebinding attack). Так закрывается окно между временем проверки и временем использования (time-of-check / time-of-use) между валидацией и подключением.
Контроль транспорта (PinnedCurlTransport)
Заголовок раздела «Контроль транспорта (PinnedCurlTransport)»Когда есть проверенный набор IP-адресов или набор закреплённых ключей SPKI (Subject Public Key Info) и передана фабрика ResponseFactory рекомендации PHP Standards Recommendation 17 (PSR-17), средство отрисовки использует Transport\PinnedCurlTransport вместо внедрённого клиента рекомендации PHP Standards Recommendation 18 (PSR-18). Транспорт применяет эти средства контроля на уровне дескриптора cURL:
- Закреплённый DNS —
CURLOPT_RESOLVEпривязывает host:port к проверенному набору IP-адресов, поэтому libcurl не выполняет собственный поиск во время подключения. Благодаря этой привязке пользовательская проверка DNS применяется к фактическому подключению; без неё libcurl мог бы разрешить другой адрес. - Закрепление открытого ключа TLS —
CURLOPT_PINNEDPUBLICKEYустанавливается из объединённого набора закреплённых ключей. Это соответствует RFC 7469 §2.6: закреплённое подключение принимается, когда набор отпечатков SPKI, предъявленный сервером, пересекается с настроенным набором закреплённых ключей, а сбой проверки закрепления неустраним. Строки закрепления нормализуются из формыsha256/<base64>в форму cURLsha256//<base64>; недопустимый закреплённый ключ вызываетInvalidSpkiPinException. - Проверка TLS включена —
CURLOPT_SSL_VERIFYPEER => true,CURLOPT_SSL_VERIFYHOST => 2. - Без автоматических перенаправлений —
CURLOPT_FOLLOWLOCATION => false,CURLOPT_MAXREDIRS => 0. Ответ 3xx передаётся на уровень политики; libcurl не переходит на непроверенный узел. В docblock класса указано, что это сделано намеренно: перенаправления проверяются повторно, а не выполняются молча. - Жёсткий тайм-аут —
CURLOPT_TIMEOUTустанавливается изrenderTimeout(по умолчанию30секунд).
Ошибка cURL или тело не строкового типа вызывает CloudflareRenderException с номером и сообщением ошибки cURL.
Рекомендации по эксплуатации закрепления
Заголовок раздела «Рекомендации по эксплуатации закрепления»Конфигурация содержит pinnedPublicKeys и отдельное backupPublicKeys. RFC 7469 §2.5 описывает резервный закреплённый ключ как отпечаток вторичной, ещё не развёрнутой пары ключей, которая хранится автономно, и рассматривает его как основной способ восстановления при непреднамеренном сбое проверки закрепления. Сохраняйте хотя бы один резервный закреплённый ключ, чтобы ротация сертификата не вывела конечную точку из строя. Отдельное поле позволяет проверять ротацию независимо. На практике:
- Закрепляйте SPKI листового сертификата или промежуточного сертификата, ротацию которого вы контролируете.
- Перед ротацией всегда настраивайте резервный закреплённый ключ для следующего сертификата.
- Пустой набор закреплённых ключей отключает закрепление; используйте это только со стабильной и известной цепочкой сертификатов. Закрепление включается через конфигурацию.
Аутентификация и обращение с секретами
Заголовок раздела «Аутентификация и обращение с секретами»- Запрос к Worker содержит
Authorization: Bearer <apiToken>.apiTokenпомечен#[SensitiveParameter], поэтому трассировки стека его скрывают. Проверка доступности отправляет тот же заголовок Bearer в запросе протокола передачи гипертекста (HTTP)HEAD. - Ключи доступа Cloudflare R2 (
accessKeyId,secretAccessKey) помечены#[SensitiveParameter]и используются только для получения ключа подписи Amazon Web Services (AWS) Signature V4. ApiKeyValidatorсравнивает ключи с помощьюhash_equals()(устойчиво к атакам по времени) и поддерживает хранение хешированных ключей по алгоритму безопасного хеширования 256 (SHA-256) черезvalidateHashed().- Объекты конфигурации помечены
final readonly— однажды заданный секрет нельзя изменить. - Берите секреты из переменных окружения или менеджера секретов. Никогда не фиксируйте их в репозитории. Пакет следует общей базовой линии безопасности NextPDF: PHPStan Level 10,
declare(strict_types=1)в каждом файле, безeval()/exec(), GitHub Actions закреплены по SHA.
Что этот пакет не утверждает
Заголовок раздела «Что этот пакет не утверждает»- Он не указывает платформенные ограничения Cloudflare (время ЦП Worker, память, верхний предел тела запроса или количество подзапросов). Единственные ограничения размера и времени, указанные в этой документации, — это те, которые пакет применяет сам, перечисленные выше и в /integrations/cloudflare/configuration/. По платформенным ограничениям обращайтесь к официальной документации Cloudflare и реализации вашего собственного Worker.
- Он не подписывает PDF и не делает никаких заявлений о соответствии подписей. Когда требуются подписи, выполните отрисовку здесь, а затем подпишите документ движком. NextPDF Pro предоставляет только подписание усовершенствованной электронной подписью PDF (PAdES) B-B; профили долговременной проверки — возможность Enterprise, они вне области применения этого моста.
- Он не сертифицирует и не гарантирует конвейер, а также не делает его “защищённым от вмешательства”. Он реализует только конкретные, проверяемые по исходному коду средства контроля, описанные на этой странице.
Оперативный регламент
Заголовок раздела «Оперативный регламент»| Симптом | Первая проверка |
|---|---|
Worker URL must use HTTPS | Проверьте схему настроенного workerUrl. |
private or reserved IP | DNS-записи имени узла Worker; ищите запись, которая разрешается в пространство RFC 1918 / петлевое / RFC 3927. |
DNS answer changed since validation | Нестабильность DNS или попытка перепривязки; выполните повторное разрешение и проверьте полный набор записей. |
cURL transport error | Сетевой путь, цепочка TLS и — если закреплённые ключи заданы — наличие SPKI предъявленного сертификата в наборе закреплённых ключей. |
| Отрисовка завершается ошибкой сразу после ротации сертификата | Набор закреплённых ключей без соответствующего резервного закреплённого ключа. Добавьте новый SPKI как резервный до ротации. |
is not installed / no LocalRendererFactoryInterface | Резервный режим включён, но не подключена ни одна фабрика, либо отсутствует nextpdf/artisan. |
| Отказы из-за ограничения частоты несогласованы между узлами | Ограничитель в памяти работает в пределах процесса; разместите перед ним общее хранилище. |
Сообщение об инцидентах
Заголовок раздела «Сообщение об инцидентах»Сообщайте об уязвимостях через GitHub Security Advisories или контакт по безопасности из файла SECURITY.md репозитория. Не оформляйте проблемы безопасности как публичные issue на GitHub.
См. также
Заголовок раздела «См. также»- /integrations/cloudflare/overview/ — почему этот пакет построен вокруг границы доверия.
- /integrations/cloudflare/configuration/ — поля наборов закреплённых ключей и ограничений.
- /integrations/cloudflare/troubleshooting/ — полное сопоставление сбоев и исключений.