Back to skill

Security audit

OpenClaw Theme Patcher

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent theme-patching purpose, but it should be reviewed because its helper script can run unsafe shell commands and can write backups outside the intended directory.

Install only if you are comfortable with a skill that edits live OpenClaw UI bundles. Before running it, confirm the active OpenClaw path, make a separate backup, use a simple lowercase theme id with only letters, numbers, and hyphens, and avoid running the helper in an untrusted workspace or modified PATH environment.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/backup_theme_bundle.py:23
Finding

Shell Command Injection Through an Attacker-Controlled OpenClaw Executable Path

Content
View full analysis

Vulnerability Details

File Location: scripts/backup_theme_bundle.py, lines 23–26
Vulnerability Type: OS command injection through unsafe shell interpolation
Risk Level: Medium

Vulnerable Code:

python
def resolve_active_install() -> Path:
    openclaw_bin = run(["bash", "-lc", "which openclaw"])
    resolved = run(["bash", "-lc", f'readlink -f "{openclaw_bin}"'])
    return Path(resolved).resolve().parent

Technical Analysis

The path returned by which openclaw is interpolated directly into a command evaluated by bash -lc. Double quotes do not prevent command substitution or protect against an embedded double quote terminating the quoted argument.

An attacker who can influence the process environment, particularly PATH, can arrange for which openclaw to return a path containing shell syntax such as command substitution. When that path is inserted into the second bash -lc invocation, Bash interprets the embedded syntax instead of treating the complete path as inert data.

The shell is unnecessary for both executable discovery and symbolic-link resolution. Passing attacker-influenced data through a shell creates an avoidable command-injection boundary.

Attack Path

  1. An attacker gains the ability to influence the PATH used when the helper is launched, such as through a compromised workspace, wrapper script, or manipulated execution environment.
  2. The attacker creates an executable named openclaw under a directory whose path contains shell-interpreted syntax.
  3. The first command, which openclaw, returns the attacker-controlled path as text.
  4. The script interpolates that text into readlink -f "..." and executes it using bash -lc.
  5. Bash evaluates the injected syntax while processing the command.
  6. The attacker's command executes with the privileges of the user running the backup helper.

Impact Assessment

Successful exploitation permits arbitrary loca ...[truncated 483 chars]

Remediation
View remediation

Remediation Suggestions

Eliminate shell execution and resolve the executable using standard-library APIs:

python
def resolve_active_install() -> Path:
    openclaw_bin = shutil.which("openclaw")
    if openclaw_bin is None:
        raise FileNotFoundError("openclaw executable was not found in PATH")
    return Path(openclaw_bin).resolve().parent

Additional hardening measures:

  1. Remove the generic shell-based run() function if no other operation requires it.
  2. If subprocesses are necessary, pass arguments as a list without shell=True or a shell interpreter.
  3. Validate that the resolved executable belongs to an expected OpenClaw installation before reading its bundles.
  4. Consider accepting an explicit installation path so security-sensitive automation does not depend on a mutable PATH.
  5. Add a regression test using executable paths containing spaces, quotes, dollar signs, and command-substitution characters, confirming that none are interpreted by a shell.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/backup_theme_bundle.py:76
Finding

Unvalidated Theme Identifier Permits Backup Directory Path Traversal

Content
View full analysis

Vulnerability Details

File Location: scripts/backup_theme_bundle.py, lines 76–98
Vulnerability Type: Path traversal and unintended file overwrite
Risk Level: Medium

Vulnerable Code:

