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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions