Skip to content

gateway-pre-start.sh token predicate does not recognize OpenClaw’s canonical SecretRef object shape (follow-up to #149) #249

Description

@jamesachurchill

What happened?

Summary

#149 established that scripts/gateway-pre-start.sh must preserve strong gateway.auth.token shapes, explicitly including "a SecretRef object". The shipped predicate handles strong strings and ${ENV_VAR} interpolations correctly, but its SecretRef-object branch checks for the wrong dict shape — so the SecretRef object shape OpenClaw actually stores is classified as weak and rotated to a fresh random token on the next gateway restart.

Steps to reproduce

Current behavior

is_strong_gateway_token in scripts/gateway-pre-start.sh treats a dict as strong when it contains a key named env, file, or exec:

if isinstance(v, dict):
    return any(k in v for k in ("env", "file", "exec"))

OpenClaw's canonical stored SecretRef shape (src/config/types.secrets.ts upstream, enforced by isSecretRef) is:

{ "source": "env" | "file" | "exec", "provider": "<name>", "id": "<id>" }

Its keys are source, provider, and id — none of which match the predicate's tuple. Result:

  1. Operator migrates gateway.auth.token to a SecretRef via openclaw config set ... --ref-provider ... --ref-source ... --ref-id ... (the same supported path already used for Telegram bot tokens).
  2. Gateway restarts; pre-start classifies the ref object as weak and rewrites the field to secrets.token_hex(32).
  3. The migration is silently undone (config is back to a plaintext literal), and every client is locked out because the new random token is held by nobody.

src/tests/unit/gateway-pre-start-token.test.ts extracts and exercises the real predicate, but its dict cases cover the key-style shape ({"exec": ...} etc.) that OpenClaw does not write, so the suite passes while the canonical shape clobbers.

Expected behavior

Per #149, all of the following should be preserved:

  • strong plain strings (existing, correct)
  • ${ENV_VAR} interpolations (existing, correct)
  • SecretRef objects in the shape OpenClaw actually stores: {source, provider, id} with source in env / file / exec (missing)

Why this matters

This is the same failure class #149 fixed for the ${ENV} shape, on the shape the current OpenClaw CLI writes. Any operator who follows the supported openclaw config set --ref-* path for the gateway token gets an immediate, silent revert-plus-lockout on the next restart — worse than the pre-#149 state, because the rotation replaces a working strong token with one no client holds. It also means the only SecretRef style that survives pre-start today is the env-interpolation string, which pushes operators back toward the more fragile env-based approach #149/#150 were moving away from.

Proposed fix

  1. Extend the predicate to accept the canonical shape, keeping the existing branches for compatibility:
if isinstance(v, dict):
    # Canonical OpenClaw SecretRef: {source, provider, id}
    if v.get("source") in ("env", "file", "exec") and \
       isinstance(v.get("provider"), str) and v.get("provider").strip() and \
       isinstance(v.get("id"), str) and v.get("id").strip():
        return True
    # Legacy variant without provider: {source, id}
    if v.get("source") in ("env", "file", "exec") and "provider" not in v and \
       isinstance(v.get("id"), str) and v.get("id").strip():
        return True
    # Key-style shape (pre-existing behavior, kept for compatibility)
    return any(k in v for k in ("env", "file", "exec"))
  1. Extend gateway-pre-start-token.test.ts with canonical-shape cases:
    • {source: "exec", provider: "x", id: "y"} → strong (preserve)
    • {source: "env", provider: "default", id: "OPENCLAW_GATEWAY_TOKEN"} → strong (preserve)
    • legacy {source: "exec", id: "y"} (no provider) → strong (preserve)
    • {source: "bogus", provider: "x", id: "y"} → weak (rotate)
    • {} and {source: "exec"} (missing id) → weak (rotate)
  2. Optional hardening in the spirit of Add post-update smokes for gateway auth continuity and real Telegram bridge delivery #151: a post-update/restart smoke that asserts gateway.auth.token retains its pre-restart shape class (literal stays literal, ref stays ref), so any future predicate regression surfaces at update time rather than as a lockout.

Notes

Found while preparing a bounded SecretRef migration for gateway.auth.token on my host, cross-checking the predicate against upstream openclaw/openclaw src/config/types.secrets.ts. The ${ENV} and strong-string branches were verified correct against the committed test; only the object-shape branch mismatches. Happy to provide the working patch as a starting point.

ClawBox version

3.1.3

OpenClaw version

2026.6.1

Jetson model / JetPack

Jetson Orin Nano

Logs and screenshots

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions