openclaw - ✅(Solved) Fix Bug: macOS gateway install --force resolves SecretRef values into plaintext LaunchAgent plist and triggers token mismatch loop [1 pull requests, 3 comments, 4 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#53742Fetched 2026-04-08 01:24:06
View on GitHub
Comments
3
Participants
4
Timeline
6
Reactions
0
Author
Timeline (top)
commented ×3cross-referenced ×1mentioned ×1subscribed ×1

openclaw gateway install --force resolves every SecretRef in openclaw.json and writes the resolved values as plaintext into the macOS LaunchAgent plist (~/Library/LaunchAgents/ai.openclaw.gateway.plist).

This defeats the purpose of moving secrets out of openclaw.json and into ~/.openclaw/.env using ${ENV:...} references.

It also appears to cause a confusing warning loop where openclaw doctor --fix and openclaw gateway restart report:

Config token differs from service token. Run openclaw gateway install --force to sync the token

…but running install --force just re-embeds the resolved values into the plist again.

Error Message

  • Subsequent restart/doctor flows can warn that the config token differs from the service token even though the gateway is functioning normally.

Root Cause

openclaw gateway install --force resolves every SecretRef in openclaw.json and writes the resolved values as plaintext into the macOS LaunchAgent plist (~/Library/LaunchAgents/ai.openclaw.gateway.plist).

This defeats the purpose of moving secrets out of openclaw.json and into ~/.openclaw/.env using ${ENV:...} references.

It also appears to cause a confusing warning loop where openclaw doctor --fix and openclaw gateway restart report:

Config token differs from service token. Run openclaw gateway install --force to sync the token

…but running install --force just re-embeds the resolved values into the plist again.

PR fix notes

PR #58141: Secrets: hard-fail unsupported SecretRef policy and fix gateway restart token drift

Description (problem / solution / changelog)

Summary

  • Problem: Gateway service restart drift checks produced false-positive token drift/unavailable warnings when gateway.auth.token used SecretRef, and unsupported runtime-mutable SecretRef surfaces were not consistently hard-failed early.
  • Why it matters: operators saw noisy or misleading drift warnings, and invalid SecretRef policy combinations could survive until later runtime paths.
  • What changed: implemented service-env-aware SecretRef drift resolution in restart checks, added explicit unsupported SecretRef policy validation for mutable config/auth surfaces, and enforced OAuth + SecretRef hard-fail in auth-profile runtime resolution paths.
  • What did NOT change (scope boundary): this PR does not implement/install-path fixes for gateway install --force file/plist SecretRef handling.

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

  • Closes #55029
  • Closes #57211
  • Related #53742
  • Related #57261
  • Related #55448
  • Related #57411
  • This PR fixes a bug or regression

C2 Cluster Coverage Matrix

Fully addressed:

  • #55029 (restart drift-check cannot verify env-backed SecretRef token)
    • Addressed by service-env-aware token SecretRef resolution in restart drift check.
  • #57211 (restart false-positive token drift warning for SecretRef token)
    • Addressed by active-mode-aware token resolution and warning suppression when resolved values match.
  • #55448 (open PR in same area)
    • Addressed/superseded by this PR’s restart drift-check fix path and tests.
  • #57411 (open PR in same area)
    • Addressed/superseded by this PR’s restart false-positive warning fix path and tests.

Partially addressed:

  • #53742
    • Addressed: restart/doctor drift-loop symptom caused by SecretRef drift-check resolution behavior.
    • Not addressed: gateway install --force plaintext projection concern in LaunchAgent plist handling.
  • #57261
    • Addressed: drift-check SecretRef resolution behavior during restart evaluation.
    • Not addressed: install-path failure behavior for file-backed gateway.auth.token SecretRef.

Additional post-review blocker fix:

  • Parent-path bypass in config set policy hard-fail (setting parent objects like hooks/channels.discord with nested unsupported SecretRef values).
    • Addressed by commit 6316fd3584: removed requested-path gating and now runs unsupported SecretRef policy validation unconditionally on post-mutation config before write.
    • Regression coverage: src/cli/config-cli.test.ts
      • fails early when parent-object writes include unsupported SecretRef objects
      • does not duplicate policy errors in --dry-run --json mode for parent-object writes

Root Cause / Regression History (if applicable)

  • Root cause: drift-check token resolution used config-only/empty-env paths that did not mirror actual service runtime env inputs for SecretRef-backed tokens.
  • Missing detection / guardrail: no restart-focused tests covered merged service command env + SecretRef token resolution and no early policy gate for unsupported mutable SecretRef paths.
  • Prior context (git blame, prior PR, issue, or refactor if known): open bug threads #55029/#57211 and open PRs #55448/#57411 identified the restart drift check path.
  • Why this regressed now: SecretRef support expansion outpaced drift-check parity and policy hard-fail coverage for runtime-mutable surfaces.
  • If unknown, what was ruled out: N/A.

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/daemon-cli/gateway-token-drift.test.ts
    • src/cli/daemon-cli/lifecycle-core.test.ts
    • src/config/validation.policy.test.ts
    • src/secrets/runtime-auth-profiles-oauth-policy.test.ts
    • src/secrets/runtime.integration.test.ts
  • Scenario the test should lock in: restart drift check resolves SecretRef token using service env and avoids false drift warnings; unsupported mutable SecretRef policy fails early; OAuth + SecretRef profile combinations hard-fail in startup/reload/auth resolution.
  • Why this is the smallest reliable guardrail: these tests hit the exact drift and policy seams without unrelated end-to-end harness overhead.
  • Existing test that already covers this (if any): updated lifecycle/drift tests now cover merged-env restart behavior.
  • If no new test is added, why not: N/A.

User-visible / Behavior Changes

  • openclaw gateway restart drift checks now resolve gateway.auth.token SecretRefs with merged service+process env inputs and reduce false-positive drift warnings.
  • Drift checks skip config token resolution when gateway.auth.mode disables token auth (password, none, trusted-proxy).
  • Unsupported runtime-mutable SecretRef assignments fail early with policy guidance in config validation/config-set paths.
  • OAuth-mode auth-profile + SecretRef combinations now hard-fail in runtime/auth resolution paths.

Diagram (if applicable)

Before:
[gateway restart] -> [config token drift check with incomplete env] -> [SecretRef unavailable/false drift warning]

After:
[gateway restart] -> [drift check with merged service env + mode-aware token resolution] -> [accurate drift result]

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:
    • Risk: stricter policy can reject previously accepted-but-invalid SecretRef configs.
    • Mitigation: deterministic error messages + docs updates clarify supported vs unsupported SecretRef surfaces.

Repro + Verification

Environment

  • OS: macOS + Linux test fixtures
  • Runtime/container: local Node/Bun test harness
  • Model/provider: N/A
  • Integration/channel (if any): gateway/daemon CLI and auth-profile runtime paths
  • Relevant config (redacted): gateway.auth.token SecretRef via env/file provider, auth profile mode combinations

Steps

  1. Configure gateway.auth.token as SecretRef and set service env token source.
  2. Run restart/drift-check paths.
  3. Attempt unsupported mutable SecretRef assignments and OAuth+SecretRef mode combinations.

Expected

  • Accurate/noisy-free drift behavior in restart path for resolvable SecretRef token.
  • Early hard-fail for unsupported policy combinations.

Actual

  • Matches expected in scoped tests.

Evidence

Attach at least one:

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

Scoped verification command (pass):

  • pnpm test -- src/config/validation.policy.test.ts src/cli/config-cli.test.ts src/cli/daemon-cli/gateway-token-drift.test.ts src/cli/daemon-cli/lifecycle-core.test.ts src/agents/auth-profiles/oauth.test.ts src/secrets/runtime-auth-profiles-oauth-policy.test.ts src/secrets/runtime.integration.test.ts src/commands/doctor-gateway-services.test.ts src/secrets/target-registry.test.ts

Human Verification (required)

  • Verified scenarios: restart drift-check SecretRef resolution, lifecycle drift warning behavior, unsupported mutable SecretRef validation, OAuth+SecretRef hard-fail behavior, docs/matrix sync.
  • Edge cases checked: token mode disabled (password), unresolved active refs, reload keeps last-known-good snapshot.
  • What you did not verify: full unrelated repo-wide suite (known unrelated skills/test lane failures remain out of this branch scope).

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 (for valid supported configs)
  • Config/env changes? (Yes/No): No
  • Migration needed? (Yes/No): No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: stricter policy gating may break previously tolerated but philosophically invalid config shapes.
    • Mitigation: explicit policy errors point to canonical SecretRef surface docs; behavior is intentional hard-fail for unsupported mutable/OAuth combinations.
  • Risk: restart drift behavior change could alter warning visibility for edge deployments.
    • Mitigation: added targeted tests for merged env and mode-gated token resolution.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • docs/auth-credential-semantics.md (modified, +7/-0)
  • docs/cli/config.md (modified, +7/-0)
  • docs/cli/daemon.md (modified, +2/-0)
  • docs/cli/gateway.md (modified, +2/-0)
  • docs/gateway/authentication.md (modified, +1/-0)
  • docs/gateway/configuration-reference.md (modified, +2/-1)
  • docs/gateway/secrets.md (modified, +1/-0)
  • docs/reference/secretref-credential-surface.md (modified, +7/-4)
  • docs/reference/secretref-user-supplied-credentials-matrix.json (modified, +4/-2)
  • src/agents/auth-profiles/oauth.test.ts (modified, +20/-0)
  • src/agents/auth-profiles/oauth.ts (modified, +7/-0)
  • src/agents/auth-profiles/policy.ts (added, +142/-0)
  • src/cli/config-cli.test.ts (modified, +177/-0)
  • src/cli/config-cli.ts (modified, +54/-6)
  • src/cli/daemon-cli/gateway-token-drift.test.ts (modified, +78/-18)
  • src/cli/daemon-cli/gateway-token-drift.ts (modified, +53/-5)
  • src/cli/daemon-cli/lifecycle-core.test.ts (modified, +67/-1)
  • src/cli/daemon-cli/lifecycle-core.ts (modified, +5/-1)
  • src/config/validation.policy.test.ts (added, +129/-0)
  • src/config/validation.ts (modified, +97/-1)
  • src/secrets/credential-matrix.ts (modified, +2/-11)
  • src/secrets/runtime-auth-collectors.ts (modified, +7/-0)
  • src/secrets/runtime-auth-profiles-oauth-policy.test.ts (added, +74/-0)
  • src/secrets/runtime.integration.test.ts (modified, +91/-0)
  • src/secrets/unsupported-surface-policy.test.ts (added, +73/-0)
  • src/secrets/unsupported-surface-policy.ts (added, +128/-0)

Code Example

{
  "gateway": {
    "token": "${ENV:OPENCLAW_GATEWAY_TOKEN}"
  }
}

---

openclaw gateway install --force

---

plutil -p ~/Library/LaunchAgents/ai.openclaw.gateway.plist

---

openclaw gateway restart
RAW_BUFFERClick to expand / collapse

Summary

openclaw gateway install --force resolves every SecretRef in openclaw.json and writes the resolved values as plaintext into the macOS LaunchAgent plist (~/Library/LaunchAgents/ai.openclaw.gateway.plist).

This defeats the purpose of moving secrets out of openclaw.json and into ~/.openclaw/.env using ${ENV:...} references.

It also appears to cause a confusing warning loop where openclaw doctor --fix and openclaw gateway restart report:

Config token differs from service token. Run openclaw gateway install --force to sync the token

…but running install --force just re-embeds the resolved values into the plist again.

Why this is a problem

  • Secrets that were intentionally externalized to ~/.openclaw/.env are copied back into a persistent service definition as plaintext.
  • This undermines the security/privacy model of SecretRef usage in openclaw.json.
  • It creates a circular remediation path:
    • OpenClaw warns that the service token differs from the config token.
    • The suggested fix is openclaw gateway install --force.
    • That command hardcodes the resolved secret values into the LaunchAgent plist.
    • The warning can still persist even though the gateway itself is healthy.

In practice, the gateway continues to work (RPC probe: ok), so the mismatch warning appears functionally cosmetic but still sends the user into an infinite loop of “fix” steps.

Steps to reproduce

  1. Move secrets from openclaw.json into ~/.openclaw/.env.
  2. Use SecretRef / env references in openclaw.json, e.g.:
{
  "gateway": {
    "token": "${ENV:OPENCLAW_GATEWAY_TOKEN}"
  }
}
  1. Run:
openclaw gateway install --force
  1. Inspect the LaunchAgent plist:
plutil -p ~/Library/LaunchAgents/ai.openclaw.gateway.plist
  1. Observe that all resolved secrets (for example OPENCLAW_GATEWAY_TOKEN, ANTHROPIC_API_KEY, TELEGRAM_BOT_TOKEN, etc.) appear as plaintext values in the plist.
  2. Run:
openclaw gateway restart
  1. Observe the warning:

Config token differs from service token. Run openclaw gateway install --force to sync the token

  1. Repeat install --force and/or doctor --fix; the warning can continue indefinitely.

Expected behavior

One of these should happen instead:

  1. openclaw gateway install should preserve SecretRef indirection and configure the LaunchAgent to load environment values at runtime, rather than serializing the resolved values into the plist.
  2. The LaunchAgent should reference ~/.openclaw/.env (or another env source) so secrets remain external to the plist.
  3. The “config token differs from service token” check should compare effective/resolved values correctly and avoid warning when the runtime values already match.

Actual behavior

  • SecretRef values are resolved during gateway install --force.
  • The resolved values are embedded into ~/Library/LaunchAgents/ai.openclaw.gateway.plist as plaintext.
  • Subsequent restart/doctor flows can warn that the config token differs from the service token even though the gateway is functioning normally.

Environment

  • OpenClaw: 2026.3.23 (ccfeecb)
  • macOS: 26.3.1 (arm64)
  • Node: 25.5.0
  • Gateway service type: LaunchAgent

Suggested direction

This feels like both a security bug and a service-install/runtime parity bug.

A robust fix would likely be:

  • keep secrets in .env / external secret sources,
  • have the LaunchAgent load them at runtime,
  • and make the token consistency check compare effective values rather than the serialized plist representation.

Happy to provide sanitized examples of the generated plist if helpful.

extent analysis

Fix Plan

To address the issue, we'll modify the openclaw gateway install command to preserve SecretRef indirection and configure the LaunchAgent to load environment values at runtime.

Step 1: Modify the openclaw gateway install command

We'll update the command to use environment variables in the LaunchAgent plist instead of resolving SecretRef values.

Step 2: Update the LaunchAgent plist configuration

We'll modify the LaunchAgent configuration to load environment values from ~/.openclaw/.env at runtime.

Example code snippet:

# Update the LaunchAgent plist to use environment variables
plutil -replace "gateway.token" -string "${ENV:OPENCLAW_GATEWAY_TOKEN}" ~/Library/LaunchAgents/ai.openclaw.gateway.plist

Step 3: Update the token consistency check

We'll modify the token consistency check to compare effective/resolved values instead of the serialized plist representation.

Example code snippet:

// Update the token consistency check
const effectiveToken = process.env.OPENCLAW_GATEWAY_TOKEN;
const serviceToken = getServiceToken(); // implement getServiceToken() to retrieve the service token
if (effectiveToken !== serviceToken) {
  console.log("Config token differs from service token.");
} else {
  console.log("Tokens are in sync.");
}

Verification

To verify the fix, follow these steps:

  1. Run openclaw gateway install with the updated command.
  2. Inspect the LaunchAgent plist using plutil -p ~/Library/LaunchAgents/ai.openclaw.gateway.plist.
  3. Verify that the SecretRef values are not resolved and embedded as plaintext.
  4. Run openclaw gateway restart and check for the warning message.
  5. If the warning persists, verify that the token consistency check is comparing effective/resolved values correctly.

Extra Tips

To prevent similar issues in the future, consider implementing the following:

  • Use environment variables or external secret sources to store sensitive values.
  • Keep the LaunchAgent configuration up-to-date with the latest security patches and best practices.
  • Regularly review and test the token consistency check to ensure it's working as expected.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

One of these should happen instead:

  1. openclaw gateway install should preserve SecretRef indirection and configure the LaunchAgent to load environment values at runtime, rather than serializing the resolved values into the plist.
  2. The LaunchAgent should reference ~/.openclaw/.env (or another env source) so secrets remain external to the plist.
  3. The “config token differs from service token” check should compare effective/resolved values correctly and avoid warning when the runtime values already match.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING