Disk 26.1100.100 и 26.1150.0: SDK досок больше не шлёт JWT на любой origin, а секрет по умолчанию убран
Обновление безопасности
Закрыта уязвимость или ослабленная проверка прав. Ставить в первую очередь.
Диск приехал в коробку двумя версиями одним днём, 21 сентября 2026 года (по официальному каналу «Битрикс24 changelog»). В 26.1100.100 поменялась одна строка, и заметно это только на порталах с сессиями в режиме separated. 26.1150.0 закрывает канал postMessage в SDK досок, через который JWT и ссылка на файл уходили на любой origin, убирает из кода секрет JWT по умолчанию и запрещает встраивать редактор досок на чужие сайты.
Читать в первую очередь администраторам коробки со своим сервером досок. Если портал и сервер жили на секрете по умолчанию 'secret_token', после обновления они разойдутся по подписи JWT. Разработчикам нужно проверить вызовы StorageFileFinderOptions и свои копии шаблона редактора досок.
Сверьте секрет JWT с сервером досок
Document\Flipchart\Configuration::getJwtSecret() раньше подставлял 'secret_token', если секрет нигде не задан. Это значение лежало в коде и в секции boards файла .settings.php модуля, в 26.1150.0 его убрали из обоих мест.
Если секрет не задан ни в bitrix/php_interface/disk-boards.php, ни в конфигурации boards, ни в опции flipchart.jwt_secret модуля disk, метод при первом обращении генерирует sha1(random_bytes(32)) и сохраняет результат в эту опцию. В режиме облачного прокси (isUsingDocumentProxy()) секрет не создаётся, и метод возвращает пустую строку. Портал, который жил на значении по умолчанию, после обновления станет подписывать JWT новым случайным секретом, а сервер досок останется со старым, и подписи разойдутся.
До обновления проверьте, задан ли секрет явно хотя бы в одном из трёх мест. Главнее всех опция flipchart.jwt_secret: если она заполнена, значения из disk-boards.php и секции boards не используются. Если секрета нет нигде, пропишите на портале тот же секрет, что стоит на сервере досок. А раз 'secret_token' лежал в исходниках модуля, лучше сразу завести новый секрет на обеих сторонах. После обновления сверьте секрет, которым подписывает портал, с секретом сервера. Если отпечатки разошлись, меняйте опцию flipchart.jwt_secret: сгенерированный секрет лежит в ней, и правка disk-boards.php или .settings.php его не перекроет. Отпечаток снимается так:
<?php declare(strict_types=1);
use Bitrix\Disk\Document\Flipchart\Configuration;
use Bitrix\Main\Loader;
Loader::requireModule('disk');
// если секрета нет ни в одном из трёх мест, вызов сам сгенерирует его и запишет в опцию
$secret = Configuration::getJwtSecret();
echo match (true) {
$secret === '' => 'пустая строка, включён облачный прокси',
$secret === 'secret_token' => 'старое значение по умолчанию, смените на портале и на сервере',
default => 'отпечаток: ' . substr(hash('sha256', $secret), 0, 16),
};
На сервере досок посчитайте SHA-256 от его секрета и сравните первые 16 символов.
Убедитесь, что редактор досок откроется
Компонент disk.flipchart.editor теперь бросает SystemException и не рисуется в трёх случаях:
app_urlне абсолютный http(s)-адрес (проверяетConfiguration::isValidAppUrl());- заголовки уже отправлены, и
FramePolicy::emit()не может выставитьContent-Security-Policy: frame-ancestors 'self'иX-Frame-Options: SAMEORIGIN; ServiceProfileResolver::resolveForEditor()не определил инстанс сервиса досок.
В последнем случае публичная ссылка, единая ссылка и Controller\Integration\Flipchart::viewDocumentAction() отдают страницу ошибки и пишут в лог через PilotLog.
Заголовки из FramePolicy запрещают встраивать страницу редактора на чужие сайты. Если вы показывали доску в iframe на другом домене, после обновления это перестанет работать. Если выводите компонент в своём коде, следите, чтобы до него заголовки ещё не ушли.
Адреса сервиса досок теперь зависят от инстанса. Configuration::getApiHost() и getAppUrl() получили необязательный параметр ServiceProfile $profile = ServiceProfile::Old. С ServiceProfile::New они читают отдельные опции и бросают DualMode\ConfigurationException, если адрес не задан или не проходит ServiceAddress::isValid(). Адреса обоих инстансов удобно проверять одним сервисом:
<?php declare(strict_types=1);
namespace Vendor\Boards\Application\Service;
use Bitrix\Disk\Document\Flipchart\Configuration;
use Bitrix\Disk\Document\Flipchart\DualMode\ConfigurationException;
use Bitrix\Disk\Document\Flipchart\DualMode\ServiceProfile;
use Bitrix\Main\Error;
use Bitrix\Main\Result;
final class BoardAddressCheck
{
public function check(ServiceProfile $profile = ServiceProfile::Old): Result
{
$result = new Result();
try {
$appUrl = Configuration::getAppUrl($profile);
$apiHost = Configuration::getApiHost($profile);
} catch (ConfigurationException $e) {
return $result->addError(new Error($e->getMessage(), 'BOARD_ADDRESS_NOT_SET'));
}
if (!Configuration::isValidAppUrl($appUrl)) {
$result->addError(new Error(
"app_url не абсолютный http(s)-адрес: {$appUrl}",
'BOARD_APP_URL_INVALID',
));
}
return $result->setData(['appUrl' => $appUrl, 'apiHost' => $apiHost]);
}
}
Обновите свои копии SDK досок
SDK досок лежит в шаблоне disk.flipchart.editor, это исходник templates/.default/src/flip-board-sdk/src/lib/flip-board-sdk.js и собранный script.js. До 26.1150.0 он слал сообщения в iframe доски с адресатом '*'. Среди них был ответ на waitParams с параметрами доски, включая JWT (token) и boardData.fileUrl. Обработчик входящих событий, в том числе AI-запросов из доски, не проверял отправителя.
Теперь все postMessage адресованы origin из appUrl. isTrustedMessage() отбрасывает входящее сообщение, если event.source не iframe доски или event.origin не совпадает. Подписку на beforeunload убрали, а невалидный appUrl или схема не http(s) вызывают исключение уже в конструкторе.
Рядом с исходником в поставку попал SECURITY-CHECKLIST.md. Из него следует, что SDK — форк вендорного @flip-chart/flip-board-sdk 0.0.31 с защитой канала сообщений. После каждого обновления библиотеки чек-лист велит проверить grep'ом исходник и собранный script.js. Отправок на '*' быть не должно, isTrustedMessage должен быть на месте, подписки на beforeunload быть не должно. Потом чек-лист велит прогнать chef test unit, а после правки исходника пересобрать бандл через chef build.
Если вы копировали disk.flipchart.editor в свой шаблон сайта, обновление копию не тронет, и в ней остался старый SDK. Проверить её хватит одной командой:
grep -rL "isTrustedMessage" --include=script.js --include=script.min.js \
local/templates/*/components/bitrix/disk.flipchart.editor/ \
bitrix/templates/*/components/bitrix/disk.flipchart.editor/
Файлы в выдаче собраны со старым SDK, такую копию пора пересобрать от нового .default.
Проверьте свой код поверх Диска
В конструктор Search\StorageFileFinderOptions (lib/search/storagefilefinderoptions.php) перед ?array $typeFileValues, который появился в 26.1100.0, вставили новый параметр array $additionalFilter = []. Вызов с восемью позиционными аргументами теперь отдаст список типов файлов в ORM-фильтр. null на восьмой позиции закончится TypeError, потому что новый параметр не nullable. Ядро само вызывает конструктор с именованными аргументами (lib/rest/service/file.php, lib/FilePicker/Provider.php), сделайте так же и передавайте типы как typeFileValues: [...]. Прочитать дополнительный фильтр можно новым getAdditionalFilter().
Internals\Model::setAttributes() (lib/internals/model.php) при пустом списке алиасов теперь доходит до setExtraAttributes() и заполняет extra-поля, а раньше выходил из метода до этого шага. Если ваша модель на базе Internals\Model рассчитывала, что после такого вызова extra-поля пустые, проверьте её.
При separated-сессиях пароль публичной ссылки спросят ещё раз
Это единственная правка кода в 26.1100.100. Internal\Service\ExternalLink\ExternalLinkPasswordService::getSessionId() берёт ID из Application::getInstance()->getKernelSession() вместо getSession(). От этого ID через sha256 строится ключ disk.external_link.password_access.<ID ссылки>.<hash>, под которым в PersistentStorageInterface 24 часа хранится подтверждение пароля публичной ссылки. Callback sessionIdResolver из конструктора, если он передан, по-прежнему главнее.
В режиме сессий default за kernel-сессией стоит KernelSessionProxy поверх той же сессии, getId() возвращает тот же ID, и для таких порталов ничего не меняется. В режиме separated kernel-сессия отдельная (KernelSession со своим ID), а общая сессия стартует лениво (enableLazyStart()). Там ключ теперь считается от другого ID, и подтверждения, сохранённые до обновления, не найдутся. Посетителю, который ввёл пароль меньше суток назад, придётся ввести его снова. Режим задаётся ключом mode в секции session. Ядро читает local/.settings.php, если он есть, иначе bitrix/.settings.php, а .settings_extra.php перекрывает эту секцию целиком.
Исправление касается и AJAX-контроллера disk.external.link/ajax.php (checkPasswordAction), который появился в 26.1100.0 (разбор) и вызывает validateAndConfirm().
Новое: FilePicker, второй инстанс досок, задачи из досок
Универсальный выбор файлов (FilePicker) состоит из контроллера Controller\FilePicker с экшенами getInitialStage, listChildren, search и resolveSelection (все POST и с CloseSession) и пространства Bitrix\Disk\FilePicker. Provider отдаёт источники, недавние файлы, дерево и поиск через StorageFileFinder, сортирует по белому списку из name, createTime и updateTime и возвращает страницу до 100 записей. SignedConfig и SignedConfigBuilder собирают подписанную конфигурацию выбора с режимом, лимитом, типами, сроком действия и привязкой к пользователю и сайту. К этому прилагаются компонент disk.file.picker (Controllerable, экшен getBootstrap) и JS-расширения disk.disk-picker и disk.disk-picker.internal на TypeScript. Опция disk_enable_universal_file_picker (в default_option.php она N) через \Bitrix\Disk\Configuration::isUniversalFilePickerEnabled() попадает только в настройки JS-расширения disk.disk-picker, экшены контроллера FilePicker от неё не зависят.
Второй инстанс сервиса досок живёт в lib/document/flipchart/dualmode/. ServiceProfile (Old и New) обозначает инстанс, ServiceProfileResolver выбирает его для объекта, папки назначения и редактора. Пилотом управляет PilotProjectService с методами prepare, activate, drop и emergencyRollback, адреса нового инстанса берутся из опций flipchart.new_service.api_host и flipchart.new_service.app_url, группы пилота из flipchart.new_service_group_ids. Консольной команды нет намеренно, в докблоке PilotProjectService сказано, что Symfony Console на коробке необязателен, поэтому процедуру запускают из /bitrix/admin/php_command_line.php. Опцию групп руками лучше не трогать. Так пропускается проверка, что хранилище группы пусто, а по тексту докблока последствия необратимы, потому что старый инстанс навсегда запоминает document_id. emergencyRollback() требует явного acceptDataLoss: true.
Задачи из досок включает опция flipchart.tasks_enabled (по умолчанию N, проверка через Configuration::isTasksEnabled()), фронтенд лежит в JS-расширении disk.board-tasks. Работают они только для досок на новом инстансе, это видно по флагу TASKS_ENABLED в disk.flipchart.editor/class.php.
Мелочи и находки
- Грант на скачивание доски выдаётся, но пока нигде не проверяется.
DocumentDownloadGrantподписанTimeSignerна 15 минут и привязан к сессии, объекту и версии,DocumentDownloadUrlServiceдобавляет его в URLgetDocumentпараметромgrant. При этомController\Integration\Flipchart::getDocumentAction()по-прежнему ищет документ только поsessionId, аDocumentDownloadGrant::open()не вызывает ни один модуль коробки. По докблоку сервиса, три места сборки URL свели в одно, чтобы пропущенное место не осталось обходом проверки. - Экшен
getBootstrapкомпонентаdisk.file.pickerобъявлен через'prefilters', то есть с заменой стандартного набора фильтров. В нёмAuthenticationиHttpMethod(POST), фильтраCsrfнет. Если берёте этот приём в своиControllerable-компоненты, добавляйтеCsrfсами. include.phpявно регистрирует автозагрузкуbitrix\disk\controller\filepickerнаlib/Controller/FilePicker.php, а в нашей тестовой коробке файл лежит вlib/controller/. Она развёрнута на регистронезависимой ФС, так что реальный регистр каталога в дистрибутиве по ней не определить. На Linux, если контроллер не найдётся, начните с этого пути.- Хотфикс собран позже основного релиза.
VERSION_DATEу 26.1100.100 стоит 2026-08-26, у 26.1150.0 стоит 2026-08-07. После установки 26.1150.0 вExternalLinkPasswordServiceостаётсяgetKernelSession(). - Константа
Storage::GROUP_STORAGE_CACHE_TAG = 'disk_group_storage'. Этот тег сбрасывается при создании и удалении хранилища группы (lib/storage.php), на него можно вешать свой тегированный кеш. - Ещё из мелких API:
RecentlyUsedManager::getFileModelListWithRecentTimeByUser(),StorageFileFinder::loadModels(),Configuration::isValidAppUrl(). - Схема БД не менялась. Добавились опции
disk_enable_universal_file_picker,flipchart.tasks_enabled,flipchart.new_service_group_ids,flipchart.new_service.api_hostиflipchart.new_service.app_url, аflipchart.jwt_secretтеперь может записаться сама.
Что делать
Перед обновлением задайте на портале явный секрет JWT, тот же, что на сервере досок. После обновления прогоните проверку секрета и адресов из примеров выше, откройте доску в редакторе и по публичной ссылке. В своём коде поищите new StorageFileFinderOptions( с позиционными аргументами, в шаблонах сайта поищите скопированный disk.flipchart.editor.