Skip to content

fix: resolve local TTS SecretRefs before conversion#72549

Merged
joshavant merged 1 commit intomainfrom
fix/minimax-tts-secretref
Apr 27, 2026
Merged

fix: resolve local TTS SecretRefs before conversion#72549
joshavant merged 1 commit intomainfrom
fix/minimax-tts-secretref

Conversation

@joshavant
Copy link
Copy Markdown
Contributor

Summary

  • Problem: local infer tts convert loaded source config directly, so messages.tts.providers.minimax.apiKey stayed as a SecretRef object instead of the resolved startup/reload snapshot value.
  • Why it matters: MiniMax TTS could fail locally and fall back to another provider even when the SecretRef audit was clean.
  • What changed: route local TTS conversion through command SecretRef resolution for messages.tts.providers.*.apiKey before computing TTS overrides and calling the runtime.
  • What did NOT change (scope boundary): gateway TTS behavior and other SecretRef coverage gaps in SecretRef coverage gaps for several auth/config paths #68690 are unchanged.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause (if applicable)

  • Root cause: the local TTS CLI path used loadConfig() directly, while SecretRef-backed command paths need the shared command SecretRef resolver to read the active gateway snapshot or local fallback resolution.
  • Missing detection / guardrail: src/cli/capability-cli.ts was not listed in command SecretRef resolution callsite coverage.
  • Contributing context (if known): MiniMax validates apiKey through normalized resolved secret input and fails when it receives an unresolved SecretRef object.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/cli/capability-cli.test.ts, src/cli/command-secret-resolution.coverage.test.ts, src/cli/command-secret-targets.test.ts
  • Scenario the test should lock in: local capability tts convert resolves messages.tts.providers.*.apiKey SecretRefs and passes the resolved config to TTS override/runtime code.
  • Why this is the smallest reliable guardrail: it exercises the CLI seam that regressed without requiring live MiniMax credentials.
  • Existing test that already covers this (if any): none before this PR.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

Local openclaw infer tts convert now supports SecretRef-backed messages.tts.providers.minimax.apiKey through the shared command SecretRef resolution path.

Diagram (if applicable)

Before:
local infer tts convert -> source config -> unresolved MiniMax apiKey SecretRef -> provider failure/fallback

After:
local infer tts convert -> command SecretRef resolver -> resolved config -> MiniMax TTS request

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) Yes
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, 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 and enforce_resolved mode.

Repro + Verification

Environment

  • OS: Linux container
  • Runtime/container: Docker node:22-bookworm
  • Model/provider: MiniMax TTS, mock local T2A v2 endpoint
  • Integration/channel (if any): local CLI TTS
  • Relevant config (redacted): messages.tts.provider: "minimax"; messages.tts.providers.minimax.apiKey as an exec SecretRef resolving to a mock static key

Steps

  1. Configure messages.tts.providers.minimax.apiKey as an exec SecretRef and point MiniMax TTS baseUrl to a local mock endpoint.
  2. Run OPENCLAW_STATE_DIR=/workspace/repro/state pnpm openclaw secrets audit --allow-exec --json.
  3. Run OPENCLAW_STATE_DIR=/workspace/repro/state pnpm openclaw infer tts convert --text "SecretRef reproduction sample" --json.
  4. Run OPENCLAW_STATE_DIR=/workspace/repro/state pnpm openclaw infer tts convert --text "SecretRef explicit MiniMax sample" --model minimax/speech-2.8-hd --json.

Expected

  • SecretRef audit is clean.
  • Both TTS commands use MiniMax and send the resolved bearer token to the mock endpoint.

Actual

  • Before: local TTS failed MiniMax with unresolved SecretRef and fell back, or failed directly for explicit MiniMax.
  • After: both local TTS commands succeed with provider: "minimax".

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Docker mock endpoint received two requests with Authorization: Bearer mock-minimax-static-key after 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.ts
  • pnpm check:changed

