bizproc · Бизнес-процессы 26.1300.0 Безопасность

bizproc 26.1300.0 + bizprocdesigner 26.700.0: дизайнер проверяет права на присланный documentType, внешнего AI-агента вернули владельцу его копии

2 мин чтения
Узнавайте о ломающих изменениях Битрикса раньше, чем они доедут до прода: свежие разборы — в канале @bxmax_updates

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

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

bizprocdesigner 26.700.0 ставится одним шагом с bizproc 26.1300.0 и закрывает экшен, который без проверки прав отдавал категории и стадии любого присланного клиентом типа документа. Внешнего AI-агента на псевдотипе Workflow, которого с bizproc 26.1200 подключал только администратор, снова может подключать владелец собственной запущенной копии, чужой templateId при этом не пройдёт. В bizproc перенос шаблона на месте (SequentialToNodeWorkflow::migrateTemplate()) теперь снимает автозапуск, который дублирует новый стартовый триггер, а узлы автомата состояний спрятали за выключенной по умолчанию опцией.

Обновите bizprocdesigner, чтобы настройки действия не раскрывали стадии

В 26.600 экшен getSettingsControlsAction() контроллера Bitrix\BizprocDesigner\Infrastructure\Controller\Activity брал documentType из запроса и права на него не проверял. Этот тип доходит до getPropertiesMap(), а контролы действия кладут в Options метаданные документа, например категории и стадии. Пользователь дизайнера мог прислать тип документа, к которому у него нет доступа, и получить эти списки. В 26.700 экшен требует CreateWorkflow на присланный тип, так же как соседний getNodeFilterMetadataAction(). Заодно documentType теперь передаётся в CBPActivity::createConfigurator().

Если ваш контроллер тоже принимает тип документа из запроса, проверьте права на него раньше, чем тип попадёт в сервисы. Хватит одного вызова \CBPDocument::canUserOperateDocumentType() и небольшой чистки входного массива. Вызов стоит в try: провайдер типа документа может бросить исключение на незнакомый тип, и без перехвата клиент вместо ACCESS_DENIED получил бы ошибку с текстом исключения. Ядро в Complex::loadSettingsAction() делает так же.

        <?php declare(strict_types=1);

namespace Vendor\Module\Infrastructure\Controller;

use Bitrix\Main\Engine\ActionFilter;
use Bitrix\Main\Engine\CurrentUser;
use Bitrix\Main\Engine\JsonController;
use Bitrix\Main\Error;
use Bitrix\Main\Loader;

final class DocumentFields extends JsonController
{
    public function configureActions(): array
    {
        return [
            'list' => [
                'prefilters' => [
                    new ActionFilter\Authentication(),
                    new ActionFilter\Csrf(),
                    new ActionFilter\HttpMethod([ActionFilter\HttpMethod::METHOD_POST]),
                ],
            ],
        ];
    }

    public function listAction(array $documentType): ?array
    {
        Loader::requireModule('bizproc');

        $documentType = $this->normalizeDocumentType($documentType);

        try {
            $allowed = $documentType !== null && \CBPDocument::canUserOperateDocumentType(
                \CBPCanUserOperateOperation::CreateWorkflow,
                (int)CurrentUser::get()->getId(),
                $documentType,
            );
        } catch (\Throwable) {
            // провайдер типа может бросить исключение на незнакомый тип
            $allowed = false;
        }

        if (!$allowed) {
            // кривой и чужой тип получают один ответ, перебором типы не выяснить
            $this->addError(new Error('Access denied', 'ACCESS_DENIED'));

            return null;
        }

        return \CBPRuntime::getRuntime()->getDocumentService()->getDocumentFields($documentType);
    }

    /** @return array{string, string, string}|null */
    private function normalizeDocumentType(array $raw): ?array
    {
        if (!array_is_list($raw) || count($raw) !== 3) {
            return null;
        }

        [$moduleId, $entity, $type] = array_map(
            static fn(mixed $part): string => is_string($part) ? trim($part) : '',
            $raw,
        );

        $isValid = preg_match('/^[a-z0-9_.]+$/D', $moduleId) === 1
            && preg_match('/^[A-Za-z0-9_\\\\]+$/D', $entity) === 1
            && $type !== '';

        return $isValid ? [$moduleId, $entity, $type] : null;
    }
}

    

Модификатор D у регулярок не даёт $ пропустить строку с переводом строки в конце. В этом же релизе его добавили в регулярку имени агента в CLI-командах генератора AI-агентов bizproc: '/^[a-z0-9_]+$/D'.

Учтите, что владелец копии снова подключает внешнего агента

