Back to skill

Security audit

Clawctl

Security checks for vulnerabilities and agentic risk

Overview

This coordination skill is coherent overall, but its dashboard exposes a persistent token-bearing web UI on all network interfaces with task mutation authority, so it needs Review before installation.

Review this skill before installing if you intend to use the dashboard. Prefer running it only on localhost, avoid sharing token URLs, rotate or delete ~/.openclaw/.clawctl-token if exposed, and treat the local SQLite database as containing task, message, and activity history for all participating agents.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
dashboard/server.py:151
Finding
Network-Exposed Dashboard Uses a Bearer Token in Cleartext URLs<![CDATA[ ## Vulnerability Details **File Location**: `dashboard/server.py:151-162`; `dashboard/index.html:808-833`; `dashboard/index.html:1232` **Vulnerability Type**: Bearer-token exposure and excessive network exposure **Risk Level**: High ### Vulnerable Code ```python def main(): import argparse parser = argparse.ArgumentParser() parser.add_argument("--port", type=int, default=3737) args = parser.parse_args() print(f"\n{'=' * 50}") print(f" clawctl dashboard") print(f"{'=' * 50}") print(f"\n Local URL:") print(f" http://localhost:{args.port}/?token={TOKEN}") print(f"\n For Tailscale/LAN access, use your IP:") print(f" http://<your-ip>:{args.port}/?token={TOKEN}") print(f"\n Token: {TOKEN}") print(f"\n{'=' * 50}\n") app.run(host="0.0.0.0", port=args.port, threaded=True) ``` ```javascript const state = { tasks: [], agents: [], selectedTask: null, token: new URLSearchParams(location.search).get('token'), sseRetries: 0, eventSource: null, effectsOn: localStorage.getItem('cc_effects') === 'true', matrixRaf: null, sheetFocusTrap: null, previousFocus: null, }; const api = { async get(endpoint) { const res = await fetch(`${endpoint}?token=${state.token}`); if (res.status === 401) throw new Error('Unauthorized'); if (!res.ok) throw new Error(`API error: ${res.status}`); return res.json(); }, async post(endpoint, body = {}) { const res = await fetch(`${endpoint}?token=${state.token}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); if (res.status === 401) throw new Error('Unauthorized'); if (!res.ok) throw new Error(`API error: ${res.status}`); return res.json(); } }; ``` ```javascript const es = new EventSource(`/api/heartbeat?token=${state.token}`); ``` ### Technical Analysis The Flask server listens on `0.0.0.0`, making the dashboard reachable through every availab ...[truncated 2398 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Bind to `127.0.0.1` by default: ```python app.run(host="127.0.0.1", port=args.port, threaded=True) ``` 2. Require an explicit, clearly documented option such as `--listen-address` or `--allow-remote` before exposing the service on LAN interfaces. 3. Require HTTPS for non-loopback access, either directly or through a correctly configured authenticated reverse proxy. 4. Remove credentials from query strings. For ordinary API requests, use an `Authorization: Bearer ...` header. 5. For browser access and SSE, prefer a secure server-established session cookie with `Secure`, `HttpOnly`, and an appropriate `SameSite` policy. 6. Remove the token from the browser URL after establishing a session, using `history.replaceState`. 7. Do not print the raw persistent token or a token-bearing URL unless explicitly requested by the user. 8. Separate read-only monitoring privileges from task-mutation privileges. Mutation endpoints should require stronger authorization and should enforce task ownership rather than unconditionally using `force=True`. 9. Rotate existing tokens after deploying the corrected authentication mechanism. 10. Add `Cache-Control: no-store` and a restrictive `Referrer-Policy` as defense-in-depth controls. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
dashboard/server.py:20
Finding
Dashboard Token File Is Created Without Explicit Owner-Only Permissions<![CDATA[ ## Vulnerability Details **File Location**: `dashboard/server.py:20-27` **Vulnerability Type**: Insecure secret-file permissions **Risk Level**: Medium ### Vulnerable Code ```python def load_or_create_token(): TOKEN_PATH.parent.mkdir(parents=True, exist_ok=True) if TOKEN_PATH.exists(): token = TOKEN_PATH.read_text().strip() if token: return token token = secrets.token_urlsafe(16) TOKEN_PATH.write_text(token) return token ``` ### Technical Analysis The persistent dashboard bearer token is written with `Path.write_text()` without explicitly setting owner-only permissions. Its resulting permissions depend on the process umask and existing filesystem state. On a system with a permissive umask or an existing token file with overly broad permissions, other local users may be able to read the credential. The parent directory is likewise created without an explicit `0700` mode. The function also does not validate that the token path is a regular, owner-controlled file rather than a symbolic link. Because this token authenticates both dashboard read operations and task mutations, it should be treated as a sensitive credential. ### Attack Path 1. The dashboard is launched and creates `~/.openclaw/.clawctl-token`. 2. The host has a permissive umask, insecure pre-existing file permissions, or an insufficiently protected `~/.openclaw` directory. 3. Another local user reads the token file. 4. The attacker connects to the dashboard, especially if it is listening on `0.0.0.0`. 5. The attacker uses the recovered token to retrieve fleet data or invoke task cancellation and completion endpoints. ### Impact Assessment A local user able to read the token obtains the same application-level authority as the legitimate dashboard user. This includes visibility into tasks, task descriptions, messages, agents, and the ability to cancel or forcibly complete tasks. The finding does not by itself elevate the attacker to ope ...[truncated 127 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Ensure the secrets directory is owner-only: ```python TOKEN_PATH.parent.mkdir(parents=True, exist_ok=True, mode=0o700) os.chmod(TOKEN_PATH.parent, 0o700) ``` 2. Create the token file atomically with mode `0600`, for example using `os.open` with `O_CREAT | O_EXCL | O_WRONLY` and mode `0o600`. 3. Apply `chmod(0o600)` to an existing token file after verifying that it is owned by the current user. 4. Use `lstat()` and reject symbolic links or non-regular files before reading or replacing the token. 5. Write a temporary owner-only file and atomically rename it to prevent partial writes and race conditions. 6. Reject empty, malformed, or unexpectedly permissive token files and generate a replacement safely. 7. Provide a supported token-rotation command and rotate any token that may have been created under insecure permissions. ]]>

T08 · Insecure Dependencies

Warning
Location
dashboard/index.html:9
Finding
Unpinned Third-Party CDN Script Executes in the Authenticated Dashboard<![CDATA[ ## Vulnerability Details **File Location**: `dashboard/index.html:9` **Vulnerability Type**: Unpinned remote client-side dependency **Risk Level**: Medium ### Vulnerable Code ```html <script src="https://cdn.tailwindcss.com"></script> ``` ### Technical Analysis The dashboard executes JavaScript directly from a third-party CDN at runtime. The URL does not identify a fixed reviewed version and does not use Subresource Integrity. Remote JavaScript executes with the same origin privileges as the dashboard's own code. It can inspect `location.search`, where the dashboard stores its persistent bearer token, read API responses and locally cached board data, invoke authenticated endpoints, and transmit captured information to an external server. No restrictive Content Security Policy is shown that would meaningfully constrain the remote script. Therefore, a CDN compromise, upstream account compromise, malicious update, or unexpected behavioral change would affect every dashboard load without requiring a new release of this project. ### Attack Path 1. An attacker compromises the CDN distribution path, the upstream asset, or an account controlling the served script. 2. A dashboard user loads `index.html`. 3. The browser downloads and executes the altered script from `cdn.tailwindcss.com`. 4. The script reads the bearer token from `location.search` and accesses dashboard data in the page or through authenticated API calls. 5. The script transmits the token or fleet data to attacker-controlled infrastructure. 6. The attacker reuses the token to access the dashboard and invoke its mutation endpoints. ### Impact Assessment Successful supply-chain exploitation can disclose the dashboard bearer token and all data available to the authenticated page. It can also perform dashboard actions in the user's browser or enable subsequent unauthorized API access. The immediate privilege is control within the dashboard origin rather than native operating-system execut ...[truncated 126 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the runtime Tailwind CDN script from the authenticated dashboard. 2. Generate the required CSS during the build or release process and package the reviewed output locally with the dashboard. 3. Pin all build-time dependencies to reviewed versions and use lock files with integrity hashes. 4. If a remote resource is unavoidable, pin an immutable version and use Subresource Integrity with an appropriate `crossorigin` attribute. 5. Add a restrictive Content Security Policy that defaults to self-hosted resources, for example: ```text default-src 'self'; script-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; ``` 6. Move inline JavaScript into a packaged local file so that unsafe inline-script CSP exceptions are unnecessary. 7. Continue removing the bearer token from `location.search`, since any script executing in the page can read it. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
Findings (26)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
CLAW_DB=/tmp/test.db .venv/bin/clawctl add "Test task" -p 1
CLAW_DB=/tmp/test.db CLAW_AGENT=agent1 .venv/bin/clawctl claim 1
CLAW_DB=/tmp/test.db .venv/bin/clawctl board
rm -f /tmp/test.db
```

Always use `CLAW_DB=/tmp/test.db` to avoid touching the real database at `~/.openclaw/clawctl.db`.
Confidence
85% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Session Persistence

Medium
Category
Rogue Agent
Content
- **`clawctl/db.py`** — All SQL lives here. Every query uses `?` parameterized placeholders. Mutating functions return `(ok: bool, payload)` tuples. The `get_db()` context manager handles commit/rollback/close. This module is imported by both the CLI and the Flask server.
- **`clawctl/cli.py`** — Click commands. Each subcommand is a thin wrapper that calls `db.*` functions and formats output. The `print_columnar()` helper handles aligned table output with Unicode-aware width calculation.
- **`clawctl/schema.sql`** — Loaded by `db.init_db()` via `Path(__file__).parent`. Uses `CREATE TABLE IF NOT EXISTS` so re-running init is safe.
- **`dashboard/server.py`** — Flask app that imports `clawctl.db`. Read-only except for claim/complete endpoints. Persistent auth token saved to `~/.openclaw/.clawctl-token`. Not part of the installable package.
- **`dashboard/index.html`** — Single-file vanilla JS web UI. Tailwind via CDN. SSE for live updates.
Confidence
72% confidence
Finding
The documentation states that a persistent auth token is saved to `~/.openclaw/.clawctl-token`, which introduces session persistence on disk. If that token is stored without strict file permissions, rotation, expiration, or secure storage guarantees, another local process or user on the same system could reuse it to access or act through the dashboard session.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The documentation first advertises 'Complete and delete actions from the UI' and then states 'The dashboard is read-mostly' and that 'The CLI is the primary interface; the dashboard is for monitoring.' Those statements actively pull in different directions because the dashboard is documented as performing task mutations, not just monitoring.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill invokes a local CLI, relies on environment variables, and implicitly requires filesystem and shell access, but it does not declare any tool scope or permission boundaries. In an agent environment, this can cause the skill to run with broader capabilities than operators expect, increasing the chance of unintended command execution or access to local files and environment data.

Session Persistence

Medium
Category
Rogue Agent
Content
# Setup

```bash
clawctl init                        # create the database
export CLAW_AGENT=your-name         # set identity (falls back to $USER with warning)
export CLAW_DB=~/.openclaw/clawctl.db  # optional, this is the default
```
Confidence
80% confidence
Finding
The skill instructs the agent to create and use a persistent local database and to store identity information in environment variables across sessions. Persistent state can retain task history, messages, and agent identifiers that may be sensitive, and if the database path or file permissions are not constrained, other local users or processes could read or tamper with that state.

Session Persistence

Medium
Category
Rogue Agent
Content
@click.option("--for", "assignee", default="", help="Assign to agent")
@click.option("--parent", type=int, default=None, help="Parent task ID")
def add(subject, desc, priority, assignee, parent):
    """Create a task"""
    with db.get_db() as conn:
        ok, task_id = db.add_task(
            conn, subject, desc, priority, assignee, db.AGENT, parent
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.

Context-Inappropriate Capability

Medium
Confidence
85% confidence
Finding
The stop path trusts a PID loaded from a writable pid file and calls os.kill(pid, SIGTERM) without verifying that the PID belongs to the dashboard process. If an attacker or another local user/process can alter that pid file, the CLI can be tricked into terminating an unintended local process under the current user's privileges.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
stdout_target = subprocess.DEVNULL
        stderr_target = subprocess.DEVNULL

    proc = subprocess.Popen(
        [sys.executable, "-m", "dashboard", "--port", str(port)],
        stdout=stdout_target,
        stderr=stderr_target,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The command prints a fully authenticated localhost URL containing the dashboard token directly to stdout. Tokens echoed to terminals can be captured by shell history tools, terminal logging, scrollback sharing, screenshots, or wrapper systems, which can expose dashboard access to unintended parties.

Context-Inappropriate Capability

Medium
Confidence
98% confidence
Finding
The bulk mark-as-read path updates messages by arbitrary ID only and does not constrain the update to messages addressed to the requesting agent. Any caller able to invoke this function can mark other agents' messages as read, undermining message integrity and hiding unread alerts or coordination items.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The dashboard exposes a destructive delete action that executes immediately when clicked, with no confirmation dialog, undo flow, or other user-facing safeguard. In a coordination console for agent fleets, accidental taps/clicks on mobile or during rapid task triage can cancel or remove active work, causing operational disruption and possible loss of task state.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The application claims token auth, but the root page is explicitly exempted from authentication while the main URL printed to users includes the token in the query string. This can mislead operators into believing the whole dashboard is protected, and exposing the entry page unauthenticated can aid discovery, leak client-side logic, and encourage unsafe token handling via URLs that may be logged, cached, or shared.

Tainted flow: 'token' from pathlib.Path.read_text (line 23, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Content
if token:
            return token
    token = secrets.token_urlsafe(16)
    TOKEN_PATH.write_text(token)
    return token
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.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The /api/task/<int:task_id>/delete endpoint performs a destructive operation by canceling/deleting a task, but there is no confirmation prompt, user-facing warning, or explanatory comment/docstring indicating that this action is irreversible or safety-relevant. In a code file, destructive operations should have some visible disclosure unless the warning is provided elsewhere in markdown documentation.

Missing User Warnings

Low
Confidence
95% confidence
Finding
The markdown includes `rm -f /tmp/test.db`, which is a destructive operation that deletes a file. Although it is part of test cleanup, the document does not explicitly warn the user that this command removes the temporary database file.

Missing User Warnings

Low
Confidence
90% confidence
Finding
The README advertises "Complete and delete actions from the UI" but does not warn users that the dashboard can perform destructive task deletions. For markdown files, actions that can affect user data or system integrity should be accompanied by a clear warning or disclosure.

Missing User Warnings

Low
Confidence
73% confidence
Finding
When verbose logging is enabled, dashboard stdout and stderr are appended to a persistent log file without warning about potentially sensitive contents. If the dashboard emits tokens, message bodies, task metadata, or other internal state, those secrets may remain on disk longer than intended and be readable by other local principals depending on file permissions.

Context-Inappropriate Capability

Low
Confidence
69% confidence
Finding
The skill description emphasizes coordination, messaging, activity feed, and dashboard functions, but this file implements direct subprocess launching with `subprocess.Popen`. Although related to the dashboard feature, process spawning is a privileged host capability that is stronger than ordinary coordination/data operations and is not otherwise constrained here.

Context-Inappropriate Capability

Low
Confidence
83% confidence
Finding
The manifest describes a coordination layer for fleet tasks, messaging, activity feed, and dashboard features. Reading process environment variables for identity and storage location is an extra capability that is not evident from that purpose alone, especially deriving agent identity from USER/CLAW_AGENT rather than from explicit function inputs.

Missing User Warnings

Low
Confidence
74% confidence
Finding
The module reads CLAW_DB, CLAW_AGENT, and USER from the environment to determine database location and agent identity. Accessing environment-derived identity information can affect attribution and privacy, but the file does not disclose this behavior beyond raw assignment statements.

Missing User Warnings

Low
Confidence
82% confidence
Finding
The init_db function creates a directory, opens a SQLite database, and executes a schema script, which are file-system write operations. Although the module docstring describes return conventions, there is no user-facing warning, print/log message, or comment near this operation disclosing that local files and directories will be created or modified.

Missing User Warnings

Low
Confidence
76% confidence
Finding
Functions such as add_task store task subject and description, and other functions later persist notes, messages, and activity metadata to the local database. These are file-write operations affecting user data, but this file provides no comments, docstrings, or user-visible logging around that persistence behavior.

Unverifiable Dependency: setuptools has 10 known advisory(ies) (CVE-2013-1633 (Setuptools vulnerable to Man-in-the-middle attacks); CVE-2025-47273 (setuptools has a path traversal vulnerability in PackageIndex.download that lead); CVE-2024-6345 (setuptools vulnerable to Command Injection via package URL) +7 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Unverifiable Dependency: click has 1 known advisory(ies) (CVE-2026-7246 (Pallets Click, versions 8.3.2 and below, contain a command injection vulnerabili)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
94% confidence
Finding
The unpinned flask dependency is more concerning in context because this skill includes a dashboard package and describes messaging/activity-feed coordination, implying network-facing or web-exposed functionality. If dependency resolution selects a Flask version affected by one of its known advisories, the application could inherit denial-of-service, session/key-handling, or other web security weaknesses.

Unverifiable Dependency: flask has 10 known advisory(ies) (CVE-2025-47278 (Flask uses fallback key instead of current signing key); CVE-2018-1000656 (Flask is vulnerable to Denial of Service via incorrect encoding of JSON data); CVE-2019-1010083 (Pallets Project Flask is vulnerable to Denial of Service via Unexpected memory u) +7 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
94% confidence
Finding
The unpinned flask dependency is more concerning in context because this skill includes a dashboard package and describes messaging/activity-feed coordination, implying network-facing or web-exposed functionality. If dependency resolution selects a Flask version affected by one of its known advisories, the application could inherit denial-of-service, session/key-handling, or other web security weaknesses.

Static analysis

No suspicious patterns detected.