Match Qwen project trust defaults

Resolve project credentials with the same default and explicit denial behavior as Qwen CLI, while treating malformed trust configuration as unverified. Verified focused readiness and security tests and desktop packaging.
This commit is contained in:
Ryder Sun committed 2026-11-07 20:35:08 +08:00
1 parent a7c3d3a84d
commit 335bbec0dc
2 files changed
+33 -5

No files matched your search

+11 -4
View File
@@ -43,14 +43,21 @@ function qwenNativeSelection(options = {}) {
const stat = fs.statSync(trustFile)
const rules = stat.isFile() && stat.size <= 1024 * 1024 ? JSON5.parse(fs.readFileSync(trustFile, 'utf8')) : null
const target = path.resolve(options.workdir)
includeProject = Boolean(rules && typeof rules === 'object' && !Array.isArray(rules)
&& Object.entries(rules).some(([folder, level]) => {
if (!rules || typeof rules !== 'object' || Array.isArray(rules)) return { keys: [], unsupported: true }
const entries = Object.entries(rules)
const trusted = entries.some(([folder, level]) => {
if (!path.isAbsolute(folder) || !['TRUST_FOLDER', 'TRUST_PARENT'].includes(level)) return false
const root = path.resolve(level === 'TRUST_PARENT' ? path.dirname(folder) : folder)
const relative = path.relative(root, target)
return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative))
}))
} catch { includeProject = false }
})
const denied = entries.some(([folder, level]) => level === 'DO_NOT_TRUST'
&& path.isAbsolute(folder) && path.normalize(folder) === path.normalize(target))
includeProject = trusted || !denied
} catch (error) {
if (error.code !== 'ENOENT') return { keys: [], unsupported: true }
includeProject = true
}
}
for (const entry of [defaults, user, ...(includeProject && project ? [project] : []), system]) {
const settings = configs.get(entry)
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict')
const fs = require('node:fs')
const os = require('node:os')
const path = require('node:path')
const { qwenDeclaredCredentialKeys } = require('../../../src/agents/cli/qwen-native-config.cjs')
const { qwenDeclaredCredentialKeys, qwenCredentialSelection } = require('../../../src/agents/cli/qwen-native-config.cjs')
const { nativeCredentialEnvironment } = require('../../../src/agents/agent-native-environment.cjs')
const { childEnvironment } = require('../../../src/agents/cli/cli-process-support.cjs')
const { redactChildSecrets } = require('../../../src/agents/cli/cli-runtime-events.cjs')
@@ -79,11 +79,32 @@ test('Qwen does not read a project credential declaration when folder trust deni
modelProviders: { openai: [{ id: 'chosen', envKey: 'PROJECT_TOKEN' }] },
}))
const env = { QWEN_CODE_SYSTEM_SETTINGS_PATH: path.join(home, 'missing.json') }
fs.writeFileSync(path.join(home, '.qwen', 'trustedFolders.json'), JSON.stringify({ [workdir]: 'DO_NOT_TRUST' }))
assert.deepEqual(qwenDeclaredCredentialKeys({ home, workdir, env }), [])
fs.writeFileSync(path.join(home, '.qwen', 'trustedFolders.json'), JSON.stringify({ [workdir]: 'TRUST_FOLDER' }))
assert.deepEqual(qwenDeclaredCredentialKeys({ home, workdir, env }), ['PROJECT_TOKEN'])
})
test('Qwen follows native folder trust defaults and rejects malformed trust files', t => {
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'meldwork-qwen-default-trust-'))
t.after(() => fs.rmSync(home, { recursive: true, force: true }))
const workdir = path.join(home, 'project')
fs.mkdirSync(path.join(home, '.qwen'))
fs.mkdirSync(path.join(workdir, '.qwen'), { recursive: true })
fs.writeFileSync(path.join(home, '.qwen', 'settings.json'), JSON.stringify({ security: { folderTrust: { enabled: true } } }))
fs.writeFileSync(path.join(workdir, '.qwen', 'settings.json'), JSON.stringify({
model: { name: 'chosen' }, security: { auth: { selectedType: 'openai' } },
modelProviders: { openai: [{ id: 'chosen', envKey: 'PROJECT_TOKEN' }] },
}))
const env = { QWEN_CODE_SYSTEM_SETTINGS_PATH: path.join(home, 'missing.json') }
const trustFile = path.join(home, '.qwen', 'trustedFolders.json')
assert.deepEqual(qwenDeclaredCredentialKeys({ home, workdir, env }), ['PROJECT_TOKEN'])
fs.writeFileSync(trustFile, JSON.stringify({ [workdir]: 'DO_NOT_TRUST' }))
assert.deepEqual(qwenDeclaredCredentialKeys({ home, workdir, env }), [])
fs.writeFileSync(trustFile, '{ malformed')
assert.deepEqual(qwenCredentialSelection({ home, workdir, env }), { keys: [], unsupported: true })
})
test('Qwen system configuration takes precedence and unsafe credential names are rejected', t => {
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'meldwork-qwen-env-precedence-'))
t.after(() => fs.rmSync(home, { recursive: true, force: true }))