QWEN_CODE_SYSTEM_SETTINGS_PATH / QWEN_CODE_SYSTEM_DEFAULTS_PATH: env override is honored without any file-ownership check
Affected: qwen-code v0.24.7 (verified; the same getSystemSettingsPath() shape is present in the 0.24.x bundles)
What
getSystemSettingsPath() (same for getSystemDefaultsPath()) reads the
QWEN_CODE_SYSTEM_SETTINGS_PATH / QWEN_CODE_SYSTEM_DEFAULTS_PATH environment
variables and, if set, returns that path unconditionally — the returned file
becomes the top system settings merge layer.
The real system settings location is a root/admin-only writable path
(/Library/Application Support/QwenCode/settings.json on macOS,
%ProgramData%\qwen-code\settings.json on Windows, /etc/qwen-code/ on
Linux). The whole point of that placement is that only the OS administrator can
set system-level settings (model provider, baseUrl, etc.). The env override
removes that trust boundary: any local user who can set environment
variables for a qwen process can point the CLI at an arbitrary file they own
and gain system-settings authority — no elevation, no admin rights needed.
Impact
System settings control model provider selection and model.baseUrl. An
attacker who can influence the environment of a qwen process (shared accounts,
parent launchers, cron wrappers, IDE terminal environments, any env
injection) can:
- Override which model provider is used — including defeating
organization-managed provider lockdowns;
- Route model traffic (and the configured API key) to an external endpoint.
A provider entry without baseUrl is accepted by the config, and the CLI
then uses the public default endpoint of that authType, so step 2 does not
require the attacker to know or type any URL.
Reproduction (v0.24.7)
- Create a user-owned file, e.g.
/tmp/sys.json:
{
"model": { "name": "x", "authType": "openai" },
"modelProviders": { "openai": [ { "id": "x" } ] }
}
- Run with the override and an env API key for the authType:
QWEN_CODE_SYSTEM_SETTINGS_PATH=/tmp/sys.json QWEN_API_KEY=whatever qwen -p "ping"
- The request is sent to the public default endpoint for the
openai authType
(observed: api.dashscope.aliyuncs.com), not to the configured/managed
endpoint.
Control: the same file with an explicit external baseUrl is also honored —
both shapes work, i.e. the gate on explicit baseUrl does not imply a gate on
the file origin.
Suggested behavior
Treat an env-supplied system settings path with the same trust as the real
system path. Minimal options (any one):
- Honor the override only if the file is a regular file (lstat, not a
symlink) owned by root (or the equivalent admin group on Windows — e.g.
under C:\ProgramData\qwen-code\); otherwise fall back to the default path
and log a warning;
- or do not allow the env override to be used for the system layer at all
(keep it, if needed, for a dedicated dev/test flag).
Happy to provide more detail on the merge-layer precedence if useful.
QWEN_CODE_SYSTEM_SETTINGS_PATH / QWEN_CODE_SYSTEM_DEFAULTS_PATH: env override is honored without any file-ownership check
Affected: qwen-code v0.24.7 (verified; the same
getSystemSettingsPath()shape is present in the 0.24.x bundles)What
getSystemSettingsPath()(same forgetSystemDefaultsPath()) reads theQWEN_CODE_SYSTEM_SETTINGS_PATH/QWEN_CODE_SYSTEM_DEFAULTS_PATHenvironmentvariables and, if set, returns that path unconditionally — the returned file
becomes the top system settings merge layer.
The real system settings location is a root/admin-only writable path
(
/Library/Application Support/QwenCode/settings.jsonon macOS,%ProgramData%\qwen-code\settings.jsonon Windows,/etc/qwen-code/onLinux). The whole point of that placement is that only the OS administrator can
set system-level settings (model provider, baseUrl, etc.). The env override
removes that trust boundary: any local user who can set environment
variables for a
qwenprocess can point the CLI at an arbitrary file they ownand gain system-settings authority — no elevation, no admin rights needed.
Impact
System settings control model provider selection and
model.baseUrl. Anattacker who can influence the environment of a qwen process (shared accounts,
parent launchers, cron wrappers, IDE terminal environments, any
envinjection) can:
organization-managed provider lockdowns;
A provider entry without
baseUrlis accepted by the config, and the CLIthen uses the public default endpoint of that authType, so step 2 does not
require the attacker to know or type any URL.
Reproduction (v0.24.7)
/tmp/sys.json:{ "model": { "name": "x", "authType": "openai" }, "modelProviders": { "openai": [ { "id": "x" } ] } }QWEN_CODE_SYSTEM_SETTINGS_PATH=/tmp/sys.json QWEN_API_KEY=whatever qwen -p "ping"openaiauthType(observed:
api.dashscope.aliyuncs.com), not to the configured/managedendpoint.
Control: the same file with an explicit external
baseUrlis also honored —both shapes work, i.e. the gate on explicit baseUrl does not imply a gate on
the file origin.
Suggested behavior
Treat an env-supplied system settings path with the same trust as the real
system path. Minimal options (any one):
symlink) owned by root (or the equivalent admin group on Windows — e.g.
under
C:\ProgramData\qwen-code\); otherwise fall back to the default pathand log a warning;
(keep it, if needed, for a dedicated dev/test flag).
Happy to provide more detail on the merge-layer precedence if useful.