Back to skill

Security audit

πŸ–₯️ Canvas-OS

Security checks for vulnerabilities and agentic risk

Overview

This Canvas app skill is understandable in purpose, but it needs review because it can expose local files, force-stop unrelated local processes, and run injected page code with weak safeguards.

Review this before installing. Use it only for trusted local Canvas apps and trusted HTML/data. Avoid passing untrusted app names, ports, or generated HTML into the helper scripts. Do not let it kill a port owner unless you have checked what process is using the port, and prefer binding any local server to 127.0.0.1. Be aware that tracker/dashboard interactions can send text back to the OpenClaw agent through deep links.

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

T09 Β· Insecure Skill Coding Practices

Error
Location
open-app.sh:7
Finding

Directory Traversal Allows Directories Outside the App Root to Be Served Over the Network

Content
View full analysis
/dev/null | jq -r '.nodes[0].displayName' 2>/dev/null)}" APPS_DIR="${CANVAS_APPS_DIR:-$HOME/.openclaw/workspace/apps}" if [ -z "$NODE" ] || [ "$NODE" = "null" ]; then echo "❌ No node found. Run: openclaw nodes status" exit 1 fi echo "πŸš€ Opening $APP_NAME on port $PORT (node: $NODE)..." # Kill any existing server on this port lsof -ti:$PORT 2>/dev/null | xargs kill -9 2>/dev/null # Check app exists if [ ! -d "$APPS_DIR/$APP_NAME" ]; then echo "❌ App not found: $APPS_DIR/$APP_NAME" exit 1 fi # Start server cd "$APPS_DIR/$APP_NAME" python3 -m http.server $PORT > /dev/null 2>&1 & ``` ### Technical Analysis `APP_NAME` is accepted as an unrestricted command-line argument and appended directly to `APPS_DIR`. The directory existence check does not canonicalize the resulting path or verify that it remains beneath the configured application root. An input containing traversal components, such as `../../..`, can therefore cause the script to change into and serve a directory outside `~/.openclaw/workspace/apps`. In addition, `python3 -m http.server` binds to all available interfaces by default. Consequently, files in the escaped directory may be available not only to Canvas through localhost but also to other systems able to connect to the host. The port is also not validated as a numeric value in the permitted TCP port range. ### Attack Path 1. An attacker or untrusted caller invokes `open-app.sh` with an application name containing path traversal components. 2. The expression `"$APPS_DIR/$APP_NAME"` resolves to a directory outside the intended application root. 3. The `-d` check succeeds because the escaped target is an existing directory. 4. The script changes its wor ...[truncated 741 chars]
Remediation
View remediation
&2 exit 1 ;; esac ``` 2. Canonicalize both the app root and requested directory using `realpath`. 3. Verify that the canonical requested path starts with the canonical application root followed by a path separator. 4. Reject symlinks or resolve them before performing the containment check. 5. Bind the HTTP server explicitly to the loopback interface: ```bash python3 -m http.server "$PORT" --bind 127.0.0.1 ``` 6. Validate that `PORT` contains only digits and is between 1 and 65535. 7. Quote every use of the port argument. 8. Run the server under a minimally privileged account with access only to the intended app directory where feasible. ]]>

T09 Β· Insecure Skill Coding Practices

Warning
Location
close-app.sh:10
Finding

Predictable and Unverified PID Files Can Cause Arbitrary Process Termination

Content
View full analysis
"/tmp/canvas-app-$APP_NAME.pid" ``` From `close-app.sh`: ```bash # Kill server if PID file exists PID_FILE="/tmp/canvas-app-$APP_NAME.pid" if [ -f "$PID_FILE" ]; then PID=$(cat "$PID_FILE") kill -9 $PID 2>/dev/null && echo "πŸ“‘ Server stopped (PID: $PID)" rm "$PID_FILE" else echo "⚠️ No PID file found for $APP_NAME" fi ``` ### Technical Analysis The scripts create and consume a predictable file in the shared `/tmp` directory. The file is not created atomically, and the cleanup script does not verify: - Ownership or permissions of the PID file. - Whether the file is a symbolic link. - Whether its contents are a single valid numeric PID. - Whether the PID still belongs to the HTTP server launched by this Skill. - Whether a stale PID has been reused by an unrelated process. The PID is also passed to `kill` without quoting or strict validation. The use of `SIGKILL` prevents graceful cleanup and gives the target process no opportunity to save state. ### Attack Path 1. A local attacker predicts the filename associated with a chosen application name. 2. The attacker creates, replaces, or influences `/tmp/canvas-app-.pid`. 3. The attacker writes the PID of another process owned by the same user into that file. 4. The user or agent invokes `close-app.sh` for that application name. 5. The script trusts the file contents and sends `SIGKILL` to the supplied PID. 6. The unrelated process terminates immediately. A similar failure can occur without an active attacker when a stale PID file remains and the operating system later assigns that PID to another process. ### Impact Assessment The script generally cannot terminate processes that the invoking ...[truncated 335 chars]
Remediation
View remediation

