From bea31af3e38389f887050abae4178892a2089524 Mon Sep 17 00:00:00 2001 From: Ryder Sun Date: Sat, 7 Nov 2026 20:58:03 +0800 Subject: [PATCH] Redact MiMo native configuration credentials Use native configuration layers to protect inline provider secrets in final and streaming output without adding them to the subprocess environment. Verified CLI, ACP and workspace tests and desktop packaging. --- .../src/agents/cli/cli-process-support.cjs | 6 +++++ desktop/src/agents/cli/mimo-native-config.cjs | 22 ++++++++++++++++++- .../agents/cli/cli-process-support.test.cjs | 16 ++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/desktop/src/agents/cli/cli-process-support.cjs b/desktop/src/agents/cli/cli-process-support.cjs index 33ea388..7adfc73 100644 --- a/desktop/src/agents/cli/cli-process-support.cjs +++ b/desktop/src/agents/cli/cli-process-support.cjs @@ -6,6 +6,7 @@ const { networkEnvironment } = require('./cli-network-environment.cjs') const { CONFIG_DIRECTORY_ENV_KEYS } = require('./cli-config-directories.cjs') const { SECRET_ENV_KEYS, nativeSecretEnvKeys } = require('./native-secret-environment.cjs') const { nativeCredentialEnvironment } = require('../agent-native-environment.cjs') +const { mimoConfigSecrets } = require('./mimo-native-config.cjs') const { redactChildSecrets } = require('./cli-runtime-events.cjs') const { agentRuntimeError, @@ -221,6 +222,11 @@ function childEnvironment(agent, workdir, options, platform) { PATH: searchPath({ platform, env: source }), } Object.defineProperty(childEnv, SECRET_ENV_KEYS, { value: nativeKeys }) + if (agent.kind === 'mimo') { + const home = platform === 'win32' ? (childEnv.USERPROFILE || childEnv.HOME) : childEnv.HOME + const secrets = mimoConfigSecrets({ home, env: childEnv, workdir }) + Object.defineProperty(childEnv, Symbol.for('meldwork.project-secret-values'), { value: secrets }) + } if (agent.kind === 'claude' && typeof workdir === 'string' && path.isAbsolute(workdir)) { const secrets = new Set() const home = platform === 'win32' ? (childEnv.USERPROFILE || childEnv.HOME) : childEnv.HOME diff --git a/desktop/src/agents/cli/mimo-native-config.cjs b/desktop/src/agents/cli/mimo-native-config.cjs index 4f9a306..4d93e00 100644 --- a/desktop/src/agents/cli/mimo-native-config.cjs +++ b/desktop/src/agents/cli/mimo-native-config.cjs @@ -155,4 +155,24 @@ function mimoConfiguredProvider(options = {}) { }) } -module.exports = { mimoConfigurationFingerprint, mimoConfiguredProvider, mimoDeclaredCredentialKeys } +function mimoConfigSecrets(options = {}) { + const configs = mimoConfigLayers(options) + if (!configs) return [] + const secrets = new Set() + const pending = configs.map(value => [value, 0]) + while (pending.length && secrets.size < 128) { + const [value, depth] = pending.pop() + if (!value || typeof value !== 'object' || depth > 32) continue + for (const [key, item] of Object.entries(value)) { + if (/(?:api[_-]?key|access[_-]?key|token|secret|password|authorization)$/i.test(key) + && typeof item === 'string' && item && item.length <= 8192 && !/^\{(?:env|file):[^}]+\}$/.test(item)) { + secrets.add(item) + const bearer = item.match(/^Bearer\s+(\S+)$/i) + if (bearer) secrets.add(bearer[1]) + } else if (item && typeof item === 'object') pending.push([item, depth + 1]) + } + } + return [...secrets] +} + +module.exports = { mimoConfigurationFingerprint, mimoConfiguredProvider, mimoDeclaredCredentialKeys, mimoConfigSecrets } diff --git a/desktop/test/agents/cli/cli-process-support.test.cjs b/desktop/test/agents/cli/cli-process-support.test.cjs index 433a469..4a8acd9 100644 --- a/desktop/test/agents/cli/cli-process-support.test.cjs +++ b/desktop/test/agents/cli/cli-process-support.test.cjs @@ -39,6 +39,22 @@ test('Claude project credentials are redacted from final and split streaming out assert.doesNotMatch(JSON.stringify(events), /project-private-value|local-private-value/) }) +test('MiMo native provider credentials are redacted from split streaming output', t => { + const workdir = fs.mkdtempSync(path.join(os.tmpdir(), 'meldwork-mimo-project-secret-')) + t.after(() => fs.rmSync(workdir, { recursive: true, force: true })) + fs.writeFileSync(path.join(workdir, 'mimocode.json'), JSON.stringify({ + model: 'custom/model', provider: { custom: { options: { apiKey: 'mimo-private-value' } } }, + })) + const child = childEnvironment({ kind: 'mimo' }, workdir, { env: { XDG_CONFIG_HOME: workdir } }, process.platform) + assert.equal(redactChildSecrets('mimo-private-value', child), '[redacted]') + const events = [] + const emitter = createRuntimeEventEmitter({ onEvent: event => events.push(event) }, child) + emitter.emit({ type: 'answer_delta', delta: 'mimo-private-' }) + emitter.emit({ type: 'answer_delta', delta: 'value' }) + emitter.finalize({ text: 'mimo-private-value', status: 'completed' }) + assert.doesNotMatch(JSON.stringify(events), /mimo-private-value/) +}) + test('Zcode receives its selected native home without changing other Agent homes', () => { const options = { env: { HOME: '/selected-zcode', USERPROFILE: 'C:\\selected-zcode' } } const unix = childEnvironment({ kind: 'zcode' }, '/tmp', options, 'darwin')