Перейти к содержимому

Безопасность и эксплуатация Artisan

Мост отрисовывает в Chrome HTML, который может быть недоверенным, помещая его за двумя независимыми сетевыми барьерами и строгой политикой содержимого. Песочница Chrome на уровне операционной системы — отдельный, необязательный механизм контроля с явными ограничениями. На этой странице описана граница защиты. Здесь не утверждается, что эта граница абсолютна.

Отрисовка — это выполнение запроса на стороне сервера: ваше приложение передаёт HTML движку браузера, который по умолчанию может загружать ресурсы. Когда недоверенные входные данные управляют исходящей загрузкой, возникает риск подделки запроса на стороне сервера (SSRF): запись CWE-918 в каталоге Common Weakness Enumeration (CWE) определяет её как извлечение сервером содержимого переданного URL без достаточной уверенности в том, что запрос достигнет ожидаемого назначения. SSRF (CWE-918) входит в перечень CWE Top 25. Стандарт проверки безопасности приложений (ASVS) проекта Open Worldwide Application Security Project (OWASP) требует контролировать исходящие запросы серверных компонентов, а не оставлять их неявными. Памятка OWASP SSRF Prevention Cheat Sheet рассматривает запрет вызовов к произвольным назначениям на сетевом уровне как надёжный механизм контроля. Описанная ниже сетевая модель “запрет по умолчанию” — ответ моста на это требование. Документ National Institute of Standards and Technology (NIST) Special Publication (SP) 800-53 SC-7 описывает тот же принцип границы: “запретить всё, разрешать в порядке исключения”; мост применяет его на транспортном уровне.

HTML, передаваемый мосту, полностью обрабатывается в рамках процесса и внутри локального экземпляра Chrome. Сам мост не выполняет исходящих сетевых вызовов и не позволяет Chrome выполнять их (см. сетевую модель ниже), поэтому содержимое входных данных не покидает узел через средство отрисовки. Персональные данные (ПДн) из входных данных попадают в создаваемые вами выходные данные формата Portable Document Format (PDF), поэтому применяйте к выходным данным те же средства контроля резидентности, что и к входным. Мост не сохраняет входные или выходные данные на диск; за сохранение отвечает вызывающая сторона.

Безопасная телеметрия и очистка журналов

Заголовок раздела «Безопасная телеметрия и очистка журналов»

ChromeHtmlRenderer и BrowserPool принимают необязательный LoggerInterface по стандарту PHP Standard Recommendation (PSR)-3. Мост записывает в журнал только эксплуатационные метаданные: длину входных данных в байтах, целевую ширину и высоту, длину выходных данных в байтах, измеренную высоту содержимого, запуск браузера с настроенным путём к исполняемому файлу, уведомления о перезапуске со счётчиком отрисовок и события закрытия. Он не записывает в журнал содержимое HTML, отрисованные байты или извлечённый текст. Это согласуется с рекомендациями NIST SP 800-92: вести журнал эксплуатационных событий, не помещая в журналы конфиденциальные полезные нагрузки. Путь к исполняемому файлу попадает в журнал. Считайте его неконфиденциальными метаданными развёртывания. Тесты проверяют форму вызовов журналирования в tests/Unit/Artisan/ChromeHtmlRendererTest.php::renderLogsDebugWithSizeWidthHeightAndPdfSize и tests/Unit/Artisan/BrowserPoolTest.php::getBrowserLogsInfoOnLaunchWithBinaryPath.

Модель сетевой изоляции (эшелонированная защита)

Заголовок раздела «Модель сетевой изоляции (эшелонированная защита)»

Мост использует два независимых барьера, чтобы обход одного из них не ставил узел под угрозу:

  1. Content-Security-Policy. Каждая отрисовка оборачивается ChromeSecurityPolicy::wrapHtml() в документ, который несёт:

    default-src 'none'; style-src 'unsafe-inline'; img-src data:;
    base-uri 'none'; form-action 'none'; frame-ancestors 'none';
    navigate-to 'none';

    Директива политики безопасности содержимого (CSP) default-src 'none' запрещает все источники ресурсов. img-src data: разрешает только встроенные изображения. navigate-to 'none' блокирует навигацию на стороне клиента. style-src 'unsafe-inline' — единственное послабление, необходимое Chrome printToPDF для применения встроенных стилей. Это проверено в src/Artisan/ChromeSecurityPolicy.php и подтверждено тестом ChromeSecurityPolicyTest::wrapHtmlIncludesNavigationCspDirectives.

  2. Блокировка транспорта Chrome DevTools Protocol (CDP). Перед загрузкой содержимого ChromeHtmlRenderer отправляет Network.enable, а затем Network.setBlockedURLs с шаблоном ['*']. Это блокирует каждый URL подресурса на транспортном уровне Chrome DevTools Protocol, независимо от CSP. Это проверено в src/Artisan/ChromeHtmlRenderer::blockAllNetworkRequests() и подтверждено тестом ChromeHtmlRendererTest::renderAutoFitsHeightAndBlocksNetworkRequests (он проверяет точный порядок методов CDP и параметр ['urls' => ['*']]). Такая блокировка на сетевом уровне соответствует механизму контроля, который рекомендации OWASP по SSRF называют сильнейшим; это запрет всего на транспортном уровне, согласующийся с NIST SP 800-53 SC-7.

