Bound native configuration alias traversal

Prevent cyclic or highly shared YAML configuration from stalling credential redaction or DeepSeek readiness checks. Checks: 164 agent tests; real DSH direct/group calls; desktop pack.
This commit is contained in:
Ryder Sun committed 2026-11-07 22:48:08 +08:00
1 parent e7c228cf43
commit 67b0f5db06
4 files changed
+23

No files matched your search

@@ -88,7 +88,9 @@ function deepseekDefaultCredentialConfigured(options = {}) {
if (!path.isAbsolute(root)) return false
const mapping = value => value && typeof value === 'object' && !Array.isArray(value)
&& (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null)
let remaining = 8192
const jsonValue = (value, seen = new Set(), depth = 0) => {
if (--remaining < 0) return false
if (value === null || typeof value === 'string' || typeof value === 'boolean') return true
if (typeof value === 'number') return Number.isFinite(value)
if (depth > 32 || (!Array.isArray(value) && !mapping(value)) || seen.has(value)) return false
@@ -20,9 +20,12 @@ function credentialLiteral(value) {
function configSecrets(configs) {
const secrets = new Set()
const pending = configs.map(value => [value, 0])
const visited = new WeakMap()
while (pending.length && secrets.size < 128) {
const [value, depth] = pending.pop()
if (!value || typeof value !== 'object' || depth > 32) continue
if (visited.has(value) && visited.get(value) <= depth) continue
visited.set(value, depth)
for (const [key, item] of Object.entries(value)) {
if ((/(?:api[_-]?key|access[_-]?key|token|secret|password|authorization)$/i.test(key)
|| (key === 'key' && value.type === 'api_key'))
@@ -4,6 +4,7 @@ const fs = require('node:fs')
const os = require('node:os')
const path = require('node:path')
const { EventEmitter } = require('node:events')
const { spawnSync } = require('node:child_process')
const { childEnvironment, failedAgentProcessError, terminateWindowsProcessTree } = require('../../../src/agents/cli/cli-process-support.cjs')
const { systemChildEnvironment } = require('../../../src/agents/cli/cli-discovery.cjs')
@@ -11,6 +12,17 @@ const { redactChildSecrets, createRuntimeEventEmitter } = require('../../../src/
const { nativeOpenClawOptions } = require('../../../src/agents/cli/openclaw-runtime.cjs')
const { workBuddyInstall } = require('../../support/cli-adapters-test-helpers.cjs')
test('native credential extraction handles cyclic and shared configuration references', () => {
const modulePath = require.resolve('../../../src/agents/cli/native-secret-environment.cjs')
const script = `const { configSecrets } = require(${JSON.stringify(modulePath)})
const root = { apiKey: 'synthetic-private-value' }
root.first = root; root.second = root
process.stdout.write(JSON.stringify(configSecrets([root])))`
const result = spawnSync(process.execPath, ['-e', script], { encoding: 'utf8', timeout: 3000 })
assert.equal(result.status, 0)
assert.deepEqual(JSON.parse(result.stdout), ['synthetic-private-value'])
})
test('Claude project credentials are redacted from final and split streaming output', t => {
const workdir = fs.mkdtempSync(path.join(os.tmpdir(), 'meldwork-claude-project-secret-'))
t.after(() => fs.rmSync(workdir, { recursive: true, force: true }))
@@ -30,6 +30,12 @@ test('DeepSeek recognizes its native default-route credential without exposing i
assert.equal(state().state, 'unknown')
write('version: 1\nrefs:\n ZGCI_API_KEY: synthetic-key\nrecords:\n invalid: {}\n')
assert.equal(state().state, 'unknown')
const aliases = [' n0: &n0 [x, x]']
for (let index = 1; index < 16; index += 1) {
aliases.push(` n${index}: &n${index} [*n${index - 1}, *n${index - 1}]`)
}
write(`version: 1\nrefs:\n ZGCI_API_KEY: synthetic-key\nrecords:\n other/provider:\n kind: grant\n payload:\n${aliases.join('\n')}\n`)
assert.equal(state().state, 'unknown')
write('version: 1\nrefs:\n ZGCI_API_KEY: synthetic-key\n')
if (process.platform !== 'win32') {
fs.chmodSync(file, 0o644)