openclaw - ✅(Solved) Fix [Bug]: .env.example ships a known-weak literal token that becomes the gateway credential when copied verbatim [3 pull requests, 1 comments, 2 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#64520Fetched 2026-04-11 06:14:39
View on GitHub
Comments
1
Participants
2
Timeline
5
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×3commented ×1referenced ×1

Error Message

Root cause: The .env.example file ships with an uncommented, active token value (change-me-to-a-long-random-token) rather than an empty placeholder or a commented-out example. The quick-start instructions explicitly say to copy the file, creating a path where the known-weak credential becomes the live production token with zero user error beyond following the documented steps. Replace the active token value in .env.example with an empty value (e.g., OPENCLAW_GATEWAY_TOKEN=) or a clearly inert placeholder that cannot be used as-is (e.g., OPENCLAW_GATEWAY_TOKEN=). Add a startup-time check that detects and rejects the literal example string change-me-to-a-long-random-token as a configured token, printing a clear error directing the operator to generate a real secret.

Root Cause

Root cause: The .env.example file ships with an uncommented, active token value (change-me-to-a-long-random-token) rather than an empty placeholder or a commented-out example. The quick-start instructions explicitly say to copy the file, creating a path where the known-weak credential becomes the live production token with zero user error beyond following the documented steps.

Fix Action

Fixed

PR fix notes

PR #64521: fix: .env.example ships a known-weak literal token that becomes the...

Description (problem / solution / changelog)

Fix Summary

openclaw/.env.example ships with OPENCLAW_GATEWAY_TOKEN=change-me-to-a-long-random-token as an uncommented, active default. Users who follow the quick-start instructions (step 1: "Copy this file to .env") and skip step 2 ("Fill only the values you use") deploy a gateway protected by a publicly known credential, allowing any LAN-adjacent party who has read the repository to authenticate as a trusted operator.

Issue Linkage

Fixes #64520

Security Snapshot

  • CVSS v3.1: 7.3 (High)
  • CVSS v4.0: 8.5 (High)

Implementation Details

Files Changed

  • .env.example (+2/-2)
  • src/gateway/credentials.test.ts (+11/-0)
  • src/gateway/credentials.ts (+7/-2)
  • src/gateway/startup-auth.test.ts (+16/-0)

Technical Analysis

  1. Clone the repo.
  2. Follow the quick-start instructions literally: cp openclaw/.env.example openclaw/.env.
  3. Skip replacing the token value (or overlook step 2).
  4. Start the gateway via docker compose up or openclaw gateway run.
  5. From a second host on the LAN, authenticate using the known literal: Authorization: Bearer change-me-to-a-long-random-token.
  6. Observe full gateway access as a trusted operator.

Validation Evidence

  • Command: pnpm exec oxlint src/ && pnpm build && pnpm check && pnpm test
  • Status: passed (with pre-existing baseline failures)

Risk and Compatibility

  • non-breaking; no known regression impact

AI-Assisted Disclosure

  • AI-assisted: yes
  • Model: github-copilot/claude-sonnet-4.6

Changed files

  • .env.example (modified, +2/-2)
  • src/gateway/credentials.test.ts (modified, +11/-0)
  • src/gateway/credentials.ts (modified, +7/-2)
  • src/gateway/startup-auth.test.ts (modified, +16/-0)

PR #64535: fix(env): replace known-weak literal token in .env.example

Description (problem / solution / changelog)

Summary

  • replace the active example gateway token in .env.example with an empty value
  • reject the known-weak literal placeholder token during gateway startup
  • add startup-auth tests covering config and env-token rejection

Testing

  • /root/openclaw-fork/node_modules/.bin/vitest run --maxWorkers 1 --maxConcurrency 1 src/gateway/startup-auth.test.ts -t "known-weak literal token|does not generate when token already exists"

Closes #64520

Changed files

  • .env.example (modified, +2/-2)
  • src/gateway/startup-auth.test.ts (modified, +29/-0)
  • src/gateway/startup-auth.ts (modified, +17/-0)

PR #64586: fix(gateway): reject known-weak example auth credentials at startup

Description (problem / solution / changelog)

Summary

  • Problem: .env.example shipped with an uncommented active value OPENCLAW_GATEWAY_TOKEN=change-me-to-a-long-random-token, and the quick-start instructions tell operators to cp .env.example .env. Any operator who skipped step 2 ("Fill only the values you use") ended up with a gateway whose token is a publicly known string — any LAN-adjacent reader of the repository could authenticate as a trusted operator.
  • Why it matters: The SECURITY.md threat model treats authenticated gateway callers as trusted operators — session management, tool invocation, exec capability, and access to configured secrets. The cost of this going wrong is full gateway compromise with zero operator error beyond following the documented install steps.
  • What changed: Blank the placeholder in .env.example (the generation path already kicks in for empty tokens), and add a startup-time assertion that throws with a clear remediation message if the known-weak token or password sentinel ever reaches resolved gateway auth — whether via OPENCLAW_GATEWAY_TOKEN, gateway.auth.token, the password equivalents, or any future surface. Follows the reporter's explicit remediation advice in #64520.
  • What did NOT change (scope boundary): No existing credential-resolution logic is touched. No config schema changes. The generation path, mode selection, SecretRef resolution, password paths, Tailscale gating, and hooks-token separation assertion all behave identically for real credentials. The only new behavior is a targeted rejection of two literal sentinel strings.

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 #64520
  • Related: #64521 (parallel proposal for the same issue, different strategy — see comments)
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: .env.example:18 was shipped with an active value rather than a blank placeholder, and the file's own quick-start comments (lines 3–5) explicitly direct operators to copy the file to .env. The startup auth resolution in src/gateway/startup-auth.ts accepts any non-empty string as a valid token; there was no detection of the literal example sentinel.
  • Missing detection / guardrail: There was no startup-time validator that inspected the resolved credential for known-weak placeholder values. The file-level comment alone ("Fill only the values you use") is an honor-system guardrail — it does not protect users who follow the quick-start literally, nor users who copy-paste a tutorial snippet from old docs. resolveGatewayAuth happily treats the example string as a valid credential.
  • Contributing context (if known): The .gitignore correctly excludes .env — this prevents accidental commit of the file, but does nothing to protect the deployed value. Defense against accidental commit and defense against accidental production use are two different problems; this PR addresses the second.

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/gateway/startup-auth.test.ts
  • Scenario the test should lock in:
    1. ensureGatewayStartupAuth rejects the token sentinel supplied via OPENCLAW_GATEWAY_TOKEN.
    2. ensureGatewayStartupAuth rejects the token sentinel supplied via cfg.gateway.auth.token.
    3. ensureGatewayStartupAuth rejects the password sentinel supplied via cfg.gateway.auth.password.
    4. ensureGatewayStartupAuth continues to accept any non-placeholder token (negative control — make sure the assertion is narrow).
    5. Direct unit tests of the exported assertGatewayAuthNotKnownWeak helper covering token sentinel, password sentinel, whitespace-padded sentinel (trimmed match), empty token (fall-through to generation), real token (no throw), and mode: "none" (no throw).
  • Why this is the smallest reliable guardrail: The assertion is a pure function over ResolvedGatewayAuth. A truth-table test against the sentinel set is the cheapest possible coverage and cannot regress silently under future refactors of the resolver chain.
  • Existing test that already covers this (if any): None. startup-auth.test.ts covered SecretRef resolution, hooks-token separation, and the generate-and-persist flow, but had no knowledge of placeholder strings.
  • If no new test is added, why not: N/A — 11 new test cases added.

User-visible / Behavior Changes

  • Operators who currently have OPENCLAW_GATEWAY_TOKEN=change-me-to-a-long-random-token set (whether from copy-pasting .env.example or an old tutorial) will now see the gateway fail to start with a clear error pointing at .env.example and suggesting openssl rand -hex 32. This is deliberate: silently swapping their "token" for a generated one would leave them debugging mysterious 401s on curl -H "Authorization: Bearer change-me-to-a-long-random-token" calls with no log line explaining what happened. Failing loudly and telling the user what to change is the secure-by-default posture and matches the reporter's explicit remediation advice in #64520.
  • No change for operators with any real token or password.
  • No change for operators with an empty OPENCLAW_GATEWAY_TOKEN — the existing auto-generation path kicks in exactly as before.

Diagram (if applicable)

Before:
  .env.example (copied to .env)
    -> OPENCLAW_GATEWAY_TOKEN=change-me-to-a-long-random-token
    -> ensureGatewayStartupAuth() accepts any non-empty token
    -> gateway starts with a publicly-known credential
    -> LAN-adjacent attacker authenticates as operator

After:
  .env.example (copied to .env)
    -> OPENCLAW_GATEWAY_TOKEN= (blank)
    -> ensureGatewayStartupAuth() falls through to generation path
    -> crypto.randomBytes(24) -> persisted to config
    -> gateway starts with a unique secret per install

And if the operator re-inserts the placeholder (e.g. from an old tutorial):
  .env copies the literal placeholder value
    -> ensureGatewayStartupAuth() -> assertGatewayAuthNotKnownWeak()
    -> throws "Invalid config: gateway auth token is set to the example
       placeholder from .env.example. Generate a real secret..."
    -> gateway refuses to start; operator fixes the token

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? Yes — this PR is a tokens-handling hardening change. The sentinel detection is added to the startup auth resolution path in src/gateway/startup-auth.ts. No persisted credential is ever rewritten silently; the flow either throws (known-weak) or passes through (real credential).
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • Risk + mitigation: The only new failure mode is "gateway refuses to start when configured with the placeholder," which is intended and matches the threat model. The assertion is scoped to a ReadonlySet of two literal strings, so it cannot accidentally reject real credentials — a collision would require a random 32-byte token to equal "change-me-to-a-long-random-token", which is infeasible.

Repro + Verification

Environment

  • OS: tested locally on Windows 11, Node 22
  • Runtime/container: Node 22 via vitest run
  • Model/provider: N/A — this is gateway auth resolution, orthogonal to providers
  • Integration/channel: gateway startup path (ensureGatewayStartupAuth)
  • Relevant config: base .env.example, no additional gateway config

Steps

  1. Clone the repo, cp .env.example .env, start the gateway with OPENCLAW_GATEWAY_TOKEN unset — gateway auto-generates a token (unchanged behavior).
  2. Set OPENCLAW_GATEWAY_TOKEN=change-me-to-a-long-random-token, start the gateway — gateway refuses to start with a clear remediation message.
  3. Set OPENCLAW_GATEWAY_TOKEN=a-legit-random-token-..., start the gateway — gateway starts normally (unchanged behavior).
  4. Set cfg.gateway.auth.mode=password, cfg.gateway.auth.password=change-me-to-a-strong-password — gateway refuses to start.

Expected

  • Placeholder rejection throws at startup with a clear error identifying the offending env var and suggesting openssl rand -hex 32.

Actual

  • Matches expected. Covered by 11 unit tests in startup-auth.test.ts.

Evidence

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

Test output:

 RUN  v4.1.4
 Test Files  1 passed (1)
      Tests  31 passed (31)

Tests that run:

  • startup-auth.test.ts grew from 20 to 31 tests (4 new in the ensureGatewayStartupAuth block covering the env/config/password sentinel paths + negative control, and 7 in a new assertGatewayAuthNotKnownWeak describe block covering the helper's full truth table).
  • Neighboring gateway auth tests also verified green locally: auth.test.ts + credentials.test.ts = 89/89 pass.

oxlint on all touched files: Found 0 warnings and 0 errors.

Human Verification (required)

  • Verified scenarios:
    • Ran the full startup-auth.test.ts suite locally: 31/31 pass.
    • Ran credentials.test.ts and auth.test.ts locally to confirm no unintended knock-on: 89/89 pass.
    • Ran oxlint on .env.example, startup-auth.ts, and startup-auth.test.ts: clean.
    • Walked the ensureGatewayStartupAuth flow to confirm the assertion fires on both the early-return path (real credential supplied) and the post-generation path (defense in depth; documented with a comment that a crypto-random token cannot match the sentinel but the rule should apply uniformly).
  • Edge cases checked:
    • Whitespace-padded placeholder (" change-me-to-a-long-random-token ") — still rejected because the check trim()s first, matching the existing behavior of assertHooksTokenSeparateFromGatewayAuth.
    • Empty token — passes the sentinel check and falls through to the normal generation path.
    • mode: "none" — no assertion (no credential to compare against).
    • Password sentinel — explicitly covered.
  • What I did NOT verify: I did not spin up a real gateway end-to-end and cURL it; the assertion is a pure function over ResolvedGatewayAuth and the unit tests cover every path the helper can be reached through.

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? Intentionally no for operators currently running with the placeholder value as their live token — this PR is a breaking fix for a vulnerable state. For everyone else, backward compatible.
  • Config/env changes? No — no new config keys, no new env vars.
  • Migration needed? Only for operators who are currently affected by the vulnerability. Migration step: generate a real token via openssl rand -hex 32 and set OPENCLAW_GATEWAY_TOKEN (or gateway.auth.token). The error message tells them exactly this.

Risks and Mitigations

  • Risk: An operator intentionally running with the placeholder value (maybe a test environment) will see the gateway refuse to start after this lands.
    • Mitigation: This is the intended behavior. The error message is clear and tells them how to generate a real token. The threat model does not accommodate "intentionally weak credentials" as a legitimate operating mode — even in test environments, a 30-second openssl rand -hex 32 produces a safer value.
  • Risk: Future .env.example changes could introduce a new placeholder string that is not in the sentinel set.
    • Mitigation: The KNOWN_WEAK_GATEWAY_TOKENS and KNOWN_WEAK_GATEWAY_PASSWORDS sets are co-located with the assertion in startup-auth.ts, making it obvious that any new placeholder needs to be added here. A long-term alternative would be to have .env.example commit a test that asserts every uncommented value is empty — out of scope for this PR.

Changed files

  • .env.example (modified, +7/-4)
  • src/gateway/startup-auth.test.ts (modified, +138/-2)
  • src/gateway/startup-auth.ts (modified, +51/-0)

Code Example

# Recommended if the gateway binds beyond loopback.
OPENCLAW_GATEWAY_TOKEN=change-me-to-a-long-random-token
# Example generator: openssl rand -hex 32

---

# Quick start:
# 1) Copy this file to `.env` (for local runs from this repo), OR to `~/.openclaw/.env` (for launchd/systemd daemons).
# 2) Fill only the values you use.
RAW_BUFFERClick to expand / collapse

Severity Assessment

CVSS Assessment

Metricv3.1v4.0
Score7.3 / 10.08.5 / 10.0
SeverityHighHigh
VectorCVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:NCVSS:4.0/AV:A/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N
CalculatorCVSS v3.1 CalculatorCVSS v4.0 Calculator

Threat Model Alignment

Classification: security-specific

Impact

openclaw/.env.example ships with OPENCLAW_GATEWAY_TOKEN=change-me-to-a-long-random-token as an uncommented, active default. Users who follow the quick-start instructions (step 1: "Copy this file to .env") and skip step 2 ("Fill only the values you use") deploy a gateway protected by a publicly known credential, allowing any LAN-adjacent party who has read the repository to authenticate as a trusted operator.

Affected Component

File: openclaw/.env.example:18

# Recommended if the gateway binds beyond loopback.
OPENCLAW_GATEWAY_TOKEN=change-me-to-a-long-random-token
# Example generator: openssl rand -hex 32

File: openclaw/.env.example:3-5

# Quick start:
# 1) Copy this file to `.env` (for local runs from this repo), OR to `~/.openclaw/.env` (for launchd/systemd daemons).
# 2) Fill only the values you use.

Technical Reproduction

  1. Clone the repo.
  2. Follow the quick-start instructions literally: cp openclaw/.env.example openclaw/.env.
  3. Skip replacing the token value (or overlook step 2).
  4. Start the gateway via docker compose up or openclaw gateway run.
  5. From a second host on the LAN, authenticate using the known literal: Authorization: Bearer change-me-to-a-long-random-token.
  6. Observe full gateway access as a trusted operator.

Demonstrated Impact

Root cause: The .env.example file ships with an uncommented, active token value (change-me-to-a-long-random-token) rather than an empty placeholder or a commented-out example. The quick-start instructions explicitly say to copy the file, creating a path where the known-weak credential becomes the live production token with zero user error beyond following the documented steps.

What breaks: Gateway auth is completely bypassed for any attacker who has read the repository and can reach the gateway port. Because authenticated callers are treated as trusted operators (per SECURITY.md), this grants full control: session management, tool invocation, exec capability, and access to any configured secrets/channels.

Why existing controls do not prevent exploitation: There is no startup-time validation that the configured token is not the example placeholder string. The resolveGatewayAuth path accepts any non-empty string as a valid token. The .gitignore correctly excludes .env, but this does not prevent deployment with the placeholder — it only prevents accidental commit.

Environment

Reproducible on current main branch with any deployment that copies .env.example to .env without modifying OPENCLAW_GATEWAY_TOKEN.

Remediation Advice

Replace the active token value in .env.example with an empty value (e.g., OPENCLAW_GATEWAY_TOKEN=) or a clearly inert placeholder that cannot be used as-is (e.g., OPENCLAW_GATEWAY_TOKEN=). Add a startup-time check that detects and rejects the literal example string change-me-to-a-long-random-token as a configured token, printing a clear error directing the operator to generate a real secret.

<!-- submission-marker:CD-rou-env-example-known-weak-token -->

extent analysis

TL;DR

Replace the default token value in .env.example with an empty or inert placeholder and add a startup check to prevent the use of the example string as a configured token.

Guidance

  • Update the .env.example file to include an empty value for OPENCLAW_GATEWAY_TOKEN, such as OPENCLAW_GATEWAY_TOKEN=, to prevent accidental use of the example token.
  • Implement a startup-time validation check in the resolveGatewayAuth path to detect and reject the literal example string change-me-to-a-long-random-token as a configured token.
  • Consider adding clear documentation and warnings in the quick-start instructions to emphasize the importance of replacing the example token with a secure, randomly generated token.
  • Review the authentication mechanism to ensure that it properly handles and validates user-provided tokens to prevent similar vulnerabilities.

Example

# In .env.example
OPENCLAW_GATEWAY_TOKEN=

# In the gateway startup code
if [ "$OPENCLAW_GATEWAY_TOKEN" = "change-me-to-a-long-random-token" ]; then
  echo "Error: Example token detected. Please generate a random token and update the .env file."
  exit 1
fi

Notes

This fix assumes that the resolveGatewayAuth path can be modified to include the startup-time validation check. Additional changes may be necessary to ensure that the authentication mechanism properly handles and validates user-provided tokens.

Recommendation

Apply the workaround by updating the .env.example file and implementing the startup-time validation check to prevent the use of the example token. This will help prevent unauthorized access to the gateway until a more permanent fix can be implemented.

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…

Still need to ship something?

×6

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

Back to top recommendations

TRENDING