Подключение внешнего агента к шаблону мы разбирали в bizprocdesigner 26.600.0. В 26.700 для него сменилась проверка прав. Экшены connectAgentAction, revokeAgentAction, regenerateAgentAction и agentStatusAction контроллера Diagram проходят через общий authorizeAgentTemplate(). Он и AbstractAgentRestController раньше спрашивали DocumentAccessService::canCreate(). С bizproc 26.1200 CreateWorkflow на псевдотипе Workflow есть только у администратора, поэтому не-админ получал отказ на любом templateId, в том числе на своей копии агента. Теперь обе точки вызывают новый canManageDocument($userId, $documentType, $templateId, $isUserAdmin).

Для реальных типов документа проверка прежняя, нужно CreateWorkflow. На псевдотипе Workflow пропускают администратора или владельца собственной запущенной копии AI-агента (canManageLaunchedTemplate(..., requireStarted: true)). Docblock поясняет, что так законный владелец копии возвращает себе доступ, а подставить чужой templateId (IDOR) при этом нельзя. Что поменялось с правами на псевдодокумент WORKFLOW в прошлой версии bizproc, смотрите в разборе bizproc 26.1200.0.

REST-метод bizprocdesigner.template.list отдаёт только шаблон, к которому привязан токен агента, и теперь проверяет его через тот же canManageDocument(). На псевдотипе Workflow не-админ получит этот шаблон, только если это его собственная запущенная копия, для реальных типов по-прежнему решает CreateWorkflow.

Сверьте, кто может писать константы через мастер агента

В bizproc права на запись констант шаблона вынесли в политику. WorkflowTemplateService::setConstants() спрашивает canWrite(int $templateId, array $documentType, int $userId) у объекта TemplateConstantsAccessPolicyInterface, который передаётся четвёртым необязательным параметром конструктора. Без него подставляется PublishTemplateConstantsAccessPolicy, то есть прежняя проверка canPersist(..., publish: true), и код, который создаёт сервис по-старому, работает как раньше.

Мастер настройки AI-агента (CBPSetupTemplateActivity) теперь пишет константы через SetupTemplateConstantsService с политикой AgentSetupTemplateConstantsAccessPolicy. Шаблон должен существовать, принадлежать тому же типу документа и быть копией AI-агента (узловой шаблон без SYSTEM_CODE), а пользователь должен быть администратором или владельцем этой копии (ACTIVATED_BY). Это проверяет AiAgentProvider::canManageLaunchedTemplate(). Право публиковать шаблон мастер больше не спрашивает.

Сотрудник с правом публикации, но без админских прав и без своей копии агента, записать константы через мастер не сможет, а владелец копии без права публикации сможет.

Проверьте AUTO_EXECUTE, если вызывали migrateTemplate() сами

SequentialToNodeWorkflow::migrateTemplate() переносит последовательный шаблон в новый дизайнер на месте. До 26.1300 он оставлял шаблону прежний AUTO_EXECUTE. Если после конвертации у шаблона появлялся стартовый триггер создания или изменения документа, процесс стартовал бы дважды, по автозапуску и по триггеру. Об этом в коде висел TODO, из которого видно, что метод готовили под active migration. Штатный код migrateTemplate() не вызывает. Конвертация, которую дизайнер делает при открытии шаблона (TemplateToNodes), ставит ручной стартовый триггер ManualStartTrigger, и фикс её не касается.

Теперь при переносе getRemainingAutoExecute() снимает бит CBPDocumentEventType::Create или CBPDocumentEventType::Edit, если для этого события появился триггер. Срабатывает это только в момент переноса, а миграций и изменений схемы БД в релизе нет.

Если вы вызывали SequentialToNodeWorkflow::migrateTemplate() из своего кода до обновления, проверьте, не остался ли у перенесённых шаблонов автозапуск на событие, для которого уже есть стартовый триггер.

Включите узлы автомата состояний опцией, если они уже в схемах

StateNode и SetStateNode появились в bizproc 26.1100.0, а в 26.1300 их убрали из списка действий: оба получили setExcluded(!WorkflowStateGroupFeature::isAvailable()). Группу ActivityGroup::WORKFLOW_STATE прячет правило OptionGroupRule в bizproc/.settings.php. По умолчанию опция bizproc:workflow_state_group_available равна N, и пока её не переключат в Y, узлов в дизайнере не видно.

Уже сохранённые блоки вид не теряют. TemplateToNodes перестал отбрасывать исключённые действия, когда собирает карты иконок, поэтому у блока остаются иконка и цвет, а подпись по умолчанию приходит в defaultTitle.

Чтобы добавлять такие узлы в новые схемы, включите опцию:

        <?php declare(strict_types=1);

use Bitrix\Main\Config\Option;

Option::set('bizproc', 'workflow_state_group_available', 'Y');

    

Рядом в коде стоит комментарий «Still under internal rollout», так что включаете вы функциональность, которую Битрикс пока раскатывает у себя.

Пересмотрите скрипты вокруг bizproc:ai-agent-reverse

Это касается тех, кто собирает AI-агентов генератором bizproc. Команда bizproc:ai-agent-reverse теперь сначала собирает шаблон из полученного template.source.json в памяти и сравнивает с исходным. Если они расходятся, файл не пишется. Записать его всё равно можно с новым флагом --allow-lossy, тогда в источник добавляется "_partial": true, и bizproc:ai-agent-generate о такой пометке предупредит. У --verify сменился смысл. Заметки проверки печатаются всегда, а флаг просит вывести отчёт, даже когда сказать нечего. Ещё bizproc:ai-agent-generate теперь сообщает, если откат сгенерированных файлов не удался.

Поправьте вызовы внутренних классов

Публичные сигнатуры вне Internal в bizproc не менялись. Если вы опирались на внутренние классы или контроллеры дизайнера:

  • В генераторе AI-агентов (Internal\AI\Agent\Generator\...) удалён RoundTripReport, вместо него EquivalenceReport. У LinkGraph::__construct(array $links, array $knownNodes) второй параметр стал обязательным, у ParsedSetupBlock и WizardMeta появился обязательный constantElements. В ConstantConfig значение $default по умолчанию теперь '' вместо null, а $options стал ?array = null вместо array = []. ActivityNodeBuilder::build() принимает $descriptor, $innerActivityType и $activityTail.
  • Controller\Activity\Complex::loadSettingsAction() принимает ещё array $sources = [] и array $documentType = []. В ответе LoadSettingsResponseDto появились autofillMap, relationAction и relationPortRuleDtoDictionary, а filterSupported повторяет availableBlocks.filter.available через NodeFilterAvailability. Старому клиенту это не мешает.
  • Внутренний пайплайн AI-ассистента дизайнера получил параметр RequestSource $source (по умолчанию Rest или Marta, в зависимости от метода) в AgentDraftService::saveAndPush(), AiAssistantDraftConverterService::covertFromAgentInput(), AiAssistantWorkflowTemplateConverterService::convertFromTemplateArrayToAgentTemplate() и валидаторах. У AgentBlock новые поля memberBlockIds, frameColorName и frameContent, у Block появилось ?FrameData $frameData.

Не стройте логику на парах взаимозаменяемых триггеров

При сохранении узла Public\Command\Activity\Settings\SaveCommandHandler в bizprocdesigner разрешает сменить тип триггера только для пар, которые модуль-владелец документа объявил в ModuleSettings::getInterchangeableTriggerPairs(). На остальные вернётся ошибка TRIGGER_TYPE_TRANSITION_NOT_ALLOWED. Тип приводится к написанию из пары, чтобы в CBPActivity::createInstance не получилось CBPCBP.... Опираться на эту схему я бы не стал, потому что в bizprocdesigner 26.800 её заменили односторонним апгрейдом (подробности в разборе bizproc 26.1400.0).

Новое

bizproc

  • Страница грида хранилища обходится одним CUser::GetList вместо запроса на каждую ячейку. Это делает Internal\Service\User\UserProvider со статическим кешем пользователей (get(), prefetch()), на него же переведён CBPHelper.
  • Internal\Service\DocumentField\FieldValueFormatter (forProperty(), forType(), format(), prefetchUsers()) печатает значение одинаково в гриде хранилища bizproc.storage.item.list и в предпросмотре таблицы данных. Даты из хранилища он приводит к времени зрителя через ValueFieldConverter::fromStorage().
  • У провайдеров DataView CompositeDataSourceProvider, StorageDataSourceProvider и VariablesDataSourceProvider появился present(SourceRef $source, iterable $rows).
  • Public\Service\ActivityGroup\OptionGroupRule реализует HideableGroupRuleInterface и прячет группу действий, пока опция модуля не Y. Docblock советует регистрировать по экземпляру на группу в bizproc/.settings.php. Флаг группы workflow_state читается в одном месте, Internal\Config\WorkflowStateGroupFeature::isAvailable(), чтобы группа и её узлы не расходились в видимости.
  • Проверку «можно ли сохранить или опубликовать» вынесли из WorkflowTemplateService в Internal\Service\WorkflowTemplate\TemplatePersistAccessService. Для узловых шаблонов она смотрит ACL шаблона (canPublish, canEdit), для остальных canCreateWorkflow, а строку шаблона кеширует.
  • Генератор AI-агентов пополнился классами ActivityDescriptorConfig, ComplexActivityRules, DescriptorRegistryDrift, StrictActivityResolver, EquivalenceReport, NodeDescriptorExtractor, WizardConstantElement и TemplateComparator на 1 313 строк, у AgentTemplateGenerator есть getDescriptorWarnings(). Для трёх штатных агентов, bitrix_ai_coach, bitrix_ai_day_planner и bitrix_ai_project_pulse, в поставке лежат исходники template.source.json.

bizprocdesigner

  • Controller\Diagram::getLastRunValuesAction(int $templateId) отдаёт значения последнего завершённого запуска шаблона по ключам выражений инспектора. Пустой результат приходит JSON-объектом, без ошибки. На отсутствующий шаблон не-админ получает ACCESS_DENIED, «не найден» ему не показывают. getAction() теперь возвращает draftSavedAt, время последнего сохранения черновика.
  • В Internal\Config\Feature два новых флага. isReadableExpressionsAvailable() читает опцию bizprocdesigner:readable_expressions_available (по умолчанию N). У isLastRunValuesAvailable() своей опции нет, он повторяет LastValuesService::isAvailable() из bizproc. Коды фич readableExpressions и lastRunValues.
  • Internal\Entity\FrameData и NodeType::Frame добавляют подложку, блок схемы с цветом, выравниванием, текстом и файлами. Для внешнего REST-агента к ним приложены проверки членства блоков, геометрии после раскладки и пересечений подложек: FrameStyleSanitizer, FramePostLayoutChecker, AgentFrameMembershipValidator, FrameBlockMatcher, UndirectedConnectivity, AgentBlockGeometry, FrameGeometry. FramePostLayoutChecker вызывается и в template.validate, и в template.draft.push, чтобы вердикты совпадали.
  • Поля создаваемого связанного документа заполняются из выходов блоков-предков. Карту строит Internal\Service\RelationAutofillMatcher::buildAutofillMap(), доступность фильтра решает сервис bizprocdesigner.activity.node.filter.availability (NodeFilterAvailability) из .settings.php.
  • В JS появились режим реакции на изменение элемента (common-node-settings/ui/reaction-mode-selector/), «читаемые выражения» (readable-expression-*, readable-expressions-veneer.js), подсказки последних значений в инспекторе данных и autofill-form.js. В config.php расширения добавлены ui.draganddrop.draggable, ui.system.radiobutton и ui.system.radiobutton.vue.

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

  • CBPMailActivity::getConfirmedEmails() считает подтверждённым адрес, который Bitrix\Mail\Public\Service\Mailbox\AddressResolver сводит к активному IMAP-ящику, так что в «Отправить письмо» годятся дополнительные адреса ящика. Метод вызывается, только когда подключается модуль bitrix24. Код готов к старому mail: class_exists, потом method_exists с откатом на findBindings() и на прямой запрос к MailboxTable пачками по 100.
  • У «Тренера знаний» (bitrix_ai_coach) в template.source.json есть поток restart с триггером AiAgentRestartTrigger. Он состоит из мастера SetupTemplateActivity и повторного создания бота ImBotCreateBotActivity. template.json агента перегенерирован, метка в installer.php сменилась на 1786348402.
  • describeSourceWarning() в AiAgentReverse предупреждает о цене перегенерации. Ревизия агента считается хешем всего поставляемого шаблона, поэтому новый template.json предложит обновление каждой запущенной копии, а подтверждение перезапишет то, что настроили на портале. В комментариях там же встречается внутренний код задачи TPL-06.
  • Санитайзер BBCode подложки FrameStyleSanitizer можно брать за образец для любого BBCode, который приходит от REST-клиента или нейросети. Разрешены теги редактора b, i, u, s, list, *, url, quote, code, img, остальные [tag] превращаются в инертный текст через &#91;. В url и img схемы javascript:, vbscript: и data: обезвреживаются и в атрибуте, и в теле тега, причём после удаления символов \x00-\x20. Длина ограничена 20 000 символов, и обрезка не разрывает тег или HTML-сущность. Заголовок подложки сервер намеренно не экранирует, иначе, как сказано в docblock, «R&D» дошёл бы до пользователя как R&amp;D.
  • PresentableProvider в docblock просит отдавать подпись значения простым текстом, экранирует тот, кто выводит. Иначе получится двойное экранирование. PreviewService::applyPresenterOverlay() глотает исключение презентера и оставляет сырые значения, если источник стал недоступен.
  • ActivityVisibilityFilter показывает подложки (EmptyBlockActivity с NODE_TYPE=FRAME) внешнему REST-агенту и прячет их от внутреннего ассистента Марты. В комментарии это названо «the single deliberate divergence» двух предикатов.
  • В Complex::loadSettingsAction() автозаполнение fail-open, а права fail-closed. Без CreateWorkflow пользователь получает пустую карту автозаполнения, и настройки грузятся без ошибки. Права проверяются на тип документа, который определил сервер, ещё до чтения метаданных связей. Идентификаторы блоков ограничены [A-Za-z0-9_], и блоки с id вида MartaGeneratedId-* автозаполнения не получают, это прямо записано в комментарии.
  • ValidateSingleRuleCommand получил белый список типов конструкций для карточек связей. Без него прямой запрос мог положить base-settings или filter на порт, где таких блоков нет.
  • Старые admin-попапы bizprocdesigner admin/bizproc_activity_settings.php и admin/bizproc_wf_settings.php подключают main/include/epilog_admin_js.php, чтобы отложенные head-блоки попадали в ответ попапа (в комментарии «Task 722886»). Из списка доступных типов в bizproc_wf_settings.php убрали FieldType::CONDITIONGROUP.

Что делать

  1. Обновите bizprocdesigner до 26.700. В 26.600 getSettingsControlsAction() отдавал категории и стадии по любому присланному типу документа.
  2. Найдите в своих контроллерах documentType из запроса и убедитесь, что права на него проверяются до обращения к сервисам.
  3. Если своих AI-агентов подключают сотрудники без прав администратора, учтите, что после обновления владелец собственной запущенной копии снова может подключить к ней внешнего агента, а чужие копии ему по-прежнему закрыты.
  4. Если вы вызывали SequentialToNodeWorkflow::migrateTemplate() из своего кода до обновления, посмотрите AUTO_EXECUTE у перенесённых шаблонов.
  5. Если в схемах уже есть StateNode или SetStateNode, решите, включать ли workflow_state_group_available.

Новая версия вышла — разбор уже в Telegram

В канале @bxmax_updates публикуем каждый свежий разбор: что сломается, что устарело и что появилось в API. Узнаете до того, как обновите прод.

Подписаться

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

bizproc 26.1400.0 Ломающее Свежее

bizproc 26.1400.0 + bizprocdesigner 26.800.0: новые REST-методы внешнего AI-агента, дочерние действия в CBPActivity, история версий шаблонов

bizproc 26.1400.0 и bizprocdesigner 26.800.0 ставятся одним шагом. Внешнему AI-агенту дизайнера придётся сменить имена трёх методов и формат запросов, потому что его V3-контроллеры перевели с `ArrayResponse` на типизированные запросы и DTO. Свои действия из `/local/activities` стоит проверить на обн…

4 мин
bizproc 26.1100.0 Безопасность Свежее

Bizproc 26.1100.0: агент удаляет процессы, зависшие на паузе, а импорт смарт-скриптов проверяет действия

В bizproc 26.1100.0 импорт смарт-скриптов через REST-конфигурацию начал проверять действия шаблона от имени импортирующего пользователя. До этого шаблон сохранялся без `ValidateProperties`, и через импорт в него могло попасть действие `CBPCodeActivity`, которое эта проверка разрешает только админист…

2 мин
bizproc 26.1200.0 Безопасность Свежее

bizproc 26.1200.0: псевдодокумент WORKFLOW закрыт для не-админов, у узловых шаблонов появились роли

До bizproc 26.1200.0 псевдодокумент `WORKFLOW` разрешал `CreateWorkflow` и `WriteDocument` любому пользователю, и запись узловых шаблонов ничем не ограничивалась. Теперь эти операции доступны только администратору, а для узловых шаблонов появились роли на `Main\Access`, которые после обновления ни к…

5 мин
bizproc 26.1075.0 Рутинное Свежее

Bizproc 26.1075.0: строже ответ AI-коуча, кеш хранилищ без нулей

Почти весь релиз про встроенного AI-агента `bitrix_ai_coach`, который проводит тесты для сотрудников. В JSON-схеме ответа модели появились обязательные `questions_review`, `correct_count` и `total_count`, а промпт велит сверять их между собой перед выводом итога. Промпты при этом разошлись. Английск…

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

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

на связи

пишет…

Можно закрыть чат и перейти на другую страницу. Когда ответ будет готов, на кнопке Домового появится отметка.

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

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

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

Войти