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

Bizproc 26.1050.0: ID бизнес-процесса проверяется по формату, а Starter его больше не пропускает

2 мин чтения

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

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

В bizproc 26.1050.0 заранее заданный ID экземпляра бизнес-процесса (PreGeneratedWorkflowId) теперь проверяется по формату в четырёх местах, а из параметров, которые собирает Bitrix\Bizproc\Starter\Parameters, вырезается совсем. Тем же релизом в нескольких сырых SQL-запросах модуля убрали склейку WORKFLOW_ID строками. Для тех, кто пишет свои активности, есть новый трейт подписки на события других модулей и событие, через которое можно подложить ядру свои шаблоны нод. Сигнатуры и схема БД не менялись.

Проверьте, откуда берётся PreGeneratedWorkflowId

CBPDocument::PARAM_PRE_GENERATED_WORKFLOW_ID (строка 'PreGeneratedWorkflowId') позволяет передать в запуск заранее известный ID процесса. Раньше ядро брало любое переданное значение. Теперь формат проверяет новый CBPRuntime::isValidWorkflowId():

        return is_string($workflowId) && preg_match('/^[0-9a-f]{13}\d\.\d{8}$/D', $workflowId) === 1;

    

Это ровно формат uniqid('', true), который возвращает CBPRuntime::generateWorkflowId(). В PHPDoc метод назван единственным источником правды о формате, менять его велено только вместе с генератором.

  • CBPDocument::startWorkflow() и startDebugWorkflow() молча заменяют невалидный ID на сгенерированный;
  • CBPRuntime::createWorkflow() и createDebugWorkflow() делают то же и вдобавок удаляют ключ из параметров процесса;
  • CBPStateService::addWorkflow() бросает Exception("InvalidWorkflowId");
  • Bitrix\Bizproc\Starter\Parameters::getValues() вырезает ключ всегда, с комментарием в коде «the client must never choose the workflow instance id».

Если ваш код передаёт в запуск собственный ID другого формата (UUID, ID из внешней системы), процесс получит не тот ID, который вы сохранили у себя, и ядро об этом не сообщит. Если вы зовёте CBPStateService::addWorkflow() напрямую со своими ID, получите исключение.

Заранее известный ID теперь берите у ядра:

        <?php declare(strict_types=1);

use Bitrix\Main\Loader;

Loader::requireModule('bizproc');

$workflowId = \CBPRuntime::generateWorkflowId();
// сохраните $workflowId у себя до запуска, формат гарантированно пройдёт проверку

$errors = [];
\CBPDocument::startWorkflow(
    $templateId,
    $documentId, // комплексный ID документа: [модуль, класс документа, ID]
    [\CBPDocument::PARAM_PRE_GENERATED_WORKFLOW_ID => $workflowId],
    $errors,
);

    

Если ID процесса приходит к вам из запроса, прогоняйте его через \CBPRuntime::isValidWorkflowId() до того, как он попадёт в выборку или SQL.

Не склеивайте ID в SQL

В том же релизе в модуле переписали несколько запросов, куда WORKFLOW_ID попадал склейкой строк:

  • ClearFilterAgent::clear() и WorkflowUserTable::onDocumentDelete() удаляют записи через deleteByFilter(['@WORKFLOW_ID' => ...]) вместо DELETE ... IN('...') из implode; в WorkflowFilterTable и WorkflowUserTable подключили DeleteByFilterTrait;
  • CreateUserFilterStepper собирает INSERT IGNORE ... WHERE ws.ID IN (?@) через SqlExpression;
  • в SchedulerEventTable, WorkflowDurationStatTable, WorkflowInstanceTable и WorkflowStateTable имена таблиц в сырых запросах экранирует quote(), а не forSql().

Убрали склейку не везде. SchedulerEventTable::deleteBySubscription() и deleteByWorkflow() по-прежнему строят WHERE WORKFLOW_ID = '{$workflowId}' со значением через forSql(), в них поменялось только экранирование имени таблицы.

Проверка формата ID и уход от склейки пришли в одном релизе и похожи на закрытие одной цепочки, в которой выбранный клиентом ID мог доехать до сырого SQL. Это наш вывод из характера правок.

Тот же приём для таблицы вашего модуля:

        <?php declare(strict_types=1);

use Bitrix\Main\Application;
use Bitrix\Main\DB\SqlExpression;

// ?@ на пустом списке бросает ArgumentException
if ($workflowIds !== []) {
    Application::getConnection()->query(new SqlExpression(
        'DELETE FROM ?# WHERE WORKFLOW_ID IN (?@)',
        'b_vendor_workflow_log',
        $workflowIds,
    ));
}

    

Подпишите активность на события другого модуля

Новый трейт Bitrix\Bizproc\Activity\Mixins\ExternalEventSubscriptionTrait берёт на себя подписку активности через SchedulerService на события чужого модуля, необязательный таймаут и проверку «моё ли это событие». Все методы, кроме приватного помощника extractFirstStringParam(), объявлены protected:

  • subscribeOnExternalEvents($moduleId, $eventNames, $hash, $timeoutSeconds = 0) подписывает на каждое событие из списка, при положительном таймауте ставит ещё и таймер;
  • unsubscribeFromExternalEvents($moduleId, $eventNames) снимает и то и другое;
  • isExternalEventIrrelevant($eventParams, $eventNames) отсекает события для закрытой активности или активности без подписки, чужие имена и чужой хеш; срабатывание таймера проходит, если активность ещё не закрыта и подписка есть;
  • isExternalEventTimeout(), extractEventObject(), extractEventError(), switchExternalEventSubscriptionTrait() для переподписки на новый хеш и reInitializeEventSubscription() для сброса состояния.

Хеш сверяется с первым строковым позиционным аргументом события, и именно там ваше событие должно передавать ключ. Набросок активности, которая ждёт подтверждения оплаты от модуля vendor.payments:

        <?php declare(strict_types=1);

use Bitrix\Bizproc\Activity\Mixins\ExternalEventSubscriptionTrait;

class CBPVendorWaitPaymentActivity extends CBPActivity implements IBPActivityExternalEventListener
{
    use ExternalEventSubscriptionTrait;

    private const MODULE = 'vendor.payments';
    private const EVENTS = ['OnPaymentConfirmed'];

    public function __construct($name)
    {
        parent::__construct($name);
        $this->arProperties = ['Title' => '', 'PaymentId' => '', 'IsPaid' => 'N'];
    }

    public function execute()
    {
        $this->subscribeOnExternalEvents(self::MODULE, self::EVENTS, (string)$this->PaymentId, 3600);

        return CBPActivityExecutionStatus::Executing;
    }

    public function onExternalEvent($arEventParameters = [])
    {
        if ($this->isExternalEventIrrelevant($arEventParameters, self::EVENTS)) {
            return;
        }

        $this->unsubscribeFromExternalEvents(self::MODULE, self::EVENTS);
        $this->IsPaid = $this->isExternalEventTimeout($arEventParameters) ? 'N' : 'Y';
        $this->workflow->closeActivity($this);
    }
}

    

В этом релизе ядро трейт ещё нигде не использует, в диффе он лежит сам по себе.

Подложите свои шаблоны нод

Bitrix\Bizproc\Public\Service\Template\NodesInstallerService::syncSection() раньше ставил шаблоны только из собственного каталога nodes/<раздел> модуля и выходил, если каталога нет. Теперь он дополнительно шлёт событие bizproc:onGetExternalNodesDirs с параметром sectionId и забирает из успешных ответов массив путей dirs. Несуществующие каталоги пропускаются, каждый подкаталог раздела ставится тем же installFromDir(), что и штатные шаблоны.

        <?php declare(strict_types=1);

namespace Vendor\Module\Internals\Integration\Bizproc;

use Bitrix\Main\Event;
use Bitrix\Main\EventResult;

final class NodesDirsHandler
{
    public static function onGetExternalNodesDirs(Event $event): EventResult
    {
        // /local/modules/vendor.module/nodes/<sectionId>
        $dir = dirname(__DIR__, 4) . '/nodes/' . $event->getParameter('sectionId');

        return new EventResult(EventResult::SUCCESS, ['dirs' => [$dir]], 'vendor.module');
    }
}

    

Регистрируйте обработчик в install/index.php модуля через EventManager::getInstance()->registerEventHandler('bizproc', 'onGetExternalNodesDirs', 'vendor.module', NodesDirsHandler::class, 'onGetExternalNodesDirs') и снимайте при удалении.

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

  • Метод трейта switchExternalEventSubscriptionTrait() назван с суффиксом Trait, остальные методы трейта без него.
  • В HiddenAiAgentsRegistry появился третий скрытый отовсюду AI-агент, bitrix_crm_call_assessment, рядом с bitrix_ai_day_planner и bitrix_booking_ai_call.
  • В install/version.php убрали пустую строку после <?php и висячую запятую. Дата сборки — 30 июля 2026.

Что делать

  1. Найдите в своём коде PARAM_PRE_GENERATED_WORKFLOW_ID и PreGeneratedWorkflowId. Если ID генерируете сами, переходите на CBPRuntime::generateWorkflowId().
  2. Проверьте прямые вызовы CBPStateService::addWorkflow(): невалидный ID теперь бросает исключение.
  3. ID процесса, пришедший из запроса, проверяйте CBPRuntime::isValidWorkflowId().
  4. Если пишете активность, которая ждёт событий другого модуля, посмотрите на ExternalEventSubscriptionTrait вместо самописной подписки.

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

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

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

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

1 мин

bizproc 26.900.0: установщик на миграциях и защита от unSign с мусором на входе

Релиз без единого изменения публичного API — `api.diff` пустой. Всё интересное в установщике: модуль переехал на `UpdateSystem\Migration`. Плюс пачка проверок типов там, где раньше подписанные значения из запроса шли в разбор как есть.

3 мин

bizproc 26.800.0: у шаблона появился источник происхождения, а ProcessStarter стал final

Бизнес-процессы готовят почву под шаблоны, которые создаёт не человек: в таблице шаблонов появилась колонка «откуда он взялся», запуск процессов вынесен в абстрактный класс, а сам `ProcessStarter` закрыт от наследования. В батче едет `bizprocdesigner 26.400.0`.

4 мин
bizproc 26.1000.0 Безопасность

bizproc 26.1000.0: активности научились проверять права на целевой документ, ScopeTokenService удалён

Самый содержательный из трёх подряд релизов бизнес-процессов. Появились публичные контракты, по которым активность резолвит «над каким документом я работаю» и **проверяет права на него**. Удалён `ScopeTokenService`, версия API активностей поднята с 2 до 3, у хранилища появились лимиты.

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

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

на связи

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

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

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

Войти