landing · Сайты 24 26.1200.0 Безопасность

Landing 26.1200.0: подписанный предпросмотр ушёл в CSP-песочницу, а импорт с заменой сайта помечает старые страницы удалёнными

2 мин чтения Устаревших API: 3

Обновление безопасности

Закрыта уязвимость или ослабленная проверка прав. Ставить в первую очередь.

Landing 26.1200.0 трогает 690 файлов, но основная масса там картинки, переводы и пересобранные бандлы. Релиз закрывает дыры в подписанной ссылке предпросмотра, чинит импорт с заменой сайта и размечает для скринридеров интерфейсы прав, настроек и редактора. Если вы вызываете AiSiteChatAvailabilityService или Controller\Copilot::executeGenerationAction() из своего кода, начните с первых двух разделов.

Уберите вызовы старых методов AiSiteChatAvailabilityService

Из Bitrix\Landing\Integration\AiAssistant\Service\AiSiteChatAvailabilityService удалены три публичных метода: checkSitesAiChatAvailability(int $bindingId), isSitesAiChatAvailable(int $bindingId) и getSitesAiChatUnavailableReason(int $bindingId). Deprecated-стадии не было, вызов упадёт сразу. Сам класс теперь помечен @internal, так что опираться на него в своём коде стоит с оглядкой.

Появился isSitesAiChatPotentiallyAvailable(): bool, но заменяет он старые методы только в интерфейсе. По комментарию к методу, это проба для отрисовки, не привязанная к сущности и не учитывающая права, а действия над конкретным сайтом надо проверять методами, которые знают про привязку.

Старый isSitesAiChatAvailable($bindingId) шёл через checkSitesAiChatAvailability(), а тот проверял две вещи подряд: checkSitesAiProductAvailability() и checkTriggerAvailability($bindingId). Оба метода остались, и эту связку можно собрать самим:

        <?php declare(strict_types=1);

use Bitrix\Landing\Integration\AiAssistant\Service\AiSiteChatAvailabilityService;
use Bitrix\Landing\Rights;

$service = new AiSiteChatAvailabilityService();

// показать ли точку входа в AI-чат: только для отрисовки, без прав и без привязки
$showEntryPoint = $service->isSitesAiChatPotentiallyAvailable();

// замена isSitesAiChatAvailable($bindingId): продукт плюс триггер для привязки
$canStart = $service->checkSitesAiProductAvailability()->isAvailable()
    && $service->isTriggerAvailable($bindingId)
    // права на сайт проверяем отдельно
    && Rights::hasAccessForSite($siteId, Rights::ACCESS_TYPES['edit']);

    

Раньше checkTriggerAvailability() ловил любое исключение из проверки включённости AI-сайтов и возвращал ERROR_TRIGGER_UNAVAILABLE, а теперь try/catch там нет, и исключение уходит к вам.

В диффе старый метод вызывался в одном месте, в result_modifier.php стартовой страницы, причём как isSitesAiChatAvailable(1), с захардкоженным bindingId = 1.

Вызывайте executeGeneration только POST-запросом с CSRF

Bitrix\Landing\Controller\Copilot::executeGenerationAction(int $generationId) был статическим, теперь это обычный метод экземпляра. Для действия executeGeneration в configureActions() добавлены фильтры Csrf и HttpMethod([POST]). Появилась и проверка владельца. Если автор генерации не текущий пользователь, действие вернёт false с ошибкой ACCESS_DENIED («Insufficient permissions to execute generation.»).

Статический вызов Copilot::executeGenerationAction($id) из PHP после обновления упадёт, GET-запрос к действию отсечёт фильтр, а чужую генерацию запустить больше не выйдет.

Не рассчитывайте на свой код в подписанном предпросмотре

Компонент landing.pub различает теперь предпросмотр по валидной подписанной ссылке (Site::isPublicHashValid) и DRAFT_MODE баз знаний и групп. Для подписанной ссылки в Битрикс24 он отдаёт заголовок:

        Content-Security-Policy: sandbox allow-scripts allow-forms allow-popups allow-modals allow-popups-to-escape-sandbox allow-downloads

    

