Back to skill

Security audit

Holocube Emotes

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its Holocube-control purpose, but its setup tooling can delete device images and has an unsafe backup path that could overwrite local files if pointed at a malicious or compromised device.

Review before installing. Use it only with a Holocube you control, avoid pointing --ip at untrusted hosts, make an independent backup before setup, avoid onboard.py unless you are willing to lose existing device images, and provide Gemini credentials through the environment or config rather than a command-line argument.

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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/setup_device.py:35
Finding

Device-Controlled Path Traversal in Image Backup

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate_sprites.py:263
Finding

Gemini API Key Can Be Exposed Through Command-Line Arguments

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/onboard.py:151
Finding

Onboarding Unconditionally Deletes Existing Device Images

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (31)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The mismatch here is especially risky because the skill reportedly includes backup and optional clearing/deletion of all device images without clearly declaring those destructive effects. In the context of an IoT display skill, undisclosed deletion or reset-adjacent file management can cause data loss, disrupt the device state, and make users underestimate the consequences of running setup commands.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The mismatch here is especially risky because the skill reportedly includes backup and optional clearing/deletion of all device images without clearly declaring those destructive effects. In the context of an IoT display skill, undisclosed deletion or reset-adjacent file management can cause data loss, disrupt the device state, and make users underestimate the consequences of running setup commands.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding

The mismatch here is especially risky because the skill reportedly includes backup and optional clearing/deletion of all device images without clearly declaring those destructive effects. In the context of an IoT display skill, undisclosed deletion or reset-adjacent file management can cause data loss, disrupt the device state, and make users underestimate the consequences of running setup commands.

Content

No source excerpt is available for this finding.

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
93% confidence
Finding

Copying the entire parent environment and passing it to a child process can expose unrelated secrets, tokens, proxy settings, and credentials to the invoked helper script. In this skill, the child process is an external dependency discovered from the system, so inheriting all environment variables unnecessarily broadens the blast radius if that dependency is compromised or replaced.

Content

Scanner excerpt · scripts/generate_sprites.py (reported line 81)May include surrounding context.

