Repository navigation
fix(security): Command injection in publish-openclaw-skills.js via skill directory name (double-quote wrapper does not s - #1163
Conversation
…ill directory name (double-quote wrapper does not s
STiFLeR7
left a comment
There was a problem hiding this comment.
Summary. publish-openclaw-skills.js built a shell command line and ran it with spawnSync(cmdline, {shell: true}); quote() only wraps unsafe tokens in double quotes, which does not stop $()/backtick command substitution in POSIX sh. A crafted .openclaw/skills/<payload>/SKILL.md directory name or passthrough arg could run arbitrary commands with the publisher's privileges. The fix switches to spawnSync(args[0], args.slice(1), {...}) with no shell, so no shell ever interprets the directory name or passthrough args.
Verified. (cloned fresh, read the full file, not just the diff)
- Confirmed the vulnerability claim directly:
quote()'s double-quote wrapping does not suppress$(...)/backticks under POSIX sh — this is standard shell behavior, and the file's only args considered "safe" without quoting are[\w./-], so an attacker-controlled skill directory name containing$(...)passes through live. - Confirmed the fix closes it: with no
shell: trueand args passed as an array, nothing parses metacharacters in any arg;clawhubis a fixed literal string (args[0]), never attacker-influenced.
Findings.
- The fix breaks this script on Windows (
scripts/publish-openclaw-skills.js:67, removedshell: true).
The file's own comment says thequote()scheme is meant to satisfy "both POSIX sh and cmd.exe," and the prereqs assumeclawhubis installed as a global npm CLI — which means its Windows binary is a.cmd/.ps1shim, not a literal.exe. I reproduced this empirically on a real Windows box:spawnSync('clawhub', args, {cwd})with noshell: truefails withENOENT(bare name can't resolve the shim), andspawnSync('<path>\clawhub.cmd', args, {cwd})with noshell: truefails withEINVAL(Node refuses to exec.cmddirectly without a shell) — same class of bug as this ecosystem's ownfix(win32): probe .cmd/.bat runtime shims via cmd.exeprecedent (Tencent/teamai-cli#505).
Suggested direction: onwin32, invoke viaspawnSync('cmd.exe', ['/c', ...args], {cwd: root})instead of the bare array form —cmd.exedoesn't support$()/backtick substitution at all (that's a POSIX-sh-only feature), so this keeps the injection fix intact while restoring.cmdresolution. POSIX can keep the no-shell array form as-is.
Not covered / unverified. I didn't check whether this script is CI-exercised (publish scripts usually aren't, they're maintainer-run), so this may not show red anywhere even though it's a real regression for a Windows-using maintainer. I didn't test an actual clawhub binary, only a synthetic .cmd shim reproducing the same resolution behavior Node documents for Windows.
Verdict. Comment. The security fix is correct and valuable — merge it. The Windows regression is real and reproducible, but it's a follow-up fix, not a reason to hold up a confirmed command-injection patch.
Security fix: Command injection in publish-openclaw-skills.js via skill directory name (double-quote wrapper does not stop $()/backtick expansion)
Severity: HIGH (CVSS 6.9)
CWE: CWE-78
File:
scripts/publish-openclaw-skills.jsWhat this fixes
scripts/publish-openclaw-skills.js builds a single shell command line and runs it with spawnSync(cmdline, { shell: true }). quote() wraps any token containing a character outside [\w./-] in DOUBLE quotes, but double quotes do not suppress command substitution, so $(...) and backticks inside the wrapped token still execute. The tokens include the raw name of any directory under .openclaw/skills/ that has a SKILL.md, and process.argv.slice(2) passed through verbatim.
Preconditions: POSIX sh (the default /bin/sh used by shell:true); an attacker can add a directory .openclaw/skills//SKILL.md to the checkout (malicious PR / poisoned tarball) or otherwise influence argv[2:]. The operator then runs
node scripts/publish-openclaw-skills.js.The change
Replaced the shell-string spawnSync(cmdline, { shell: true }) call with an argv-array spawnSync(args[0], args.slice(1), { stdio: 'inherit', cwd: root }) so no shell interprets the skill directory name or passthrough argv, eliminating command substitution. The display-only cmdline is preserved unchanged.
Impact if unpatched
Arbitrary command execution with the publisher's privileges (typically a maintainer or CI identity holding a logged-in clawhub token) whenever the publish script runs; an attacker who lands one crafted directory name in .openclaw/skills/ can exfiltrate the registry token or tamper with published skills.
Prepared by OpenClaw BountyBot. Finding surfaced by Failsafe Nexus (Pandora) and the patch adversarially reviewed by GLM. Please review carefully — automated patches are a starting point, not a guarantee.