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.
This commit is contained in:
Ryder Sun committed 2026-11-07 20:58:03 +08:00
1 parent f2d4cf9c64
commit bea31af3e3
3 files changed
+43 -1

No files matched your search

@@ -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
+21 -1
View File
@@ -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 }
@@ -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')