Без allow-same-origin у страницы непрозрачный origin, и до cookie, хранилища и API портала она не дотянется. По docblock sandboxSignedPreview(), под origin портала скрипт страницы выполнялся с сессией портала того, кто открыл ссылку. Своё превью устройств редактор запирает в sandbox-фрейм ровно поэтому, а по прямой ссылке эта страница открывалась вообще без песочницы.

От фишинга песочница не спасает. Форме входа, которую автор нарисует на адресе портала, посетитель поверит, поэтому в режиме песочницы (Landing::getDevicePreviewMode(), он теперь включается и для подписанной ссылки, и для sandbox-фрейма превью в редакторе) ядро выключает код автора:

  • Hook\Page\HeadBlock::enabled() и Hook\Page\GTM::enabled() возвращают false;
  • Hook\Page\B24button::enabled() тоже возвращает false, но новая проверка стоит ниже ветки с ранним return true, и в этой ветке виджет остаётся включённым (условие ветки в хунк не попало). Про виджет в комментарии сказано, что его CODE «is stored as sent (Hook::saveData() checks no option list)»;
  • HTML-блок санируется через новый LandingBlocksHtmlComponent::mustSanitize(), даже если у сайта свой домен. Раньше сайт со своим доменом получал сырой HTML и в публикации, и в предпросмотре.

Если ваши блоки или компоненты выводят произвольный код автора, стоит вести себя так же:

        <?php declare(strict_types=1);

namespace Vendor\Landing\Render;

use Bitrix\Landing\Landing;

final class PartnerWidgetRenderer
{
    public function render(string $authorHtml): string
    {
        // в песочнице предпросмотра ядро глушит код автора, не выбиваемся из общего поведения
        if (Landing::getDevicePreviewMode()) {
            return '';
        }

        return $authorHtml;
    }
}

    

Ещё три правки вокруг предпросмотра:

  • Подписанный «хвост» preview/<hash>/ теперь ставится только на ссылки своего сайта. Landing::getPublicUrl() проверяет это новым protected isPreviewSite(), и маркеры #landing<id>/#block<id>, ведущие на страницы другого сайта, превращаются в обычный публичный URL. Комментарий объясняет, что подписанный хвост открывает весь черновик сайта.
  • Редирект по forceLandingId на ветке «страница не найдена» раньше делал Landing::createInstance() по id из запроса и уводил туда без проверок. Теперь новый getForceReloadUrl() требует, чтобы страница существовала, принадлежала этому же сайту и прошла check_permissions, если он включён у компонента. Иначе, как сказано в комментарии, подписанный предпросмотр одного сайта превращался в подписанную ссылку на черновик другого.
  • У облачного сайта со своим доменом ссылка предпросмотра строится на хосте портала, в виде https://<portal>/pub/site/<id>/preview/<hash>/. Хеш подписан парой «хост портала + путь публикации», и новый класс Site\PreviewUrl следит, чтобы хост ссылки и хост подписи не разъезжались. Раньше в такие ссылки подставлялся домен сайта.

Проверьте импорт с заменой сайта

Шаг Transfer\Script\Action\UpdateReplacedSitePages должен при замене сайта убирать его старые страницы. Условие в нём было перевёрнуто, if ($siteId > 0) return;, так что при замене сайта шаг не делал ничего. Теперь стоит $siteId <= 0, и при замене шаг выполняется:

  • снимает области страниц через TemplateRef::deleteArea() и помечает страницы удалёнными через Landing::markDelete();
  • если markDelete() вернул ошибку, возвращает области через новый TemplateRef::restoreArea() и бросает TransferException;
  • выполняется в финальном блоке сценария ReplaceSite, после UpdateSpecialPages и перед ActivateRights.