Результат: удалённый URL в <img>, таблице стилей, шрифте, скрипте или iframe во входных данных не загружается. Мост не реализует список разрешённых доменов или фильтр частных IP-адресов, потому что они ему не нужны: он вообще не допускает исходящую загрузку подресурсов.

Примечание о расхождении: docblock nextpdf/core для writeHtmlChrome() говорит, что Chrome “will fetch external resources”, и советует настроить политику, чтобы “block private IP ranges and limit allowed domains.” Это описывает настраиваемую модель списка разрешений. Поставляемая в Artisan ChromeSecurityPolicy не предоставляет списка разрешений; она безусловно блокирует все запросы подресурсов. Авторитетен код, а не docblock ядра. Это расхождение зафиксировано для команды документации ядра.

ChromeSecurityPolicy::validate() выполняется до того, как мост обратится к Chrome, и отклоняет:

ПроверкаОграничениеОбоснование
Размер HTML> maxHtmlSize (по умолчанию 5 МБ)Ограничение на исчерпание ресурсов (неконтролируемое потребление ресурсов из CWE Top 25)
Data URI в кодировке Base64группа захвата >= 13_000_000 байтОграничение против “бомб” распаковки
<meta http-equiv="refresh">любой (без учёта регистра, single/double кавычки)Блокирует перенаправления на стороне клиента к внутренним конечным точкам — вектор навигации SSRF

Блокировка meta-refresh — явное усиление защиты от SSRF. Без неё подконтрольный злоумышленнику HTML мог бы перенаправить Chrome на конечную точку метаданных облака до printToPDF. Граничное поведение подтверждается в ChromeSecurityPolicyTest (validateThrowsOnOversizedHtml, validateRejectsMetaRefreshRedirect, validateRejectsMetaRefreshCaseInsensitive, validateRejectsMetaRefreshWithSingleQuotes, validateRejectsOversizedBase64DataUri, validateRejectsBase64DataUriAtExactThreshold).

Кроме того, ChromeSecurityPolicy::wrapHtml() удаляет </style> из defaultCss перед внедрением, чтобы не допустить выхода из блока стилей в контекст скрипта (подтверждено тестом ChromeSecurityPolicyTest::wrapHtmlStripsStyleClosingTagsFromDefaultCss).

Песочница Chrome на уровне операционной системы — отдельный механизм контроля по сравнению с описанными выше сетевыми барьерами, и мост её не гарантирует.

  • По умолчанию noSandbox равно false, поэтому Chrome запускается со включённой собственной песочницей. Мост не реализует эту песочницу; он полагается на песочницу исполняемого файла Chrome, которая зависит от поддержки ядра узла.
  • Установка noSandbox: true запускает Chrome с --no-sandbox. Это удаляет песочницу изоляции процессов Chrome. Такой вариант предусмотрен для контейнеров, где песочница не может инициализироваться. Это реальное снижение изоляции: компрометация средства отрисовки больше не сдерживается песочницей Chrome.
  • Сетевые барьеры моста (CSP + блокировка CDP) остаются в силе независимо от того, включена ли песочница, но они не заменяют изоляцию процессов. Применяются рекомендации OWASP ASVS о наименьших привилегиях: запускайте Chrome от имени пользователя без прав root, в ограниченном контейнере, используйте noSandbox только там, где это неизбежно, и рассматривайте развёртывание с --no-sandbox как требующее более высокого доверия к входным данным.

В этой документации не утверждается, что мост “secure by default” или “tamper-proof”. В ней также не утверждается, что отключение песочницы безопасно. Здесь описаны имеющиеся механизмы контроля и границы их действия. Подготовка контейнера с поддержкой песочницы описана на странице /integrations/artisan/chrome-renderer-setup/.

Эти режимы сбоев перечислены на основе src/Artisan/Exception/ и кода render/transport:

УсловиеПроявляется какИсточник
chrome-php/chrome отсутствуетChromeNotAvailableException (с командой установки)BrowserPool::getBrowser()
HTML превышает maxHtmlSizeRuntimeException (с сообщением “exceeds maximum allowed size” — превышен максимальный размер)ChromeSecurityPolicy::validate()
Data URI в кодировке Base64 чрезмерного размераRuntimeException (с сообщением “oversized base64 data URI” — data URI Base64 чрезмерного размера)ChromeSecurityPolicy::validate()
Запрещённый meta-refreshRuntimeException (с сообщением “forbidden meta refresh redirect” — запрещённое перенаправление meta-refresh)ChromeSecurityPolicy::validate()
Запуск / тайм-аут / сбой ChromeChromeRenderException (оборачивая причину)ChromeHtmlRenderer::render()
Chrome вернул пустой PDFChromeRenderException (с сообщением “returned empty data” — возвращены пустые данные)ChromeHtmlRenderer::render()
У страницы нет потока содержимогоPdfParseExceptionPageImporter::import()