python
    theme_id = args.theme_id.strip()
    out_base = Path(args.output_dir).expanduser().resolve()
    backup_dir = out_base / f"openclaw-{theme_id}-theme"
    backup_dir.mkdir(parents=True, exist_ok=True)

    js_path = find_asset("js")
    css_path = find_asset("css")
    js_text = js_path.read_text(encoding="utf-8", errors="ignore")
    css_text = css_path.read_text(encoding="utf-8", errors="ignore")

    dark_selector = f":root[data-theme={theme_id}]"
    light_selector = f":root[data-theme={theme_id}-light]"
    dark_block = extract_css_block(css_text, dark_selector)
    light_block = extract_css_block(css_text, light_selector)
    js_snippets = extract_js_snippets(js_text, theme_id)

    (backup_dir / "bundle-js.txt").write_text(js_text, encoding="utf-8")
    (backup_dir / "bundle-css.txt").write_text(css_text, encoding="utf-8")
    if dark_block:
        (backup_dir / f"{theme_id}.dark.css").write_text(dark_block, encoding="utf-8")
    if light_block:
        (backup_dir / f"{theme_id}.light.css").write_text(light_block, encoding="utf-8")

Technical Analysis

The command-line theme_id is used directly in directory and file path construction without enforcing the documented lowercase, short, stable identifier format. A value containing path separators and parent-directory components can cause the normalized destination to leave out_base.

The fixed openclaw- prefix does not prevent traversal. An identifier can first introduce a child path and then use repeated parent-directory components. The resulting backup_dir is created with parents=True, and existing files with fixed backup names are overwritten by write_text().

The theme identifier is also reused in the dark and l ...[truncated 1630 chars]

Remediation
View remediation

Remediation Suggestions

Enforce the theme identifier format before using it in selectors or paths:

python
theme_id = args.theme_id.strip()
if not re.fullmatch(r"[a-z0-9][a-z0-9-]{0,63}", theme_id):
    ap.error(
        "theme_id must be 1–64 characters and contain only "
        "lowercase letters, digits, and hyphens"
    )

Resolve and constrain the destination:

python
out_base = Path(args.output_dir).expanduser().resolve()
backup_dir = (out_base / f"openclaw-{theme_id}-theme").resolve()

try:
    backup_dir.relative_to(out_base)
except ValueError:
    ap.error("Resolved backup directory is outside the output directory")

Additional hardening measures:

  1. Apply the same containment check to every dynamically constructed output file.
  2. Reject absolute paths, path separators, . components, and .. components in identifiers.
  3. Refuse to overwrite an existing backup unless the caller explicitly supplies an overwrite option.
  4. Write files atomically using temporary files inside the validated backup directory followed by a rename.
  5. Create backups with restrictive permissions if bundle contents are considered sensitive.
  6. Add tests for absolute paths, repeated traversal sequences, mixed separators, empty identifiers, Unicode edge cases, and excessively long identifiers.
Vulnerability Patterns
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (6)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The stated purpose is theme patching, but the skill also instructs creation of backups, export of full JS/CSS bundles, and extraction of internal snippets and metadata to workspace files. That is a meaningful expansion of data-access behavior because it can copy proprietary application code or sensitive local modifications out of the live install without making that collection behavior explicit in the primary description.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill instructs the agent to use shell commands and to read/write live application bundles, but it declares no explicit tool restrictions or permission scope. That increases the chance an agent will execute broad filesystem or shell actions beyond the intended install path, especially because the workflow includes path discovery and direct patching of active assets.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill normalizes direct editing of installed, hashed frontend bundles in the active application without an upfront warning that this modifies live production files and may break the UI. Because it targets the active install rather than a source checkout, mistakes can immediately degrade the running application and make upgrades or troubleshooting harder.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · SKILL.md (reported line 16)May include surrounding context.

md
Use this skill for installed bundle patching, not upstream source development.

Default rule: add a new theme from the user's requested palette. Do not overwrite built-in themes unless the user explicitly asks.

For migrations, use a stricter rule set:
- preserve the user's old custom theme first

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

This markdown file describes modifying live application bundles and creating backups, which can affect system integrity and application behavior. While it mentions reversibility and restoring stock UI later, it does not give a clear upfront user warning that editing production bundles is risky and may cause breakage or downtime.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/backup_theme_bundle.py (reported line 20)May include surrounding context.

python
def run(cmd: list[str]) -> str:
    return subprocess.check_output(cmd, text=True).strip()


def resolve_active_install() -> Path:

Static analysis

No suspicious patterns detected.