Human Verification (required)

  • Verified scenarios: clean SecretRef audit, local default MiniMax TTS, local explicit MiniMax model TTS, mock endpoint bearer-token request capture.
  • Edge cases checked: explicit provider/model selection disables fallback and still succeeds with the resolved SecretRef.
  • What you did not verify: real MiniMax API credentials/service response.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: local TTS now attempts command SecretRef resolution for configured TTS API-key refs.
    • Mitigation: resolution is scoped to messages.tts.providers.*.apiKey and uses the existing shared resolver with enforce_resolved behavior.

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot Bot commented Apr 27, 2026

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🟠 High Potential arbitrary command execution via SecretRef exec provider during infer tts convert secret resolution
1. 🟠 Potential arbitrary command execution via SecretRef exec provider during `infer tts convert` secret resolution
Property Value
Severity High
CWE CWE-78
Location src/cli/capability-cli.ts:1116-1121

Description

infer tts convert now resolves SecretRefs before running by calling resolveCommandSecretRefsViaGateway(...) with mode: "enforce_resolved".

If the gateway is unavailable or its snapshot is incomplete, resolveCommandSecretRefsViaGateway falls back to local secret resolution, which can execute configured secret providers of source: "exec" (spawning a subprocess) based on values from the user/project-controlled config.

Why this is a security risk:

  • Input/control: loadConfig() loads project/user configuration which may come from a checked-out repository or other untrusted source.
  • Dangerous behavior: local fallback uses resolveSecretRefValue()resolveExecRefs() which runs spawn(command, args, ...) for secrets.providers.<name>.command.
  • Trigger: this behavior can now be triggered simply by invoking infer tts convert (even if the user did not intend to run any local exec-based provider), when SecretRefs are present and the gateway cannot resolve them.

This can lead to executing attacker-controlled binaries/scripts referenced in config (e.g., an exec provider pointing at an absolute path within the repo), resulting in arbitrary code execution under the user's account.

Recommendation

Treat exec-based SecretRef providers as explicitly opt-in for CLI commands, and avoid spawning processes during implicit resolution flows.

Suggested mitigations (pick one or combine):

  1. Disable local fallback for exec providers in this command path (or in general) unless a user explicitly enables it:
await resolveCommandSecretRefsViaGateway({
  config: loadConfig(),
  commandName: "infer tts convert",
  targetIds: getTtsCommandSecretTargetIds(),
  mode: "enforce_resolved",// new option: disallowLocalExecProviders: true
});
  1. Allowlist provider sources permitted for automatic resolution (e.g., only env/file), and fail closed when config contains exec SecretRefs:
if (providerConfig.source === "exec") {
  throw new Error("Exec secret providers are disabled for this command unless --allow-exec-secrets is set");
}
  1. If exec providers must remain supported, require:
  • an interactive prompt or --yes/--allow-exec-secrets flag
  • and a hardcoded trusted directory policy not controllable by config (do not rely on trustedDirs/allowInsecurePath values provided by the same untrusted config).

Analyzed PR: #72549 at commit e265c32

Last updated on: 2026-04-27T03:19:04Z

@openclaw-barnacle openclaw-barnacle Bot added cli CLI command changes size: S maintainer Maintainer-authored PR labels Apr 27, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 27, 2026

Greptile Summary

Routes local infer tts convert through the shared command SecretRef resolver (resolveCommandSecretRefsViaGateway) before computing TTS overrides and calling the runtime, fixing a bug where messages.tts.providers.minimax.apiKey was passed as an unresolved SecretRef object to MiniMax. The refactor also extracts STATIC_TTS_TARGET_IDS into its own named constant and export, keeping the spread in STATIC_AGENT_RUNTIME_BASE_TARGET_IDS to avoid any regression on agent-runtime resolution.

Confidence Score: 5/5

This 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 loadConfig() to resolveCommandSecretRefsViaGateway(...), a constant extraction with no semantic change to agent-runtime resolution, and a new exported helper. New test coverage directly locks in the fixed behaviour. No existing contract is altered and the gateway path is untouched.

No files require special attention.

Reviews (1): Last reviewed commit: "fix: resolve tts secret refs for local i..." | Re-trigger Greptile

@joshavant joshavant merged commit 4878d3e into main Apr 27, 2026
67 of 70 checks passed
@joshavant joshavant deleted the fix/minimax-tts-secretref branch April 27, 2026 03:31
joshavant added a commit that referenced this pull request Apr 27, 2026
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant