OpenClaw 2026.2.19-2 < 2026.2.21 - Command Injection via Newline in systemd Unit Generation
Description
OpenClaw version 2026.2.19-2 prior to 2026.2.21 contains a command injection vulnerability in systemd unit file generation where attacker-controlled environment values are not validated for CR/LF characters, allowing newline injection to break out of Environment= lines and inject arbitrary systemd directives. An attacker who can influence config.env.vars and trigger service install or restart can execute arbitrary commands with the privileges of the OpenClaw gateway service user.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
openclawnpm | < 2026.2.21 | 2026.2.21 |
Affected products
1Patches
161f646c41fb4Daemon: harden systemd unit env rendering
3 files changed · +46 −5
CHANGELOG.md+1 −0 modified@@ -53,6 +53,7 @@ Docs: https://docs.openclaw.ai - Discord/Gateway: handle close code 4014 (missing privileged gateway intents) without crashing the gateway. Thanks @thewilloftheshadow. - Security/Net: strip sensitive headers (`Authorization`, `Proxy-Authorization`, `Cookie`, `Cookie2`) on cross-origin redirects in `fetchWithSsrFGuard` to prevent credential forwarding across origin boundaries. (#20313) Thanks @afurm. +- Security/Systemd: reject CR/LF in systemd unit environment values and fix argument escaping so generated units cannot be injected with extra directives. Thanks @thewilloftheshadow. - Skills/Security: sanitize skill env overrides to block unsafe runtime injection variables and only allow sensitive keys when declared in skill metadata, with warnings for suspicious values. Thanks @thewilloftheshadow. - Auto-reply/Runner: emit `onAgentRunStart` only after agent lifecycle or tool activity begins (and only once per run), so fallback preflight errors no longer mark runs as started. (#21165) Thanks @shakkernerd. - Agents/Failover: treat non-default override runs as direct fallback-to-configured-primary (skip configured fallback chain), normalize default-model detection for provider casing/whitespace, and add regression coverage for override/auth error paths. (#18820) Thanks @Glucksberg.
src/daemon/systemd-unit.test.ts+26 −0 added@@ -0,0 +1,26 @@ +import { describe, expect, it } from "vitest"; +import { buildSystemdUnit } from "./systemd-unit.js"; + +describe("buildSystemdUnit", () => { + it("quotes arguments with whitespace", () => { + const unit = buildSystemdUnit({ + description: "OpenClaw Gateway", + programArguments: ["/usr/bin/openclaw", "gateway", "--name", "My Bot"], + environment: {}, + }); + const execStart = unit.split("\n").find((line) => line.startsWith("ExecStart=")); + expect(execStart).toBe('ExecStart=/usr/bin/openclaw gateway --name "My Bot"'); + }); + + it("rejects environment values with line breaks", () => { + expect(() => + buildSystemdUnit({ + description: "OpenClaw Gateway", + programArguments: ["/usr/bin/openclaw", "gateway", "start"], + environment: { + INJECT: "ok\nExecStartPre=/bin/touch /tmp/oc15789_rce", + }, + }), + ).toThrow(/CR or LF/); + }); +});
src/daemon/systemd-unit.ts+19 −5 modified@@ -1,8 +1,17 @@ import { splitArgsPreservingQuotes } from "./arg-split.js"; import type { GatewayServiceRenderArgs } from "./service-types.js"; +const SYSTEMD_LINE_BREAKS = /[\r\n]/; + +function assertNoSystemdLineBreaks(value: string, label: string): void { + if (SYSTEMD_LINE_BREAKS.test(value)) { + throw new Error(`${label} cannot contain CR or LF characters.`); + } +} + function systemdEscapeArg(value: string): string { - if (!/[\\s"\\\\]/.test(value)) { + assertNoSystemdLineBreaks(value, "Systemd unit values"); + if (!/[\s"\\]/.test(value)) { return value; } return `"${value.replace(/\\\\/g, "\\\\\\\\").replace(/"/g, '\\\\"')}"`; @@ -18,9 +27,12 @@ function renderEnvLines(env: Record<string, string | undefined> | undefined): st if (entries.length === 0) { return []; } - return entries.map( - ([key, value]) => `Environment=${systemdEscapeArg(`${key}=${value?.trim() ?? ""}`)}`, - ); + return entries.map(([key, value]) => { + const rawValue = value ?? ""; + assertNoSystemdLineBreaks(key, "Systemd environment variable names"); + assertNoSystemdLineBreaks(rawValue, "Systemd environment variable values"); + return `Environment=${systemdEscapeArg(`${key}=${rawValue.trim()}`)}`; + }); } export function buildSystemdUnit({ @@ -30,7 +42,9 @@ export function buildSystemdUnit({ environment, }: GatewayServiceRenderArgs): string { const execStart = programArguments.map(systemdEscapeArg).join(" "); - const descriptionLine = `Description=${description?.trim() || "OpenClaw Gateway"}`; + const descriptionValue = description?.trim() || "OpenClaw Gateway"; + assertNoSystemdLineBreaks(descriptionValue, "Systemd Description"); + const descriptionLine = `Description=${descriptionValue}`; const workingDirLine = workingDirectory ? `WorkingDirectory=${systemdEscapeArg(workingDirectory)}` : null;
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/openclaw/openclaw/commit/61f646c41fb43cd87ed48f9125b4718a30d38e84ghsapatchWEB
- github.com/advisories/GHSA-vffc-f7r7-rx2wghsaADVISORY
- github.com/openclaw/openclaw/security/advisories/GHSA-vffc-f7r7-rx2wghsavendor-advisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2026-32063ghsaADVISORY
- www.vulncheck.com/advisories/openclaw-command-injection-via-newline-in-systemd-unit-generationghsathird-party-advisoryWEB
News mentions
0No linked articles in our index yet.