T09 Β· Insecure Skill Coding Practices

Warning
Location
SKILL.md:93
Finding

Documented Startup Procedure Forcibly Terminates Unrelated Port Owners

Content
View full analysis
/dev/null # 2. Start server cd ~/.openclaw/workspace/apps/$APP python3 -m http.server $PORT > /dev/null 2>&1 & ``` The troubleshooting instructions repeat the same behavior: ```text **Server port already in use?** - Kill existing: `lsof -ti:[PORT] | xargs kill -9` ``` ### Technical Analysis The Skill explicitly instructs the agent to terminate every process associated with the selected port. It does not determine whether the process belongs to Canvas OS, whether it was launched by this Skill, or whether it is safe to terminate. The use of `kill -9` sends an unconditional `SIGKILL`, bypassing normal application shutdown and cleanup procedures. The unquoted, unvalidated port value is an additional shell-hardening weakness. This violates least privilege because opening a Canvas application should not require terminating arbitrary unrelated services. ### Attack Path 1. A legitimate application or service is already listening on a selected Canvas port. 2. The user or agent follows the documented application-opening sequence. 3. `lsof` returns the PID or PIDs of all processes using that port. 4. `xargs kill -9` forcibly terminates those processes without verifying ownership by Canvas OS. 5. The Canvas HTTP server takes over the port after the unrelated service is stopped. An attacker who can influence the selected port could intentionally target a known user-owned service. ### Impact Assessment The command is constrained by the permissions of the user running it, but it can terminate any signalable process using the selected port. Potential consequences include denial of service, aborted transactions, lost unsaved data, interrupted development environments, and corruption ...[truncated 39 chars]
Remediation
View remediation

T09 Β· Insecure Skill Coding Practices

Warning
Location
templates/dashboard/index.html:97
Finding

Unescaped Agent-Supplied Fields Permit DOM Injection in Canvas Templates

Content
View full analysis
{ document.getElementById('list-' + n + '-title').textContent = title; document.getElementById('list-' + n).innerHTML = items.map(i => '
  • ' + i.text + '' + (i.badge || '') + '
  • ' ).join(''); }, ``` Tracker template: ```javascript function render() { const container = document.getElementById('items'); container.innerHTML = items.map((item, i) => `
    ${item.done ? '' : ''}
    ${item.title}
    ${item.meta || ''}
    ${item.streak ? 'πŸ”₯ ' + item.streak : ''}
    `).join(''); updateTimestamp(); } ``` The tracker can send messages to the agent through: ```javascript function sendToJarvis(msg) { window.location.href = 'openclaw://agent?message=' + encodeURIComponent(msg); } ``` ### Technical Analysis Both templates insert data received through their public JavaScript APIs into `innerHTML` without HTML escaping or sanitization. Affected dashboard properties include `i.text`, `i.status`, and `i.badge`. A malicious `status` value can also escape the intended class attribute. Affected tracker properties include `item.title`, `item.meta`, and `item.streak`. Although script elements inserted through `innerHTML` are not consistently executed, active markup such as image or SVG event handlers can execute when parsed or triggered. Injected markup can also alter the interface, create deceptive control ...[truncated 1642 chars]
    Remediation
    View remediation

    T09 Β· Insecure Skill Coding Practices

    Warning
    Location
    canvas-inject.py:21
    Finding

    Incomplete Template-Literal Escaping Allows JavaScript Expression Injection

    Content
    View full analysis
    Remediation
    View remediation
    Vulnerability Patterns
    • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
    • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
    • 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
    Findings (10)

    Vague Triggers

    Medium
    Category
    Not specified by scanner
    Confidence
    95% confidence
    Finding

    The README advertises very broad natural-language trigger phrases such as 'Show my stats on canvas' and 'Build me a habit tracker,' which can overlap with ordinary user conversation. In an agent skill ecosystem, ambiguous triggers can cause the skill to activate unexpectedly, increasing the chance that it serves local content, opens a localhost app, or initiates bidirectional app-agent behavior without clear user intent.

    Content

    No source excerpt is available for this finding.

    Missing User Warnings

    Medium
    Category
    Not specified by scanner
    Confidence
    95% confidence
    Finding

    The README describes a design that serves local app files over localhost, injects data via JavaScript eval, and allows apps to send commands back through deep links, but it does not warn users about the security implications. This combination materially raises risk because eval enables code execution in the UI context and two-way command channels can turn a rendered app into an agent-control surface, especially if app content or injected data is not strictly trusted and validated.

    Content

    No source excerpt is available for this finding.

    Undeclared Tool Scope

    Medium
    Category
    MCP Least Privilege
    Confidence
    93% confidence
    Finding

    The skill documents and relies on shell execution (python3 -m http.server, lsof, kill, curl) but does not declare any explicit tool scope such as permissions or allowed-tools. That mismatch weakens least-privilege controls and can cause an agent runtime to grant broader shell access than users expect, which is especially risky because the skill also includes process management and code/JS injection workflows.

    Content

    No source excerpt is available for this finding.

    Missing User Warnings

    Medium
    Category
    Not specified by scanner
    Confidence
    95% confidence
    Finding

    The documented command kills whatever process is listening on the selected port using kill -9 with no validation, attribution check, or user confirmation. In practice this can terminate unrelated local services or other users' development processes, causing data loss or denial of service, and the risk is heightened here because the skill encourages repeated port-based app launching as a normal workflow.

    Content

    No source excerpt is available for this finding.

    Description-Behavior Mismatch

    Medium
    Category
    Not specified by scanner
    Confidence
    95% confidence
    Finding

    This helper intentionally bypasses platform restrictions by opening about:blank and then injecting arbitrary HTML into the target node via a JavaScript eval step that calls document.write(). If html_content is influenced by untrusted input, this becomes arbitrary script/DOM injection in the Canvas context, and even when used as designed it normalizes a restriction-bypass pattern that undermines the platform’s trust and isolation model.

    Content

    No source excerpt is available for this finding.

    Intent-Code Divergence

    Medium
    Category
    Not specified by scanner
    Confidence
    92% confidence
    Finding

    The module is presented as a harmless HTML helper/workaround, but its actual behavior is remote code execution in the browser-like Canvas environment because it emits an eval action containing JavaScript. That mismatch increases operator risk: users may pass arbitrary content believing it is simple markup transport, when in reality the mechanism executes active content and bypasses normal safety expectations.

    Content

    No source excerpt is available for this finding.

    Missing User Warnings

    Medium
    Category
    Not specified by scanner
    Confidence
    95% confidence
    Finding

    The script unconditionally force-kills any process listening on the user-selected port via kill -9, without validating ownership, confirming with the user, or restricting the target to a process previously started by this tool. A user can supply an arbitrary port and cause denial of service against unrelated local services, and SIGKILL prevents graceful shutdown or cleanup.

    Content

    No source excerpt is available for this finding.

    Missing User Warnings

    Medium
    Category
    Not specified by scanner
    Confidence
    96% confidence
    Finding

    User interactions transmit tracker content to an external agent URI via the custom openclaw://agent scheme with no consent prompt, disclosure, or data-minimization. Because item titles may contain sensitive habit or health-related information, toggling or adding items can leak user data to another component automatically, creating a privacy and unintended-action risk in this app-platform context.

    Content

    No source excerpt is available for this finding.

    Missing User Warnings

    Low
    Category
    Not specified by scanner
    Confidence
    86% confidence
    Finding

    The script writes a predictable PID file into /tmp based on unsanitized app name input. Because /tmp is world-writable, this can enable symlink or clobbering issues and may overwrite unintended files if the script runs with elevated privileges; it also exposes process metadata to other local users.

    Content

    No source excerpt is available for this finding.

    Natural-Language Policy Violations

    Low
    Category
    Not specified by scanner
    Confidence
    82% confidence
    Finding

    The document declares lang="en", which hard-codes English as the page language. Under the stated policy, forcing a specific language without user opt-in or clear justification is a natural-language policy concern.

    Content

    No source excerpt is available for this finding.

    Static analysis

    No suspicious patterns detected.