Если ChromeRenderException возникает внутри отрисовки, оно повторно выбрасывается без изменений. Любой другой Throwable оборачивается в ChromeRenderException с сохранением предыдущего исключения (подтверждено тестами ChromeHtmlRendererTest::renderRethrowsChromeRenderExceptionWithoutWrapping и ::renderWrapsUnexpectedThrowablesWithChromeRenderException). Страница Chrome всегда закрывается в блоке finally, даже при сбое.

  • Размер входных данных: maxHtmlSize (по умолчанию 5 МБ) и ограничение data URI в Base64 в 13 МБ.
  • Время: renderTimeout секунд ограничивает и загрузку содержимого, и синхронные вызовы CDP. Управляющие команды CDP используют фиксированный тайм-аут 5 секунд.
  • Процесс: BrowserPool перезапускает Chrome каждые 100 отрисовок, чтобы ограничить рост памяти, и закрывает процесс при close() / уничтожении.

Это ограничения, а не квоты. Для любого пути, доступного недоверенным входным данным, всё равно используйте ограничения ресурсов на уровне узла (cgroup, ulimit, бюджет запросов), согласующиеся с рекомендациями CWE Top 25 по потреблению ресурсов.

Внедрите логгер PSR-3, чтобы фиксировать начало отрисовки (размер, ширина, высота), завершение отрисовки (размер выходных данных, высота содержимого), запуск браузера (путь к исполняемому файлу), перезапуск браузера (счётчик отрисовок) и закрытие браузера (счётчик отрисовок). Это единственные генерируемые события, и они не содержат полезной нагрузки. Используйте их для целевых уровней обслуживания (SLO) по задержке и для оповещений о частоте перезапусков.

УтверждениеИсточникclause_id (идентификатор пункта)reference_id (идентификатор ссылки)
Исходящие запросы серверных компонентов должны контролироватьсяOWASP ASVS 5.0§ (SSRF/outbound control — контроль исходящих запросов)
SSRF = сервер извлекает переданный URL без проверки назначенияперечень CWE Top 25 2025 (CWE-918)cwe_top25_2025#x28.x2.p2
SSRF (CWE-918) входит в перечень CWE Top 25перечень CWE Top 25 2025cwe_top25_2025#x1.p73
Неконтролируемое потребление ресурсов входит в перечень CWE Top 25перечень CWE Top 25 2025 (CWE-400)cwe_top25_2025#x19.x2.p2
Защита границы по принципу “запрет по умолчанию” (разрешение в порядке исключения)стандарт NIST SP 800-53 ред. 5, SC-7SC-7
Запрет на сетевом уровне вызовов к произвольным назначениям — сильный механизм контроля SSRFсерия памяток OWASP Cheat Sheet Series (SSRF Prevention §Network layer — предотвращение SSRF, сетевой уровень)owasp_cheatsheet_series#x132.x2
Защита компонентов, загружающих URL, от SSRFсерия памяток OWASP Cheat Sheet Series§ (SSRF prevention, URL-fetch tools — предотвращение SSRF, средства загрузки по URL)
Изоляция отрисовки недоверенного содержимого, наименьшие привилегииOWASP ASVS 5.0§ (sandbox / least privilege — песочница / наименьшие привилегии)
Вести журнал эксплуатационных событий; не помещать полезные нагрузки в журналыNIST SP 800-92§ (log content guidance — рекомендации по содержимому журналов)

Цитаты получены через систему соответствия NextPDF (манифест корпуса 1d05b7c4…d790b6); текст пунктов перефразирован, а не приведён дословно.

УгрозаМеханизм контроляОстаточный риск
SSRF через удалённый подресурсCSP default-src 'none' + CDP setBlockedURLs('*')Ошибка движка Chrome, которая обходит оба барьера (эшелонированная защита снижает риск, но не устраняет его)
SSRF через навигацию meta-refreshПроверка до Chrome отклоняет тегНовый вектор навигации, не соответствующий шаблону
Исчерпание ресурсовОграничения размера входных данных + ограничения Base64 + тайм-аут + перезапуск каждые 100 отрисовокНет поузловой квоты; сочетайте с cgroup/ulimit
Компрометация процесса средства отрисовкиПесочница Chrome когда включенаnoSandbox: true полностью удаляет этот механизм контроля
Выход из стилей / внедрение</style> удаляется в defaultCss; CSP блокирует скриптыВнедрение через будущий вектор, который не будет удалён

Мост не выполняет никаких криптографических операций. Он создаёт байты PDF через Chrome и встраивает их. Подписание, шифрование и поведение в режиме Federal Information Processing Standards (FIPS) относятся к ядру/Premium и не затрагивают Artisan.

  • Настройка — /integrations/artisan/configuration/
  • Настройка средства отрисовки Chrome — /integrations/artisan/chrome-renderer-setup/
  • Устранение неполадок — /integrations/artisan/troubleshooting/
  • Использование в продакшене — /integrations/artisan/production-usage/
  • Обзор — /integrations/artisan/overview/