translate · Перевод 26.100.0 Безопасность

Translate 26.100.0: импорт CSV и редактор переводов больше не пишут за пределы разрешённых папок

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

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

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

В Translate 26.100.0 закрыли выход за пределы разрешённых папок при записи языковых файлов через редактор и CSV-импорт модуля «Перевод». Обновиться стоит всем, у кого модуль установлен. Публичный API не ломается, но CSV-импорт теперь пропускает файлы с путями вне разрешённых папок.

Новое

Проверяйте путь к lang-файлу одним вызовом

Bitrix\Translate\IO\Path::resolveAllowedLangFile(string $rawPath, string $languageId): Main\Result принимает сырой путь (например, из колонки CSV) и код языка. Метод приводит путь к итоговому виду, подставляет код языка и проверяет результат. Файл должен лежать в lang-папке именно этого языка и быть .php, путь должен входить в папки из настройки INIT_FOLDERS и не вести в служебные папки из Translate\IGNORE_BX_NAMES (/bitrix/backup, /bitrix/cache, /bitrix/updates и другие). В успешном результате getData() вернёт path и normalizedPath, второй не зависит от языка. При отказе в результате одна ошибка с кодом из новых констант класса:

  • Path::ERROR_INVALID_PATH: путь не удаётся привести к итоговому виду;
  • Path::ERROR_NOT_LANG_FILE: файл не в lang-папке нужного языка или не .php;
  • Path::ERROR_PATH_NOT_ALLOWED: путь вне INIT_FOLDERS или внутри служебной папки.

Если у вас свой импорт переводов или генератор lang-файлов, собственные проверки путей можно заменить этим методом. Пути для всех языков разрешайте до первой записи, как теперь делает редактор модуля:

        <?php declare(strict_types=1);

namespace Vendor\I18n\Application\Service;

use Bitrix\Main\Application;
use Bitrix\Main\Error;
use Bitrix\Main\Loader;
use Bitrix\Main\Localization\Translation;
use Bitrix\Main\Result;
use Bitrix\Translate\IO\Path;

final class LangFilePathResolver
{
    /**
     * @param list<string> $languageIds
     * @return Result data: код языка => абсолютный путь к lang-файлу
     */
    public function resolveAll(string $rawPath, array $languageIds): Result
    {
        $result = new Result();
        if (!Loader::includeModule('translate')) {
            return $result->addError(new Error('Модуль translate не установлен', 'TRANSLATE_MISSING'));
        }

        $documentRoot = rtrim(Application::getDocumentRoot(), '/');
        $paths = [];

        foreach ($languageIds as $languageId) {
            $resolved = Path::resolveAllowedLangFile($rawPath, $languageId);
            $error = $resolved->getError();

            if ($error !== null) {
                $reason = match ($error->getCode()) {
                    Path::ERROR_PATH_NOT_ALLOWED => 'папка вне INIT_FOLDERS или служебная',
                    Path::ERROR_NOT_LANG_FILE => 'не php-файл в lang-папке языка',
                    default => 'путь не разбирается',
                };

                // один отказ, и не пишем ни один язык
                return $result->addError(new Error("{$rawPath} [{$languageId}]: {$reason}", $error->getCode()));
            }

            $paths[$languageId] = Translation::convertLangPath(
                $documentRoot . $resolved->getData()['path'],
                $languageId,
            );
        }

        return $result->setData($paths);
    }
}

    

Путь в path метод отдаёт от корня сайта, поэтому DOCUMENT_ROOT приклеиваем сами. Потом, как и модуль, пропускаем результат через Translation::convertLangPath(), ведь с репозиторием переводов файл языка лежит не рядом с модулем.

Что сломается

Проверьте INIT_FOLDERS перед импортом CSV

Импорт (Controller\Import\ImportCsv) теперь прогоняет каждую пару «файл + язык» через resolveAllowedLangFile(). Пара «файл + язык» не импортируется, если путь выходит за INIT_FOLDERS, ведёт в служебную папку или не указывает на .php в lang-папке нужного языка.

Импорт при этом не останавливается. Отклонённые пути приходят в ответе шага в ключе WARNING, не больше десяти, дальше «...».