Соседний шаг CheckReplacedSite раньше проверял только, что id заменяемого сайта передан. Теперь он требует siteId > 0 и право edit на этот сайт (Rights::hasAccessForSite()) и выполняется после SetContextUser. Текст ошибки остался прежним, «Replaced site ID is required», так что при отказе в правах сообщение будет сбивать с толку. Vibe\Installer теперь передаёт в импорт ключ replaceSiteId вместо siteId.

Новое

TemplateRef::deleteArea($lid) теперь возвращает массив снятых записей (без ID), а TemplateRef::restoreArea(array $areas) умеет вернуть их обратно. Пара пригодится, если вы сами удаляете страницы и хотите откатиться при ошибке:

        <?php declare(strict_types=1);

namespace Vendor\Landing\Service;

use Bitrix\Landing\Landing;
use Bitrix\Landing\TemplateRef;
use Bitrix\Main\Result;

final class PageRemover
{
    public function remove(int $landingId): Result
    {
        $areas = TemplateRef::deleteArea($landingId);
        $deleted = Landing::markDelete($landingId);

        if (!$deleted->isSuccess()) {
            TemplateRef::restoreArea($areas);
        }

        return $deleted;
    }
}

    

Bitrix\Landing\Site\PreviewUrl — новый final класс без состояния, все методы статические: isCloudTarget(), isCloudPreview(), getPortalOrigin(), withRequestPort(), getSitePath(), buildSiteBase(), buildPreviewBase(). Например, withRequestPort() возвращает к «голому» хосту порт из запроса, но только если порт числовой и относится к тому же хосту:

        <?php declare(strict_types=1);

use Bitrix\Landing\Site\PreviewUrl;

PreviewUrl::withRequestPort('portal.example.ru', 'portal.example.ru:8080'); // 'portal.example.ru:8080'
PreviewUrl::withRequestPort('portal.example.ru', 'evil.example.ru:8080');   // 'portal.example.ru'

    

getPortalOrigin() вне веб-запроса возвращает пустую строку, запасной хост ядро не подставляет. По комментарию, ссылка на чужом хосте выглядела бы целой, но проверку подписи не прошла бы.

Компонент landing.blocks.message принимает новый параметр MESSAGE_TYPE (status или alert, по умолчанию status), который выводится как role сообщения. MESSAGE при этом по-прежнему выводится сырым, так что текст экранируйте сами:

        $APPLICATION->IncludeComponent('bitrix:landing.blocks.message', '', [
    'MESSAGE' => htmlspecialcharsbx($errorText),
    'MESSAGE_TYPE' => 'alert',
]);

    

Мелочи и находки

  • Большой проход по доступности. В landing.settings появились live-регион для ошибок, aria-busy и фокус на блоке фатальной ошибки. В landing.roles и landing.role_edit добавились aria-label, заголовки таблиц с scope и aria-required. В options.php поля получили <label for>, у верхней панели редактора кнопки устройств сообщают aria-pressed, а в landing.landing_designblock кнопки из <span> стали <button>. Комментарии к этим правкам местами длиннее самих правок.
  • Шаблоны обросли атрибутами data-testid. Похоже на подготовку к UI-автотестам, но в диффе это нигде не сказано.
  • Экранирование по дороге: $helpUrl и src iframe в landing.landing_designblock и src iframe в landing.landing_view теперь проходят htmlspecialcharsbx. В landing.role_edit название роли берётся из ~CURRENT, чтобы не кодировать его дважды.
  • В options.php подпись поля получает префикс раздела, только если фраза уже переведена. В комментарии сказано, что фраза «is newer than the locales of the product».
  • Favicon публичной страницы собирается как rtrim(SITE_RELATIVE_URL, '/') . '/favicon.ico'. Раньше при адресе без завершающего слеша имя файла приклеивалось к последнему сегменту пути.
  • У корзины сайтов появилось пустое состояние (landing.sites, landing.site_tile, новый параметр TOTAL_COUNT).
  • Из install/images/landing/ удалены 212 картинок: 199 фотографий business/*, 10 логотипов и 3 паттерна. По stat.txt столько же ушло из /bitrix/images/landing/, а в migration_config.json появилось сопоставление install/images → images. Ссылаются ли на эти файлы блоки, по диффу не видно.
  • Инлайн-шрифт иконок для песочницы предпросмотра теперь собирается один раз на вендора (Icon::addInlineVendorAssets()), раньше его перечитывали для каждого блока.
  • Дата сборки 26.1200.0 — 17 августа 2026, раньше обоих патчей 26.1100.100 (20 августа) и 26.1100.200 (4 сентября). Правки контроллера файлов Диска из 26.1100.200 этот дифф не трогает, они остаются на месте.
  • Deprecated нет, схема БД не менялась.

Что делать

  • Поищите в коде isSitesAiChatAvailable, checkSitesAiChatAvailability, getSitesAiChatUnavailableReason и executeGenerationAction.
  • Прогоните импорт с заменой сайта на тестовом портале.
  • Откройте подписанный предпросмотр своего сайта в Битрикс24 и посмотрите, как он выглядит без кода автора.

Устаревшие и удалённые API в этой версии

Символ Статус Чем заменять
Bitrix\Landing\Integration\AiAssistant\Service\AiSiteChatAvailabilityService::isSitesAiChatAvailable() Удалено Bitrix\Landing\Integration\AiAssistant\Service\AiSiteChatAvailabilityService::isSitesAiChatPotentiallyAvailable() (только для UI; для действий checkSitesAiProductAvailability() + isTriggerAvailable())
Bitrix\Landing\Integration\AiAssistant\Service\AiSiteChatAvailabilityService::checkSitesAiChatAvailability() Удалено —
Bitrix\Landing\Integration\AiAssistant\Service\AiSiteChatAvailabilityService::getSitesAiChatUnavailableReason() Удалено —

Читайте дальше

landing 26.1100.0 Безопасность Свежее

Landing 26.1100.0: закрыта цепочка уязвимостей, заодно ужесточены repo.register, savePicture и проверка ссылок предпросмотра

`landing` 26.1100.0 (сборка от 06.08.2026) почти целиком про безопасность. Закрыты исполнение кода через манифест repo-блока и импорт архива сайта, инъекция в `eval()` через CODE страницы, SSRF через обработчик виджета, XSS в выводе ассетов, предсказуемый в облаке md5-хеш предпросмотра. В комментари...

14 мин
landing 26.1100.200 Безопасность Свежее

Landing 26.1100.200: контроллер файлов Диска требует авторизацию и наконец отвязался от удалённого Block::isContains

Контроллер `Bitrix\Landing\Controller\DiskFile`, через который блоки Сайтов отдают файлы Диска, получил префильтры и проверку права на чтение страницы. Заодно он перестал вызывать `Block::isContains()`, который удалили ещё в 26.1000.0. Публичные сигнатуры и БД не менялись.

3 мин
landing 26.1100.100 Рутинное Свежее

Landing 26.1100.100: только номер версии

В `install/version.php` поменялись номер версии и дата сборки (20 августа), больше в релизе ничего нет. Следом идёт [landing 26.1100.200](/bitrix-updates/landing/landing-26-1100-200) с содержательной правкой контроллера файлов Диска.

1 мин
landing 26.1000.0 Заметное

Landing 26.1000.0: удалён Block::isContains, ссылки теперь режутся по белому списку схем

Обновление `landing` 26.1000.0 (сборка от 24.08.2026) — это на 90% security-хардening плюс новая песочница для превью страниц «на устройствах». Дифф огромный — 1300 файлов, +73 тысячи строк — но почти всё это пересобранные JS-бандлы, контентные блоки и webp вместо png. Содержательного PHP — примерно...

4 мин
Мы используем файлы cookie для улучшения работы сайта. Продолжая использовать сайт, вы соглашаетесь с нашей политикой конфиденциальности.
AI Домовой

AI Домовой История

на связи

пишет…
Нет истории чатов
AI Домовой

Нужна авторизация

Войдите, чтобы задавать вопросы AI Домовому.

Войти