Back to skill

Security audit

Canvas Os 1.0.1

Security checks for vulnerabilities and agentic risk

Overview

This Canvas app skill is mostly purpose-aligned, but it should be reviewed because its scripts can expose local folders, kill unrelated local processes, and inject JavaScript into Canvas without strong scoping.

Install only if you are comfortable with a skill that starts local web servers, injects JavaScript into Canvas, and lets Canvas apps send messages back to the agent. Use it only with trusted app content and trusted data, avoid arbitrary app names or ports, and review the scripts before allowing process cleanup or serving local directories.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (6)

T09 · Insecure Skill Coding Practices

Error
Location
canvas-inject.py:18
Finding

JavaScript Injection Through Incomplete Template-Literal Escaping

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
templates/dashboard/index.html:98
Finding

DOM-Based Cross-Site Scripting in Dashboard List Rendering

Content
View full analysis
{ document.getElementById('list-' + n + '-title').textContent = title; document.getElementById('list-' + n).innerHTML = items.map(i => '
  • ' + i.text + '' + (i.badge || '') + '
  • ' ).join(''); }, ``` ### Technical Analysis The `setList()` API concatenates `i.text`, `i.status`, and `i.badge` directly into an HTML string and assigns it to `innerHTML`. No output encoding, sanitization, or allowlist validation is performed. An attacker-controlled value can break out of the intended text or attribute context and introduce arbitrary HTML. Event-handler attributes, active elements, or other browser-supported execution mechanisms can consequently execute in the Canvas page. Using `textContent` for the list title does not protect the independently unsafe item fields. ### Attack Path 1. An attacker controls or influences data that the agent uses to populate a dashboard list. 2. A list property contains crafted HTML, such as an element with an event handler. 3. The agent calls `dashboard.setList()` with the malicious item. 4. `setList()` concatenates the value into a markup string. 5. Assignment to `innerHTML` causes the browser to parse the attacker-controlled markup. 6. The injected browser behavior executes when its activation condition is met. ### Impact Assessment The attacker can execute JavaScript in the dashboard's Canvas origin and manipulate the complete dashboard DOM. This can enable interface spoofing, alteration or theft of page-visible data, deceptive interaction prompts, and attempts to invoke the `openclaw://agent` deep-link handler. Host-level privileges depend on the Canvas sandbox and the behavior ...[truncated 29 chars]
    Remediation
    View remediation
    { const titleNode = document.getElementById('list-' + n + '-title'); const listNode = document.getElementById('list-' + n); titleNode.textContent = String(title); listNode.replaceChildren(); const allowedStatuses = new Set(['done', 'wip', 'todo']); for (const item of items) { const li = document.createElement('li'); const text = document.createElement('span'); const badge = document.createElement('span'); text.textContent = String(item.text ?? ''); badge.textContent = String(item.badge ?? ''); const status = allowedStatuses.has(item.status) ? item.status : ''; badge.classList.add('badge'); if (status) badge.classList.add(status); li.append(text, badge); listNode.appendChild(li); } } ``` Also validate the shape and maximum size of injected data. If HTML formatting is genuinely required, use a maintained allowlist-based sanitizer and disallow scripts, event attributes, dangerous URLs, and active embedded content. ]]>

    T09 · Insecure Skill Coding Practices

    Error
    Location
    templates/tracker/index.html:126
    Finding

    DOM-Based Cross-Site Scripting in Tracker Item Rendering

    Content
    View full analysis
    `
    ${item.done ? '' : ''}
    ${item.title}
    ${item.meta || ''}
    ${item.streak ? '🔥 ' + item.streak : ''}
    `).join(''); updateTimestamp(); } ``` ### Technical Analysis The tracker interpolates `item.title`, `item.meta`, and `item.streak` into an HTML template and assigns the result to `innerHTML`. These properties are accepted through the public `tracker.setItems()` and `tracker.addItem()` APIs without validation or encoding. A malicious item can inject elements and event-handler attributes into the generated document. The use of an inline `onclick` handler further mixes executable code and markup, making a strict Content Security Policy more difficult to deploy. ### Attack Path 1. An attacker causes malicious tracker data to be passed to `tracker.setItems()` or `tracker.addItem()`. 2. The payload is stored in the global `items` array. 3. The API invokes `render()`. 4. `render()` interpolates attacker-controlled properties into an HTML string. 5. The browser parses the string through `container.innerHTML`. 6. Injected active content executes immediately or when the user interacts with it. 7. The payload can manipulate the tracker or attempt to send deceptive commands through the agent deep-link mechanism. ### Impact Assessment Successful exploitation permits arbitrary JavaScript execution within the tracker page. An attacker can m ...[truncated 248 chars]
    Remediation
    View remediation
    { const row = document.createElement('div'); row.classList.add('item'); if (Boolean(item.done)) row.classList.add('done'); row.addEventListener('click', () => toggle(index)); const checkbox = document.createElement('div'); checkbox.className = 'checkbox'; const content = document.createElement('div'); content.className = 'item-content'; const title = document.createElement('div'); title.className = 'item-title'; title.textContent = String(item.title ?? ''); const meta = document.createElement('div'); meta.className = 'item-meta'; meta.textContent = String(item.meta ?? ''); const streak = document.createElement('div'); streak.className = 'item-streak'; streak.textContent = item.streak ? `🔥 ${String(item.streak)}` : ''; content.append(title, meta); row.append(checkbox, content, streak); container.appendChild(row); }); updateTimestamp(); } ``` Validate each item against a strict schema, constrain string lengths, and use `addEventListener()` instead of inline event handlers. Consider a restrictive Content Security Policy that disallows inline scripts. ]]>

    T05 · Unauthorized Access and Privilege Escalation

    Error
    Location
    open-app.sh:23
    Finding

    Directory Traversal Can Expose Arbitrary Directories Over the Network

    Content
    View full analysis
    /dev/null 2>&1 & ``` ### Technical Analysis `APP_NAME` is incorporated into a filesystem path without rejecting separators, `..` components, absolute paths, or symbolic-link escapes. The directory check only confirms that the resulting path exists; it does not verify that the canonical target remains inside `APPS_DIR`. In addition, `python3 -m http.server` binds to all available network interfaces by default unless `--bind` is specified. Although the application is described as a localhost service, the actual command can expose the selected directory to other hosts that can reach the machine. ### Attack Path 1. An attacker or untrusted caller supplies an app name containing traversal components, such as a sequence resolving outside the configured application root. 2. The concatenated path resolves to an existing directory outside `APPS_DIR`. 3. The directory check succeeds because it checks existence rather than containment. 4. The script changes into the unintended directory. 5. Python's HTTP server starts and publishes that directory on the selected port. 6. A network-adjacent host connects to the machine and retrieves files accessible to the server process. Symbolic links inside the application directory can provide an equivalent escape path if canonical containment is not enforced. ### Impact Assessment Files readable by the invoking user within the selected directory may become remotely accessible. Depending on the chosen path, exposed content could include workspace data, source co ...[truncated 253 chars]
    Remediation
    View remediation
    &2 exit 1 fi ``` 2. Resolve both paths canonically and enforce containment: ```bash BASE_DIR="$(cd "$APPS_DIR" && pwd -P)" APP_DIR="$(cd "$APPS_DIR/$APP_NAME" 2>/dev/null && pwd -P)" || exit 1 case "$APP_DIR/" in "$BASE_DIR"/*) ;; *) echo "App path escapes application directory" >&2; exit 1 ;; esac ``` 3. Bind the server exclusively to loopback: ```bash python3 -m http.server "$PORT" --bind 127.0.0.1 ``` 4. Validate that `PORT` is numeric and within the range `1-65535`. 5. Avoid following application-directory symbolic links unless explicitly intended. 6. Apply host firewall rules as defense in depth. ]]>

    T09 · Insecure Skill Coding Practices

    Warning
    Location
    close-app.sh:10
    Finding

    Predictable and Untrusted PID Files Permit Process Termination and File Clobbering

    Content
    View full analysis
    "/tmp/canvas-app-$APP_NAME.pid" ``` The cleanup script later trusts that file without validating its ownership, type, contents, or associated process: ```bash 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 PID file resides in the globally shared `/tmp` namespace and has a predictable name influenced by `APP_NAME`. The open script writes through the path without checking whether it is a symbolic link, allowing a local attacker to pre-create the path as a link to another user-writable target. The close script accepts the file's contents as a process identifier and invokes `kill -9` without checking that the value is a valid positive integer or that the process is the Python server originally created by the application. Because `APP_NAME` is also not constrained to a safe filename component, path separators can affect where the PID file is read, written, or removed. ### Attack Path **Arbitrary same-user process termination:** 1. A local attacker predicts the PID filename for an application. 2. The attacker creates or modifies that file and stores the PID of another process owned by the same user. 3. The user invokes `close-app.sh` for the selected application. 4. The script reads the attacker-controlled PID. 5. `kill -9` terminates the unrelated process. **File clobbering:** 1. A local attacker creates the predictable PID path as a symbolic link to a file writable by the victim. 2. The user invokes `open-app.sh`. 3. Shell redirecti ...[truncated 492 chars]
    Remediation
    View remediation

    T05 · Unauthorized Access and Privilege Escalation

    Warning
    Location
    open-app.sh:17
    Finding

    Unconditional Force-Termination of Unrelated Port Owners

    Content
    View full analysis
    /dev/null | xargs kill -9 2>/dev/null ``` The Skill instructions recommend the same behavior: ```bash # 1. Kill any existing server on the port lsof -ti:$PORT | xargs kill -9 2>/dev/null ``` ### Technical Analysis The script identifies every process associated with the selected port and immediately sends `SIGKILL`. It does not verify that the process belongs to Canvas OS, was launched by this project, or is safe to terminate. `SIGKILL` prevents applications from performing normal cleanup, flushing state, or saving data. A user-controlled `PORT` therefore becomes a mechanism for terminating unrelated processes owned by the invoking user. ### Attack Path 1. An unrelated application is listening on a known port. 2. An attacker or untrusted caller causes `open-app.sh` to be invoked with that port. 3. `lsof` returns the unrelated application's process identifier. 4. `xargs kill -9` immediately terminates the process. 5. The affected service becomes unavailable and may lose unsaved state. ### Impact Assessment Exploitation causes denial of service against processes that the invoking user is authorized to signal. It can terminate development servers, local databases, or other user applications and may cause data corruption due to abrupt shutdown. It does not bypass operating-system process ownership checks. ]]>
    Remediation
    View remediation
    /dev/null 2>&1; then echo "Port $PORT is already in use" >&2 exit 1 fi ``` If automatic cleanup is required: 1. Terminate only a PID previously recorded by Canvas OS. 2. Verify that the PID still belongs to the expected Python HTTP server and application directory. 3. Send `SIGTERM` first and wait for a bounded interval. 4. Use `SIGKILL` only if the identity remains verified and graceful shutdown fails. 5. Validate the port as an integer in the range `1-65535`. 6. Remove the unconditional `lsof | kill -9` recommendation from `SKILL.md`. ]]>
    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 (12)

    Tp4

    High
    Category
    MCP Tool Poisoning
    Confidence
    96% confidence
    Finding

    The skill is presented as a UI/app-platform helper, but it also instructs arbitrary JavaScript injection into a live Canvas target using eval/document.write and direct node manipulation. That mismatch is dangerous because it hides powerful code-execution behavior behind an innocuous description, making abuse or unsafe invocation more likely.

    Content

    No source excerpt is available for this finding.

    Missing User Warnings

    Medium
    Category
    Not specified by scanner
    Confidence
    95% confidence
    Finding

    The README explicitly promotes 'Live data injection via JavaScript eval' and 'Two-way communication' where apps send commands back to the agent, but it provides no warning about trust boundaries, input validation, or the system-impacting nature of those capabilities. In this skill's context, that omission is dangerous because combining eval-based injection with agent command callbacks can enable code execution, prompt/command injection, or unintended agent actions from app-controlled content.

    Content

    No source excerpt is available for this finding.

    Vague Triggers

    Medium
    Category
    Not specified by scanner
    Confidence
    96% confidence
    Finding

    The README advertises highly generic trigger phrases such as 'Open business dashboard' and 'Show my stats on canvas' that are likely to collide with ordinary user requests. In an agentic environment, broad activation phrases can cause the skill to run unintentionally, which is especially risky here because the skill can launch local content, inject JavaScript, and receive commands back from the app.

    Content

    No source excerpt is available for this finding.

    Undeclared Tool Scope

    Medium
    Category
    MCP Least Privilege
    Confidence
    88% confidence
    Finding

    The skill documents shell-capable operations such as starting HTTP servers, killing processes, and navigating/evaluating code on Canvas, but it declares no explicit tool scope or permissions boundary. This increases the risk of unintended command execution because users and the platform cannot clearly constrain what the skill is allowed to do.

    Content

    No source excerpt is available for this finding.

    Vague Triggers

    Medium
    Category
    Not specified by scanner
    Confidence
    94% confidence
    Finding

    Trigger phrases like 'Open [app]', 'Update [element]', and 'Show [data] on canvas' are broad and overlap with normal conversational requests, increasing the chance the skill activates when the user did not intend privileged Canvas or shell actions. Because this skill can start servers and inject code into a target UI, accidental activation carries meaningful security risk.

    Content

    No source excerpt is available for this finding.

    Missing User Warnings

    Medium
    Category
    Not specified by scanner
    Confidence
    93% confidence
    Finding

    The documented command lsof -ti:$PORT | xargs kill -9 forcibly terminates any process bound to the port without validating ownership or warning the user. This can disrupt unrelated local services and cause data loss or denial of service if used on a shared or important port.

    Content

    No source excerpt is available for this finding.

    Description-Behavior Mismatch

    Medium
    Category
    Not specified by scanner
    Confidence
    94% confidence
    Finding

    This helper is explicitly designed to bypass normal Canvas loading restrictions by opening about:blank and then using an eval action to execute JavaScript that calls document.write() with attacker-controlled HTML. If html_content is influenced by untrusted input, this becomes arbitrary script/HTML injection in the Canvas target, enabling XSS-like behavior, credential theft, UI spoofing, or execution of privileged in-canvas actions.

    Content

    No source excerpt is available for this finding.

    Intent-Code Divergence

    Medium
    Category
    Not specified by scanner
    Confidence
    96% confidence
    Finding

    The function is documented as HTML injection, but it actually returns an eval action that executes JavaScript in the target context. That mismatch is dangerous because downstream users may believe they are only rendering markup, while in reality they are enabling code execution with any scripts or event handlers embedded in the supplied HTML.

    Content

    No source excerpt is available for this finding.

    Missing User Warnings

    Medium
    Category
    Not specified by scanner
    Confidence
    93% confidence
    Finding

    The script reads a PID from a predictable file in /tmp and then unconditionally sends SIGKILL to that PID before deleting the file. If the PID file is stale, tampered with, or points to a reused PID, the script can terminate the wrong process, creating a local denial-of-service risk and making recovery/debugging harder because SIGKILL bypasses graceful shutdown.

    Content

    No source excerpt is available for this finding.

    Missing User Warnings

    Medium
    Category
    Not specified by scanner
    Confidence
    96% confidence
    Finding

    The script unconditionally force-kills every process bound to the requested port using lsof ... | xargs kill -9, with no validation, confirmation, or ownership check. Because the port is user-controlled, this can terminate unrelated services on the host, causing denial of service or data loss if a critical process is using that port.

    Content

    No source excerpt is available for this finding.

    Missing User Warnings

    Medium
    Category
    Not specified by scanner
    Confidence
    94% confidence
    Finding

    The page sends user interaction data to an external agent via the custom openclaw://agent URI whenever a tracker item is toggled, and the add button also triggers an agent message. This creates an undisclosed data flow from local UI actions to another component, potentially exposing habit names and completion status without clear consent or visibility to the user.

    Content

    No source excerpt is available for this finding.

    Natural-Language Policy Violations

    Low
    Category
    Not specified by scanner
    Confidence
    93% confidence
    Finding

    The document sets 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 locale policy violation.

    Content

    No source excerpt is available for this finding.

    Static analysis

    No suspicious patterns detected.