Translate 26.100.0: импорт CSV и редактор переводов больше не пишут за пределы разрешённых папок
Обновление безопасности
Закрыта уязвимость или ослабленная проверка прав. Ставить в первую очередь.
В 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.