python
prompt = f"{character_desc} The robot looks friendly and slightly mischievous. Square composition."

    print(f"Generating base character...")
    env = os.environ.copy()
    env["GEMINI_API_KEY"] = api_key

    result = subprocess.run(

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
93% confidence
Finding

This line repeats the same pattern of forwarding the full process environment to an external helper, which can leak unrelated environment secrets to child code. Because the skill's purpose is image generation for a holocube, broad environment inheritance is not necessary for functionality and therefore increases risk without clear benefit.

Content

Scanner excerpt · scripts/generate_sprites.py (reported line 100)May include surrounding context.

python
out_path = output_dir / f"{emote}.png"
    prompt = f"Make this robot head {desc} Keep the same wireframe holographic style on pure black background. Same character."

    env = os.environ.copy()
    env["GEMINI_API_KEY"] = api_key

    result = subprocess.run(

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill instructs the agent to use shell, network discovery, file writes, and environment-dependent sprite generation, but it declares no explicit tool scope or permissions. That makes the operational boundary unclear and can lead to an agent invoking powerful capabilities such as subnet scanning, device modification, and file deletion without prior restriction or user visibility.

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/generate_sprites.py (reported line 50)May include surrounding context.

python
return p
    # Try finding via npm
    try:
        result = subprocess.run(["npm", "root", "-g"], capture_output=True, text=True)
        root = result.stdout.strip()
        p = os.path.join(root, "openclaw/skills/nano-banana-pro/scripts/generate_image.py")
        if os.path.exists(p):

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The script reads a Gemini API key from the user's global OpenClaw configuration even though its visible task is local sprite generation. Accessing secrets from a broader application config increases secret exposure and expands trust to any downstream component invoked by this script, especially since the key is then forwarded to another script through the environment.

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/generate_sprites.py (reported line 84)May include surrounding context.

python
env = os.environ.copy()
    env["GEMINI_API_KEY"] = api_key

    result = subprocess.run(
        ["uv", "run", script_path, "--prompt", prompt, "--filename", str(base_path), "--resolution", "1K"],
        capture_output=True, text=True, timeout=60, env=env,
    )

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/generate_sprites.py (reported line 103)May include surrounding context.

python
env = os.environ.copy()
    env["GEMINI_API_KEY"] = api_key

    result = subprocess.run(
        ["uv", "run", script_path, "--prompt", prompt, "--filename", str(out_path), "-i", str(base_path), "--resolution", "1K"],
        capture_output=True, text=True, timeout=60, env=env,
    )

Dynamic Request Target

Medium
Category
Server-Side Request Forgery
Confidence
87% confidence
Finding

The script accepts a user-supplied --ip value and uses it directly in an HTTP request target, allowing the process to connect to arbitrary hosts rather than only the intended holocube device. In an agent or automation context, untrusted input could abuse this to probe internal services or reach unexpected endpoints from the machine running the skill.

Content

Scanner excerpt · scripts/holocube.py (reported line 106)May include surrounding context.

python
"""Check holocube connectivity and storage."""
    ip = ip or DEFAULT_IP
    try:
        with urllib.request.urlopen(f"http://{ip}/v.json", timeout=3) as r:
            info = json.loads(r.read())
        with urllib.request.urlopen(f"http://{ip}/space.json", timeout=3) as r:
            space = json.loads(r.read())

Dynamic Request Target

Medium
Category
Server-Side Request Forgery
Confidence
87% confidence
Finding

This second request reuses the same unvalidated --ip-derived destination, expanding the attacker-controlled network interaction to another endpoint on the target host. If an agent can be induced to run this with arbitrary input, it can be used for internal network enumeration and limited SSRF-style probing from the local environment.

Content

Scanner excerpt · scripts/holocube.py (reported line 108)May include surrounding context.

python
try:
        with urllib.request.urlopen(f"http://{ip}/v.json", timeout=3) as r:
            info = json.loads(r.read())
        with urllib.request.urlopen(f"http://{ip}/space.json", timeout=3) as r:
            space = json.loads(r.read())
        print(f"Model: {info.get('m', 'unknown')}")
        print(f"Version: {info.get('v', 'unknown')}")

Tainted flow: 'ip' from input (line 222, user input) → urllib.request.urlopen (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/onboard.py (reported line 30)May include surrounding context.

python
"""Scan local subnet for GeekMagic devices."""
    def check(ip):
        try:
            with urllib.request.urlopen(f"http://{ip}/v.json", timeout=1) as r:
                data = json.loads(r.read())
                if "m" in data:
                    return ip, data

Tainted flow: 'ip' from input (line 231, user input) → urllib.request.urlopen (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/onboard.py (reported line 189)May include surrounding context.

python
urllib.request.urlopen(f"http://{ip}/set?img=%2Fimage%2Fadam-neutral.gif", timeout=3)
    print("\n  Set to Photo Album, default: neutral")

    with urllib.request.urlopen(f"http://{ip}/space.json", timeout=3) as r:
        space = json.loads(r.read())
    print(f"  Storage: {space.get('free', 0) // 1024}KB free")

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The onboarding script silently reads a Gemini API key from an unrelated local OpenClaw configuration path rather than limiting itself to credentials the user explicitly provides for this setup. Cross-skill credential harvesting increases the blast radius of a compromised or overly broad skill and violates least-privilege expectations, especially because the key is then reused by another component for external API calls.

Content

No source excerpt is available for this finding.

Dynamic Request Target

Medium
Category
Server-Side Request Forgery
Confidence
60% confidence
Finding

Request target host is built from a dynamic or untrusted value. If the host is attacker-influenced, this enables SSRF to arbitrary internal or metadata endpoints.

Content

Scanner excerpt · scripts/holocube.py (reported line 32)May include surrounding context.

python
def check_device(ip):
    """Verify holocube is reachable."""
    try:
        with urllib.request.urlopen(f"http://{ip}/v.json", timeout=3) as r:
            info = json.loads(r.read())
        with urllib.request.urlopen(f"http://{ip}/space.json", timeout=3) as r:
            space = json.loads(r.read())

Dynamic Request Target

Medium
Category
Server-Side Request Forgery
Confidence
60% confidence
Finding

Request target host is built from a dynamic or untrusted value. If the host is attacker-influenced, this enables SSRF to arbitrary internal or metadata endpoints.

Content

Scanner excerpt · scripts/onboard.py (reported line 30)May include surrounding context.

python
def check_device(ip):
    """Verify holocube is reachable."""
    try:
        with urllib.request.urlopen(f"http://{ip}/v.json", timeout=3) as r:
            info = json.loads(r.read())
        with urllib.request.urlopen(f"http://{ip}/space.json", timeout=3) as r:
            space = json.loads(r.read())

Dynamic Request Target

Medium
Category
Server-Side Request Forgery
Confidence
60% confidence
Finding

Request target host is built from a dynamic or untrusted value. If the host is attacker-influenced, this enables SSRF to arbitrary internal or metadata endpoints.

Content

Scanner excerpt · scripts/onboard.py (reported line 158)May include surrounding context.

python
def check_device(ip):
    """Verify holocube is reachable."""
    try:
        with urllib.request.urlopen(f"http://{ip}/v.json", timeout=3) as r:
            info = json.loads(r.read())
        with urllib.request.urlopen(f"http://{ip}/space.json", timeout=3) as r:
            space = json.loads(r.read())

Dynamic Request Target

Medium
Category
Server-Side Request Forgery
Confidence
60% confidence
Finding

Request target host is built from a dynamic or untrusted value. If the host is attacker-influenced, this enables SSRF to arbitrary internal or metadata endpoints.

Content

Scanner excerpt · scripts/onboard.py (reported line 184)May include surrounding context.

python
def check_device(ip):
    """Verify holocube is reachable."""
    try:
        with urllib.request.urlopen(f"http://{ip}/v.json", timeout=3) as r:
            info = json.loads(r.read())
        with urllib.request.urlopen(f"http://{ip}/space.json", timeout=3) as r:
            space = json.loads(r.read())

Dynamic Request Target

Medium
Category
Server-Side Request Forgery
Confidence
60% confidence
Finding

Request target host is built from a dynamic or untrusted value. If the host is attacker-influenced, this enables SSRF to arbitrary internal or metadata endpoints.

Content

Scanner excerpt · scripts/onboard.py (reported line 186)May include surrounding context.

python
def check_device(ip):
    """Verify holocube is reachable."""
    try:
        with urllib.request.urlopen(f"http://{ip}/v.json", timeout=3) as r:
            info = json.loads(r.read())
        with urllib.request.urlopen(f"http://{ip}/space.json", timeout=3) as r:
            space = json.loads(r.read())

Dynamic Request Target

Medium
Category
Server-Side Request Forgery
Confidence
60% confidence
Finding

Request target host is built from a dynamic or untrusted value. If the host is attacker-influenced, this enables SSRF to arbitrary internal or metadata endpoints.

Content

Scanner excerpt · scripts/onboard.py (reported line 189)May include surrounding context.

python
def check_device(ip):
    """Verify holocube is reachable."""
    try:
        with urllib.request.urlopen(f"http://{ip}/v.json", timeout=3) as r:
            info = json.loads(r.read())
        with urllib.request.urlopen(f"http://{ip}/space.json", timeout=3) as r:
            space = json.loads(r.read())

Dynamic Request Target

Medium
Category
Server-Side Request Forgery
Confidence
60% confidence
Finding

Request target host is built from a dynamic or untrusted value. If the host is attacker-influenced, this enables SSRF to arbitrary internal or metadata endpoints.

Content

Scanner excerpt · scripts/setup_device.py (reported line 21)May include surrounding context.

python
def check_device(ip):
    """Verify holocube is reachable."""
    try:
        with urllib.request.urlopen(f"http://{ip}/v.json", timeout=3) as r:
            info = json.loads(r.read())
        with urllib.request.urlopen(f"http://{ip}/space.json", timeout=3) as r:
            space = json.loads(r.read())

Dynamic Request Target

Medium
Category
Server-Side Request Forgery
Confidence
60% confidence
Finding

Request target host is built from a dynamic or untrusted value. If the host is attacker-influenced, this enables SSRF to arbitrary internal or metadata endpoints.

Content

Scanner excerpt · scripts/setup_device.py (reported line 23)May include surrounding context.

python
def check_device(ip):
    """Verify holocube is reachable."""
    try:
        with urllib.request.urlopen(f"http://{ip}/v.json", timeout=3) as r:
            info = json.loads(r.read())
        with urllib.request.urlopen(f"http://{ip}/space.json", timeout=3) as r:
            space = json.loads(r.read())

Dynamic Request Target

Medium
Category
Server-Side Request Forgery
Confidence
60% confidence
Finding

Request target host is built from a dynamic or untrusted value. If the host is attacker-influenced, this enables SSRF to arbitrary internal or metadata endpoints.

Content

Scanner excerpt · scripts/setup_device.py (reported line 39)May include surrounding context.

python
def check_device(ip):
    """Verify holocube is reachable."""
    try:
        with urllib.request.urlopen(f"http://{ip}/v.json", timeout=3) as r:
            info = json.loads(r.read())
        with urllib.request.urlopen(f"http://{ip}/space.json", timeout=3) as r:
            space = json.loads(r.read())

Dynamic Request Target

Medium
Category
Server-Side Request Forgery
Confidence
60% confidence
Finding

Request target host is built from a dynamic or untrusted value. If the host is attacker-influenced, this enables SSRF to arbitrary internal or metadata endpoints.

Content

Scanner excerpt · scripts/setup_device.py (reported line 66)May include surrounding context.

python
def check_device(ip):
    """Verify holocube is reachable."""
    try:
        with urllib.request.urlopen(f"http://{ip}/v.json", timeout=3) as r:
            info = json.loads(r.read())
        with urllib.request.urlopen(f"http://{ip}/space.json", timeout=3) as r:
            space = json.loads(r.read())

Static analysis

No suspicious patterns detected.