Политика разрешений для shell-сессий
Проблема
POST /session/:id/shell выполняет shell-команду напрямую через демон,
без вызова инструмента LLM или стандартного потока посредничества в разрешениях агента. До
этого изменения эндпоинт был нестрогой мутацией и был доступен по токену демона вместе с
id сессии, либо через loopback-интерфейс разработчика по умолчанию без токена.
Это слишком широкие полномочия для прямого shell-интерфейса. Вызывающая сторона не должна иметь возможности выполнять shell-команды, если оператор демона явно не включит этот интерфейс и вызывающая сторона не докажет, что она привязана к целевой сессии.
Цели
- Отключить прямой shell сессии по умолчанию.
- Требовать явного включения оператором с помощью
qwen serve --enable-session-shell. - Требовать настройки bearer-токена перед тем, как включение вступит в силу.
- Требовать client id, зарегистрированный в адресуемой сессии.
- Применять ту же политику в REST-маршруте, HTTP-диспетчере ACP и sink-е выполнения bridge.
- Оставить стандартные подтверждения shell-инструментов агента и посредничество в разрешениях без изменений.
Нецели
- Не маршрутизировать прямой shell через
PermissionMediator. - Не изменять отправку промптов, постановку промптов в очередь или поведение ожидающих промптов SDK.
- Не добавлять специфичный для shell rate limiter.
- Не добавлять алиас переменной окружения для флага включения.
Проектирование
runQwenServe один раз разрешает и обрезает bearer-токен. После этого вычисляется
одно итоговое булево значение:
sessionShellCommandEnabled =
opts.enableSessionShell === true && token !== undefined;Это значение передается в bridge, REST-приложение и ACP-диспетчер. Встроенные
вызывающие стороны, которые напрямую вызывают createServeApp, вычисляют наличие токена
с помощью проверки на непустую строку, чтобы token: '' вел себя как отсутствие токена
как для строгого контроля мутаций, так и для анонсирования возможностей shell.
REST-маршрут использует mutate({ strict: true }). В демоне без токена на
loopback-интерфейсе строгий контроль возвращает 401 token_required до запуска обработчика. Когда
токен настроен, обработчик отклоняет отключенный shell с ошибкой
session_shell_disabled, затем требует X-Qwen-Client-Id, затем проверяет
тело команды и, наконец, делегирует выполнение bridge.
ACP-диспетчер сохраняет возможность диспетчеризации _qwen/session/shell для старых клиентов, но
не анонсирует её в списке _qwen.methods при инициализации, если
эффективная политика не включена. Отключенные ACP-вызовы возвращают стабильную
JSON-RPC ошибку session_shell_disabled без логирования команды или вызова
bridge. Включенные вызовы по-прежнему требуют, чтобы соединение владело сессией и
должны использовать client id привязки к сессии, установленный bridge-ом.
Bridge применяет финальную проверку защиты в глубину в
executeShellCommand(): отключено, отсутствует client id, неизвестная сессия, затем
непривязанный client id. Только после прохождения этих проверок он публикует shell-события,
выполняет команду или записывает историю shell.
Контракт ошибок
REST:
- нет токена:
401,code: token_required - отключено:
403,code/errorKind: session_shell_disabled - отсутствует client id:
403,code/errorKind: client_id_required - некорректный или непривязанный client id: существующая
400 invalid_client_id - неизвестная сессия: существующий маппинг
404 SessionNotFoundError
ACP:
- отключено:
RPC.INVALID_REQUEST,data.errorKind: session_shell_disabled - отсутствует client id привязки к сессии:
RPC.INVALID_REQUEST,data.errorKind: client_id_required - сессия без владельца и невалидный client id сохраняют существующие маппинги JSON-RPC
Совместимость
DaemonSessionClient.shellCommand() продолжает работать, когда демон
явно включен и аутентифицирован, поскольку клиент сессии несет в себе
привязанный к сессии client id. Базовый вызов DaemonClient.shellCommand(sessionId, command)
должен передавать opts.clientId, иначе он получит client_id_required.
Покрытие тестами
Реализация покрыта целевыми тестами bridge, REST, транспорта ACP, запуска serve и парсера команд. Наиболее ценные проверки — это поведение с отключенным состоянием по умолчанию, строгий контроль без токена, анонсирование возможностей, фильтрация методов инициализации ACP, контроль sink-а bridge и распространение привязанного к сессии client id.