fix: resolve local TTS SecretRefs before conversion#72549
Conversation
🔒 Aisle Security AnalysisWe found 1 potential security issue(s) in this PR:
1. 🟠 Potential arbitrary command execution via SecretRef exec provider during `infer tts convert` secret resolution
Description
If the gateway is unavailable or its snapshot is incomplete, Why this is a security risk:
This can lead to executing attacker-controlled binaries/scripts referenced in config (e.g., an RecommendationTreat Suggested mitigations (pick one or combine):
await resolveCommandSecretRefsViaGateway({
config: loadConfig(),
commandName: "infer tts convert",
targetIds: getTtsCommandSecretTargetIds(),
mode: "enforce_resolved",
// new option: disallowLocalExecProviders: true
});
if (providerConfig.source === "exec") {
throw new Error("Exec secret providers are disabled for this command unless --allow-exec-secrets is set");
}
Analyzed PR: #72549 at commit Last updated on: 2026-04-27T03:19:04Z |
Greptile SummaryRoutes local Confidence Score: 5/5This PR is safe to merge — the fix is narrow, well-tested, and follows the existing SecretRef resolution pattern used by other command paths. Changes are tightly scoped: a single call-site switch from No files require special attention. Reviews (1): Last reviewed commit: "fix: resolve tts secret refs for local i..." | Re-trigger Greptile |
This reverts commit 4878d3e.
This reverts commit 6506f23.
This reverts commit f89ac65.
Summary
infer tts convertloaded source config directly, somessages.tts.providers.minimax.apiKeystayed as a SecretRef object instead of the resolved startup/reload snapshot value.messages.tts.providers.*.apiKeybefore computing TTS overrides and calling the runtime.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Root Cause (if applicable)
loadConfig()directly, while SecretRef-backed command paths need the shared command SecretRef resolver to read the active gateway snapshot or local fallback resolution.src/cli/capability-cli.tswas not listed in command SecretRef resolution callsite coverage.apiKeythrough normalized resolved secret input and fails when it receives an unresolved SecretRef object.Regression Test Plan (if applicable)
src/cli/capability-cli.test.ts,src/cli/command-secret-resolution.coverage.test.ts,src/cli/command-secret-targets.test.tscapability tts convertresolvesmessages.tts.providers.*.apiKeySecretRefs and passes the resolved config to TTS override/runtime code.User-visible / Behavior Changes
Local
openclaw infer tts convertnow supports SecretRef-backedmessages.tts.providers.minimax.apiKeythrough the shared command SecretRef resolution path.Diagram (if applicable)
Security Impact (required)
Yes/No) NoYes/No) YesYes/No) NoYes/No) NoYes/No) NoYes, explain risk + mitigation: local TTS now uses the same command SecretRef resolver already used by other supported command paths, scoped only to static TTS provider API-key targets andenforce_resolvedmode.Repro + Verification
Environment
node:22-bookwormmessages.tts.provider: "minimax";messages.tts.providers.minimax.apiKeyas an exec SecretRef resolving to a mock static keySteps
messages.tts.providers.minimax.apiKeyas an exec SecretRef and point MiniMax TTSbaseUrlto a local mock endpoint.OPENCLAW_STATE_DIR=/workspace/repro/state pnpm openclaw secrets audit --allow-exec --json.OPENCLAW_STATE_DIR=/workspace/repro/state pnpm openclaw infer tts convert --text "SecretRef reproduction sample" --json.OPENCLAW_STATE_DIR=/workspace/repro/state pnpm openclaw infer tts convert --text "SecretRef explicit MiniMax sample" --model minimax/speech-2.8-hd --json.Expected
Actual
provider: "minimax".Evidence
Docker mock endpoint received two requests with
Authorization: Bearer mock-minimax-static-keyafter the fix.Local validation:
pnpm test src/cli/capability-cli.test.ts src/cli/command-secret-targets.test.ts src/cli/command-secret-targets.import.test.ts src/cli/command-secret-resolution.coverage.test.tspnpm check:changedHuman Verification (required)
Review Conversations
Compatibility / Migration
Yes/No) YesYes/No) NoYes/No) NoRisks and Mitigations
messages.tts.providers.*.apiKeyand uses the existing shared resolver withenforce_resolvedbehavior.