Severity Assessment
CVSS Assessment
| Metric |
v3.1 |
v4.0 |
| Score |
7.8 / 10.0 |
8.5 / 10.0 |
| Severity |
High |
High |
| Vector |
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N |
| Calculator |
CVSS v3.1 Calculator |
CVSS v4.0 Calculator |
Threat Model Alignment
Classification: security-specific
OpenClaw's gateway security guide states that shared-secret bearer credentials are full operator secrets for the gateway, and authorizeTokenAuth() treats a matching OPENCLAW_GATEWAY_TOKEN as successful token authentication. The Linux node-daemon install path persists that token inside the generated user systemd unit, so any same-host principal that can read the unit crosses the gateway.auth boundary without pairing or another gateway credential. SECURITY.md excludes exposed third-party or user-controlled credentials, but this token is OpenClaw's own gateway secret. The issue is therefore in scope as a credential-disclosure bug, even under the one-user model, because the documented mitigation for shared hosts is separate OS-user boundaries and this install path weakens that boundary when the unit inherits default read permissions.
Impact
On Linux, openclaw node install copies the caller's OPENCLAW_GATEWAY_TOKEN into the generated openclaw-node.service user unit as an inline Environment= entry. A same-host user or process that can read that unit recovers a bearer secret that OpenClaw accepts as full gateway operator authentication.
Affected Component
Files: openclaw/src/daemon/service-env.ts:421-449, openclaw/src/commands/node-daemon-install-helpers.ts:19-68, openclaw/src/cli/node-cli/daemon.ts:131-167, openclaw/src/daemon/systemd.ts:560-622, openclaw/src/daemon/systemd-unit.ts:20-35, openclaw/src/gateway/auth.ts:350-363
const gatewayToken = normalizeOptionalString(env.OPENCLAW_GATEWAY_TOKEN);
return {
...buildCommonServiceEnvironment(env, sharedEnv),
OPENCLAW_GATEWAY_TOKEN: gatewayToken,
OPENCLAW_ALLOW_INSECURE_PRIVATE_WS: allowInsecurePrivateWs,
OPENCLAW_SYSTEMD_UNIT: resolveNodeSystemdServiceName(),
OPENCLAW_SERVICE_KIND: NODE_SERVICE_KIND,
};
return entries.map(([key, value]) => {
const rawValue = value ?? "";
return `Environment=${systemdEscapeArg(`${key}=${rawValue.trim()}`)}`;
});
const unit = buildSystemdUnit({
programArguments,
workingDirectory,
environment: environmentSansDotEnvEntries,
environmentFiles: environmentFileResult.environmentFiles,
});
await fs.writeFile(unitPath, unit, "utf8");
if (!safeEqualSecret(params.connectToken, params.authToken)) {
return { ok: false, reason: "token_mismatch" };
}
return { ok: true, method: "token" };
Technical Reproduction
- On a Linux host with user systemd enabled, export a real gateway token into the shell that will run the installer:
OPENCLAW_GATEWAY_TOKEN=<gateway-secret>.
- Run
openclaw node install --host 127.0.0.1 --port 18789. runNodeDaemonInstall() passes process.env to buildNodeInstallPlan(), which calls buildNodeServiceEnvironment() and returns an environment object containing OPENCLAW_GATEWAY_TOKEN.
resolveNodeService().install() forwards that environment into the shared systemd installer. writeSystemdUnit() keeps the node token in environmentSansDotEnvEntries, buildSystemdUnit() renders Environment=OPENCLAW_GATEWAY_TOKEN=<token>, and fs.writeFile(unitPath, unit, "utf8") writes the resulting unit to ~/.config/systemd/user/openclaw-node.service without an explicit restrictive mode.
- From another same-host account or process that can read the target user's systemd unit path, read
~/.config/systemd/user/openclaw-node.service and extract the inline token.
- Present that token to any gateway HTTP or WebSocket client.
authorizeTokenAuth() compares it with the configured gateway token and returns { ok: true, method: "token" }, granting operator access.
Demonstrated Impact
The vulnerable path is specific to the Linux node-daemon installer. buildNodeServiceEnvironment() always copies OPENCLAW_GATEWAY_TOKEN into the generated service environment, buildNodeInstallPlan() returns only environment (not environmentValueSources), and runNodeDaemonInstall() passes that inline environment directly into service.install(). In the shared systemd writer, writeSystemdGatewayEnvironmentFile() only emits a protected 0600 env file for values sourced from the state-dir dotenv or an existing environment file; the node token never enters that path, so environmentFileResult.environmentFiles stays empty for the token and buildSystemdUnit() serializes it inline as Environment=OPENCLAW_GATEWAY_TOKEN=.... The resulting user unit is written with fs.writeFile(unitPath, unit, "utf8") and no follow-up chmod, so visibility falls back to the caller's directory and umask defaults instead of an owner-only policy. A reader that extracts the token bypasses pairing and any narrower device identity checks because the gateway auth layer accepts the shared bearer token as full operator access.
The same claim does not hold for the current macOS launchd path. prepareLaunchAgentProgramArguments() writes launchd environment values to ~/.openclaw/service-env/<label>.env with mode 0600, stores the wrapper script with mode 0700, and rewrites the plist to reference that wrapper instead of embedding the token inline. The verified remaining exposure is therefore the Linux user-systemd node install flow.
Environment
Verified against OpenClaw release v2026.5.4 (published 2026-05-05T08:24:01Z) and current upstream source commit 7188e4f4ad87a51a11d3dc3c7909fd79ea01d6e9 on the Linux node-daemon install path: openclaw/src/cli/node-cli/daemon.ts → openclaw/src/commands/node-daemon-install-helpers.ts → openclaw/src/daemon/service-env.ts → openclaw/src/daemon/systemd.ts / openclaw/src/daemon/systemd-unit.ts → openclaw/src/gateway/auth.ts. Scope was calibrated against the macOS launchd implementation in openclaw/src/daemon/launchd.ts, which already moves service environment variables into owner-only files.
Remediation Advice
Handle node-daemon gateway credentials with the same owner-only secret-file pattern already used by the launchd installer and by the Linux gateway installer for file-backed environment values. Carry environmentValueSources through the node install plan, mark OPENCLAW_GATEWAY_TOKEN as a managed secret that must not remain inline in the unit, and enforce owner-only permissions for any service artifact that still needs to reference credential material.
Severity Assessment
CVSS Assessment
Threat Model Alignment
Classification:
security-specificOpenClaw's gateway security guide states that shared-secret bearer credentials are full operator secrets for the gateway, and
authorizeTokenAuth()treats a matchingOPENCLAW_GATEWAY_TOKENas successful token authentication. The Linux node-daemon install path persists that token inside the generated user systemd unit, so any same-host principal that can read the unit crosses thegateway.authboundary without pairing or another gateway credential. SECURITY.md excludes exposed third-party or user-controlled credentials, but this token is OpenClaw's own gateway secret. The issue is therefore in scope as a credential-disclosure bug, even under the one-user model, because the documented mitigation for shared hosts is separate OS-user boundaries and this install path weakens that boundary when the unit inherits default read permissions.Impact
On Linux,
openclaw node installcopies the caller'sOPENCLAW_GATEWAY_TOKENinto the generatedopenclaw-node.serviceuser unit as an inlineEnvironment=entry. A same-host user or process that can read that unit recovers a bearer secret that OpenClaw accepts as full gateway operator authentication.Affected Component
Files:
openclaw/src/daemon/service-env.ts:421-449,openclaw/src/commands/node-daemon-install-helpers.ts:19-68,openclaw/src/cli/node-cli/daemon.ts:131-167,openclaw/src/daemon/systemd.ts:560-622,openclaw/src/daemon/systemd-unit.ts:20-35,openclaw/src/gateway/auth.ts:350-363Technical Reproduction
OPENCLAW_GATEWAY_TOKEN=<gateway-secret>.openclaw node install --host 127.0.0.1 --port 18789.runNodeDaemonInstall()passesprocess.envtobuildNodeInstallPlan(), which callsbuildNodeServiceEnvironment()and returns an environment object containingOPENCLAW_GATEWAY_TOKEN.resolveNodeService().install()forwards that environment into the shared systemd installer.writeSystemdUnit()keeps the node token inenvironmentSansDotEnvEntries,buildSystemdUnit()rendersEnvironment=OPENCLAW_GATEWAY_TOKEN=<token>, andfs.writeFile(unitPath, unit, "utf8")writes the resulting unit to~/.config/systemd/user/openclaw-node.servicewithout an explicit restrictive mode.~/.config/systemd/user/openclaw-node.serviceand extract the inline token.authorizeTokenAuth()compares it with the configured gateway token and returns{ ok: true, method: "token" }, granting operator access.Demonstrated Impact
The vulnerable path is specific to the Linux node-daemon installer.
buildNodeServiceEnvironment()always copiesOPENCLAW_GATEWAY_TOKENinto the generated service environment,buildNodeInstallPlan()returns onlyenvironment(notenvironmentValueSources), andrunNodeDaemonInstall()passes that inline environment directly intoservice.install(). In the shared systemd writer,writeSystemdGatewayEnvironmentFile()only emits a protected0600env file for values sourced from the state-dir dotenv or an existing environment file; the node token never enters that path, soenvironmentFileResult.environmentFilesstays empty for the token andbuildSystemdUnit()serializes it inline asEnvironment=OPENCLAW_GATEWAY_TOKEN=.... The resulting user unit is written withfs.writeFile(unitPath, unit, "utf8")and no follow-upchmod, so visibility falls back to the caller's directory and umask defaults instead of an owner-only policy. A reader that extracts the token bypasses pairing and any narrower device identity checks because the gateway auth layer accepts the shared bearer token as full operator access.The same claim does not hold for the current macOS launchd path.
prepareLaunchAgentProgramArguments()writes launchd environment values to~/.openclaw/service-env/<label>.envwith mode0600, stores the wrapper script with mode0700, and rewrites the plist to reference that wrapper instead of embedding the token inline. The verified remaining exposure is therefore the Linux user-systemd node install flow.Environment
Verified against OpenClaw release
v2026.5.4(published2026-05-05T08:24:01Z) and current upstream source commit7188e4f4ad87a51a11d3dc3c7909fd79ea01d6e9on the Linux node-daemon install path:openclaw/src/cli/node-cli/daemon.ts→openclaw/src/commands/node-daemon-install-helpers.ts→openclaw/src/daemon/service-env.ts→openclaw/src/daemon/systemd.ts/openclaw/src/daemon/systemd-unit.ts→openclaw/src/gateway/auth.ts. Scope was calibrated against the macOS launchd implementation inopenclaw/src/daemon/launchd.ts, which already moves service environment variables into owner-only files.Remediation Advice
Handle node-daemon gateway credentials with the same owner-only secret-file pattern already used by the launchd installer and by the Linux gateway installer for file-backed environment values. Carry
environmentValueSourcesthrough the node install plan, markOPENCLAW_GATEWAY_TOKENas a managed secret that must not remain inline in the unit, and enforce owner-only permissions for any service artifact that still needs to reference credential material.