По умолчанию в INIT_FOLDERS указано /bitrix/,/local/. Если вы переводите через CSV lang-файлы вне этих папок, например публичной части сайта, импорт теперь их пропустит. Добавьте нужную папку в настройки модуля «Перевод».

Папки в INIT_FOLDERS сравниваются по границе

Bitrix\Translate\Permission::isAllowPath() теперь сравнивает пути по границе папки: путь должен совпасть с папкой из INIT_FOLDERS или лежать внутри неё. Если в INIT_FOLDERS указано /local/my, файлы из /local/mymodule/ больше не считаются разрешёнными. Класс помечен @internal, так что из своего кода его лучше не вызывать.

Тем, кто рассчитывал на старое сравнение, придётся вписать такие папки в настройку целиком.

Свой белый список папок стоит проверить на ту же ошибку. Сверяйте в нём путь после всех подстановок, тот самый, по которому потом будете писать. Вариант с границей папки:

        <?php declare(strict_types=1);

namespace Vendor\Exchange\Domain;

use Bitrix\Main\IO\InvalidPathException;
use Bitrix\Main\IO\Path;

final class AllowedFolders
{
    /** @param list<string> $folders например ['/upload/import'] */
    public function __construct(private readonly array $folders) {}

    public function contains(string $path): bool
    {
        try {
            $path = Path::normalize($path);
        } catch (InvalidPathException) {
            return false;
        }

        if ($path === null) {
            return false;
        }

        foreach ($this->folders as $folder) {
            $folder = rtrim($folder, '/');
            // /upload/import-old начинается с /upload/import, но лежит рядом, а не внутри
            if ($path === $folder || str_starts_with($path, $folder . '/')) {
                return true;
            }
        }

        return false;
    }
}

    

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

  • Импорт больше не обрывается на первой плохой строке. Заодно починили счётчик ошибок, из-за которого одна ошибка на шаге пропускала все следующие файлы этого шага.
  • Редактор (SaveFile, SaveSource, WipeEmpty) проверяет путь для каждого языка до первой записи. Если хоть один не прошёл, редактор ничего не меняет в этом файле и возвращает ошибку.
  • Deprecated в релизе нет, схема БД не менялась. Версия выросла с 26.0.0, VERSION_DATE у новой 2026-09-01.

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

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

Подписаться

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

vote 26.300.0 Безопасность Свежее

Vote 26.300.0: список проголосовавших отдаётся только по подписи

В Vote 26.300.0 закрыли утечку списка проголосовавших через попап «Кто проголосовал», в том числе в анонимных опросах, и ужесточили права на удаление и сброс опросов в админском списке. Обновиться стоит всем, у кого на портале есть опросы. Если вы копировали к себе шаблон `.userfield` компонента `vo…

4 мин
timeman 26.300.0 Ломающее Свежее

Timeman 26.200.0–26.300.0: выключенный «Учёт рабочего времени» отключает REST timeman.*, перерывы за период отдаёт RecordProvider

Timeman 26.200.0 и 26.300.0 пришли в коробочный Битрикс24 в один прогон. В 26.200.0 модуль начал слушаться настройки портала: если инструмент «Учёт рабочего времени» выключен, перестают работать REST `timeman.*`, контроллеры и компоненты модуля, а уведомления не уходят. В 26.300.0 появился публичный…

2 мин
tasks 26.350.0 Рутинное Свежее

Tasks 26.350.0: NEW_COMMENTS_COUNT читается точечно

В tasks 26.350.0 модуль иначе читает `NEW_COMMENTS_COUNT`, значения при этом прежние. Это касается `CTaskItem::fetchList()` и `fetchListArray()` с этим полем в select, а значит и `Manager\Task::getList()` с REST-методом `tasks.task.list`, и REST-метода `tasks.task.get`. Раньше ради этого поля `Count…

1 мин
superset 26.500.0 Заметное Свежее

Superset 26.500.0: проверка готовности сервера и повтор GET после быстрого 5xx

Superset 26.500.0 из коробочного Битрикс24 нужен тем, кто обращается из своего кода к собственному (self-hosted) серверу BI Конструктора. У публичных провайдеров два новых метода: `ServerProvider::checkReadiness()` проверяет, что Superset видит свою базу метаданных, а `DatasetProvider::listPhysical(…

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

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

на связи

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

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

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

Войти