Skip to content

[Fix] Keep node systemd tokens out of unit files#84408

Closed
samzong wants to merge 2 commits into
openclaw:mainfrom
samzong:fix/systemd-gateway-token-envfile
Closed

[Fix] Keep node systemd tokens out of unit files#84408
samzong wants to merge 2 commits into
openclaw:mainfrom
samzong:fix/systemd-gateway-token-envfile

Conversation

@samzong

@samzong samzong commented May 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Problem: Linux node host systemd installs could place OPENCLAW_GATEWAY_TOKEN inline in the generated unit, exposing the token through unit inspection and backups.
  • Solution: Treat the node gateway token as an EnvironmentFile-managed value and write it to the owner-only node systemd env file instead of inline Environment= directives.
  • What changed: node install planning now carries environment value sources into systemd staging; systemd writes node file-backed managed values to node.systemd.env, migrates operator entries from the legacy shared env file, and scrubs only OpenClaw-managed token entries on uninstall.
  • What did NOT change (scope boundary): gateway service env-file ownership and non-systemd service managers keep their existing behavior.

Motivation

  • Node service credentials should not be serialized into systemd unit files, and existing operator-added env-file entries must survive re-stage/upgrade paths.

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

Real behavior proof (required for external PRs)

Behavior addressed: openclaw node install --force on Linux user systemd keeps the node gateway token out of the generated unit, writes the current token to an owner-only node env file, migrates operator entries from the old shared systemd env file, and leaves only operator entries after uninstall.

Real environment tested: Docker Desktop Linux container running Debian 12 with systemd PID 1 and a real unprivileged user systemd manager (systemd 252, systemctl --user is-system-running=running, Linux 6.10.14-linuxkit aarch64).

Exact steps or command run after this patch:

OPENCLAW_GATEWAY_TOKEN=<redacted proof token> pnpm openclaw node install --force --host 127.0.0.1 --port 18789 --json
systemctl --user is-enabled openclaw-node.service
systemctl --user show openclaw-node.service --property=LoadState,ActiveState,SubState --no-page
pnpm openclaw node uninstall --json

Evidence after fix:

linux=Linux 6.10.14-linuxkit aarch64
systemd=systemd 252 (252.39-1~deb12u2)
user_manager=running
unit_path=/home/ocproof/.config/systemd/user/openclaw-node.service
envfile_path=/home/ocproof/.openclaw/node.systemd.env
envfile_mode=600
enabled_before_uninstall=enabled
show_before_uninstall=LoadState=loaded;ActiveState=active;SubState=running;
unit_has_inline_gateway_token=no
unit_has_token_value=no
node_envfile_had_gateway_token=yes
node_envfile_preserved_legacy_operator=yes
node_envfile_has_legacy_token=no
legacy_gateway_envfile_preserved_before_uninstall=yes
uninstall_removed_node_unit=yes
node_envfile_exists_after_uninstall=yes
node_envfile_token_after_uninstall=no
node_envfile_preserved_operator_after_uninstall=yes
uninstall_preserved_legacy_gateway_envfile=yes

Observed result after fix: The node service was enabled and running under the real user systemd manager. The generated unit referenced EnvironmentFile=-/home/ocproof/.openclaw/node.systemd.env, did not contain inline OPENCLAW_GATEWAY_TOKEN, and did not contain the redacted proof token value. The node env file was 0600, preserved a legacy operator key from gateway.systemd.env, replaced the legacy token with the current redacted token, and uninstall removed only the managed token while preserving operator env-file entries.

What was not tested: A live gateway connection was not started in the container; the node host stayed running and retried 127.0.0.1:18789, which is sufficient for credential placement, migration, and user-systemd install/uninstall proof.

Before evidence (optional but encouraged): Regression tests fail on the old behavior for legacy env-file migration and unlink failure cleanup; the new tests pass after this patch.

Root Cause (if applicable)

  • Root cause: node systemd installs reused generic systemd environment rendering without distinguishing inline-managed values from file-managed token values, so the gateway token could be rendered into the unit file.
  • Missing detection / guardrail: no regression covered node-specific systemd token placement, legacy env-file migration, or uninstall behavior when the unit file removal fails.
  • Contributing context (if known): prior gateway env-file preservation logic was correct for gateway services but did not model the node service's token ownership separately.

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/daemon/systemd.test.ts, src/commands/node-daemon-install-helpers.test.ts
  • Scenario the test should lock in: node file-backed gateway tokens are written to node.systemd.env, are not rendered inline in unit or backup files, preserve/migrate operator env-file entries, and are scrubbed safely during uninstall.
  • Why this is the smallest reliable guardrail: these tests exercise the service install plan and systemd renderer directly without needing a full daemon runtime.
  • Existing test that already covers this (if any): N/A
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

Linux node host systemd units no longer inline OPENCLAW_GATEWAY_TOKEN; the token is stored in ~/.openclaw/node.systemd.env with 0600 permissions.

Diagram (if applicable)

Before:
openclaw node install -> openclaw-node.service -> Environment=OPENCLAW_GATEWAY_TOKEN=...

After:
openclaw node install -> openclaw-node.service -> EnvironmentFile=~/.openclaw/node.systemd.env -> 0600 token file

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: token placement changes from inline unit text to an owner-only env file. The migration preserves operator entries and the real user-systemd proof verifies the unit does not contain the redacted token value.

Repro + Verification

Environment

  • OS: macOS local checkout for unit tests; Docker Desktop Linux container for real user-systemd proof
  • Runtime/container: Node 24.14.0, pnpm 11.1.0, Debian 12 systemd container
  • Model/provider: N/A
  • Integration/channel (if any): Linux user systemd node host service
  • Relevant config (redacted): OPENCLAW_GATEWAY_TOKEN=<redacted proof token>

Steps

  1. Seed a legacy ~/.openclaw/gateway.systemd.env with a redacted old token and an operator key.
  2. Run OPENCLAW_GATEWAY_TOKEN=<redacted proof token> pnpm openclaw node install --force --host 127.0.0.1 --port 18789 --json under an unprivileged user systemd manager.
  3. Inspect the generated unit, node env file mode/content markers, and service state.
  4. Run pnpm openclaw node uninstall --json and inspect the unit/env-file cleanup markers.

Expected

  • The generated unit does not contain the gateway token inline or by value.
  • node.systemd.env is 0600, contains the current token, and preserves legacy operator entries.
  • Uninstall removes the unit and removes only the managed token while preserving operator entries.

Actual

  • Matches expected; see copied live proof output above.

Evidence

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

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: targeted Vitest coverage, formatting, diff whitespace, autoreview, and real Linux user-systemd install/uninstall proof.
  • Edge cases checked: legacy shared env-file migration, stale managed token cleanup, unlink failure preserving token state, backup-unit token scrubbing, and uninstall preserving operator entries.
  • What you did not verify: live gateway connection from the node host to a running gateway.

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
  • Config/env changes? (Yes/No) Yes
  • Migration needed? (Yes/No) No manual migration
  • If yes, exact upgrade steps: re-running openclaw node install --force migrates operator entries from the legacy shared env file into node.systemd.env while replacing the managed token value.

Risks and Mitigations

  • Risk: existing operator entries in the old shared env file could be dropped when switching node services to the new env file.
    • Mitigation: migration reads the old shared file first and regression coverage plus runtime proof verify operator entries are preserved.

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime cli CLI command changes commands Command implementations size: L proof: supplied External PR includes structured after-fix real behavior proof. labels May 20, 2026
@samzong samzong force-pushed the fix/systemd-gateway-token-envfile branch from d6b81e6 to 8f96eb2 Compare May 20, 2026 03:04
@clawsweeper

clawsweeper Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

Workflow note: Future ClawSweeper reviews update this same comment in place.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

Summary
The PR changes Linux node systemd installs to treat OPENCLAW_GATEWAY_TOKEN as file-backed, write it to node.systemd.env, migrate legacy env-file operator entries, scrub token assignments from unit backups, and clean managed node tokens on uninstall.

Reproducibility: yes. Source inspection on current main shows the node token is included in service environment without file-backed metadata and the systemd renderer emits environment values inline; I did not run a local install path in this read-only review.

PR rating
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
Patch quality: 🐚 platinum hermit
Summary: Strong real-systemd proof and a focused implementation make the PR likely mergeable pending ordinary maintainer and CI review.

Rank-up moves:

  • none
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Real behavior proof
Sufficient (live_output): The PR body includes redacted copied live output from a real Debian user-systemd install/uninstall path showing the token absent from the unit and stored only in a mode-600 node env file.

Next step before merge
No automated repair is needed after the latest head addressed the prior single-quote sanitizer finding; the remaining action is ordinary maintainer review and CI.

Security
Cleared: The diff is security-sensitive and improves credential placement; after the single-quote sanitizer fix, I found no concrete remaining security or supply-chain regression.

Review details

Best possible solution:

Land this PR after normal CI and maintainer review, then use it as the canonical fix for #78043 and supersede #78044.

Do we have a high-confidence way to reproduce the issue?

Yes. Source inspection on current main shows the node token is included in service environment without file-backed metadata and the systemd renderer emits environment values inline; I did not run a local install path in this read-only review.

Is this the best way to solve the issue?

Yes. The PR uses the existing environment-source contract instead of adding a separate token path, adds node-specific env-file ownership, and covers backup sanitization, migration, and uninstall behavior.

Label changes:

  • add P1: The PR addresses a real Linux node-systemd credential exposure path for gateway bearer tokens, which is urgent but scoped to a specific install flow.
  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes redacted copied live output from a real Debian user-systemd install/uninstall path showing the token absent from the unit and stored only in a mode-600 node env file.
  • add rating: 🐚 platinum hermit: Current PR rating is 🐚 platinum hermit because proof is 🦞 diamond lobster, patch quality is 🐚 platinum hermit, and Strong real-systemd proof and a focused implementation make the PR likely mergeable pending ordinary maintainer and CI review.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes redacted copied live output from a real Debian user-systemd install/uninstall path showing the token absent from the unit and stored only in a mode-600 node env file.
  • remove rating: 🦐 gold shrimp: Current PR rating is rating: 🐚 platinum hermit, so this older rating label is no longer current.
  • remove status: ⏳ waiting on author: Current PR status label is status: 👀 ready for maintainer look.

Label justifications:

  • P1: The PR addresses a real Linux node-systemd credential exposure path for gateway bearer tokens, which is urgent but scoped to a specific install flow.
  • rating: 🐚 platinum hermit: Current PR rating is 🐚 platinum hermit because proof is 🦞 diamond lobster, patch quality is 🐚 platinum hermit, and Strong real-systemd proof and a focused implementation make the PR likely mergeable pending ordinary maintainer and CI review.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes redacted copied live output from a real Debian user-systemd install/uninstall path showing the token absent from the unit and stored only in a mode-600 node env file.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes redacted copied live output from a real Debian user-systemd install/uninstall path showing the token absent from the unit and stored only in a mode-600 node env file.

What I checked:

  • Current main lacks file-backed node token metadata: buildNodeInstallPlan() currently returns the node service environment without environmentValueSources, so the systemd installer has no source metadata telling it to keep OPENCLAW_GATEWAY_TOKEN out of inline unit environment rendering. (src/commands/node-daemon-install-helpers.ts:58, 18a514e39e8b)
  • Current main renders service environment inline: renderEnvLines() renders non-empty environment entries as Environment=${key}=${value}, which explains the reported current-main unit-file exposure when the token remains in the node service environment. (src/daemon/systemd-unit.ts:30, 18a514e39e8b)
  • PR carries the source metadata through install: The PR diff changes runNodeDaemonInstall() to pass environmentValueSources into service.install() and changes the node install plan to mark OPENCLAW_GATEWAY_TOKEN as file. (src/cli/node-cli/daemon.ts:136, 6e842f604219)
  • PR moves file-managed node values to node env file: The PR diff adds file-managed key collection, writes file-backed environment values into the systemd env-file path selected for node services, and filters those keys out of generated inline unit environment entries. (src/daemon/systemd.ts:202, 6e842f604219)
  • Prior review finding is addressed in latest head: The latest head adds parseSystemdEnvAssignments() with single-quote support and tests covering Environment='OPENCLAW_GATEWAY_TOKEN=single-quoted-token', resolving the earlier backup-sanitizer gap called out in the bot review comment. (src/daemon/systemd-unit.ts:138, 6e842f604219)
  • Systemd contract check: The upstream systemd documentation says Environment= uses systemd syntax where quotes wrap whole assignment items, env files override inline Environment=, and environment variables are not suitable for secrets; the PR's whole-assignment parsing and file-backed storage align with that contract.

Likely related people:

  • Md. Al-Mosabbir Rakib: Available blame for the central systemd install/write path and node install call path points to this current-main commit author across the inspected files. (role: current-main area contributor; confidence: low; commits: e00cb664ad4b; files: src/daemon/systemd.ts, src/cli/node-cli/daemon.ts, src/commands/node-daemon-install-helpers.ts)
  • coygeek: Filed the linked credential-disclosure issue and authored the older overlapping PR with the same remediation direction, making them useful context for comparing/superseding this branch. (role: related reporter and overlapping fix author; confidence: medium; commits: 3949a9fb6be1; files: src/daemon/systemd.ts, src/commands/node-daemon-install-helpers.ts, src/cli/node-cli/daemon.ts)

Codex review notes: model gpt-5.5, reasoning high; reviewed against 18a514e39e8b.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d6b81e60d4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/daemon/systemd.ts
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels May 20, 2026
@clawsweeper

clawsweeper Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

✨ Hatched: 🥚 common Sunspot Signal Puff

Hatch command

Comment @clawsweeper hatch when this PR is hatchable.

Hatchability rules:

  • Merged PRs are hatchable.
  • Open PRs are hatchable when they are status: 👀 ready for maintainer look, status: 🚀 automerge armed, or labeled clawsweeper:automerge.
  • Closed unmerged PRs are hatchable only when one of those hatchable labels is still present in the durable record.

Rarity: 🥚 common.
Trait: sparkles near resolved comments.
Image traits: location proof lagoon; accessory rollback rope; palette sunrise gold and clean white; mood proud; pose balancing on a branch marker; shell paper lantern shell; lighting calm overcast light; background gentle dashboard dots.
Share on X: post this hatch
Copy: My PR egg hatched a 🥚 common Sunspot Signal Puff in ClawSweeper.

What is this egg doing here?
  • Eggs appear after the PR passes real-behavior proof. It is here for vibes, not verdicts: it does not change labels, ratings, merge decisions, or automation.
  • The shell reacts to review momentum: open follow-up work warms it up, re-review makes it wobble, and a clean final review lets it hatch.
  • Hatchability usually comes from sufficient real-behavior proof, no blocking P0/P1/P2 findings, no security attention needed, and clean correctness. A merged PR is already final, so merge makes the egg hatchable independently.
  • The hatch is seeded from this repository and PR number, so the same PR keeps the same creature; the reviewed head SHA can only change safe visual details.
  • Rarity is just collectible sparkle: 🥚 common, 🌱 uncommon, 💎 rare, ✨ glimmer, and 🌈 legendary.

Signed-off-by: samzong <samzong.lu@gmail.com>
@samzong samzong force-pushed the fix/systemd-gateway-token-envfile branch from 8f96eb2 to a137175 Compare May 20, 2026 03:15
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 20, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels May 20, 2026
Signed-off-by: samzong <samzong.lu@gmail.com>
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 20, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P1 High-priority user-facing bug, regression, or broken workflow. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels May 20, 2026
@Takhoffman

Copy link
Copy Markdown
Contributor

@clawsweeper merge

@Takhoffman

Copy link
Copy Markdown
Contributor

@clawsweeper automerge

@clawsweeper clawsweeper Bot added the clawsweeper:automerge Maintainer opted this PR into bounded ClawSweeper-reviewed automerge label May 21, 2026
@clawsweeper

clawsweeper Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

🦞🔧
ClawSweeper automerge is enabled.

Draft PRs stay fix-only until GitHub marks them ready for review. Pause with /clawsweeper stop.

Automerge progress:

  • 2026-05-21 04:57:25 UTC review queued 6e842f604219 (queued)

@clawsweeper

clawsweeper Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper 🐠 reef update

Thanks for the work here. GitHub would not let ClawSweeper push to this branch with the available credentials, so the fix moved to a narrow replacement PR. nothing personal, just permission currents.

Why replacement: ClawSweeper could not update the source PR branch directly; GitHub did not grant sufficient push rights to the bot for that branch.
Replacement PR: #84815
Why close: this run explicitly closes the superseded source PR after the credited replacement PR is open, so review continues in one place.
This closeout is intentional for this run: the replacement PR is now the active review lane.
Credit follows the fix over to the replacement PR. no sneaky treasure grab.
Co-author credit kept:

fish notes: model gpt-5.5, reasoning high; reviewed against f626b66.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clawsweeper:automerge Maintainer opted this PR into bounded ClawSweeper-reviewed automerge cli CLI command changes commands Command implementations gateway Gateway runtime P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: L status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Linux node daemon install inlines gateway token into user systemd unit

2 participants