Back to skill

Security audit

ClawStatus

Security checks for vulnerabilities and agentic risk

Overview

ClawStatus is a legitimate-looking OpenClaw dashboard, but it exposes sensitive status data and administrative controls without authentication when run as documented.

Only install or run this in a tightly controlled local environment. Do not bind it to 0.0.0.0 or expose it to a network unless authentication is enforced, mutation endpoints are disabled or protected, and sensitive dashboard responses are reviewed/redacted. Treat it as an administrative control surface, not a read-only status page.

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

T05 · Unauthorized Access and Privilege Escalation

Error
Location
clawstatus.py:2742
Finding
Unauthenticated Administrative APIs Permit OpenClaw Configuration Changes and Cron Control<![CDATA[ ## Vulnerability Details **File Location**: `clawstatus.py:2742-2745`, with affected routes at `clawstatus.py:2814-2889` and privileged operations at `clawstatus.py:818-976` **Vulnerability Type**: Missing authentication and authorization on administrative endpoints **Risk Level**: High ### Vulnerable Code The application explicitly disables authentication: ```python def create_app() -> Flask: app = Flask(__name__) # Requirement: page works out of box, no token input needed required_token = None ``` The authorization helper permits every request when no token is configured: ```python def _is_authorized(required_token: Optional[str]) -> bool: if not required_token: return True got = _token_from_request() return bool(got and got == required_token) def _require_auth(required_token: Optional[str]): if _is_authorized(required_token): return None return jsonify({"error": "unauthorized", "valid": False}), 401 ``` State-changing routes rely on this ineffective check: ```python @app.post("/api/agents/<agent_id>/model") def api_agent_model_update(agent_id: str): auth_resp = _require_auth(required_token) if auth_resp is not None: return auth_resp payload = request.get_json(silent=True) or {} model_id = str(payload.get("model") or "").strip() if not model_id: return jsonify({"error": "missing model"}), 400 try: result = _update_agent_model(agent_id, model_id) except KeyError: return jsonify({"error": "agent not found", "agentId": agent_id}), 404 except ValueError: return jsonify({"error": "invalid model", "model": model_id}), 400 except PermissionError as e: return jsonify({"error": f"write failed: {e}"}), 500 except OSError as e: return jsonify({"error": f"write failed: {e}"}), 500 return jsonify(result) @app.post("/api/crons/<job_id>/model") def api_cron_model_update(job_id: str): auth_resp = _require_a ...[truncated 4863 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Load and enforce the configured authentication token: ```python def create_app() -> Flask: app = Flask(__name__) required_token = _load_auth_token() ``` 2. Fail closed when binding to any non-loopback address. Refuse startup on `0.0.0.0` or an external address unless strong authentication is configured. 3. Separate read-only monitoring permissions from administrative permissions. Administrative routes should require a distinct, higher-privilege credential. 4. Disable mutation endpoints by default. Require an explicit option such as `--enable-admin-api` before registering routes that edit models, execute jobs, delete jobs, or restart services. 5. Add CSRF protection to browser-accessible state-changing routes. Use unpredictable CSRF tokens and restrictive `SameSite` cookies if session-based authentication is introduced. 6. Do not accept authentication tokens through URL query parameters because URLs may be stored in browser history, proxy logs, and access logs. 7. Compare secret tokens using `hmac.compare_digest()` and enforce transport encryption through a secured reverse proxy when remote access is required. 8. Apply rate limits and record authenticated audit events for every configuration change, job execution, job deletion, and service restart. 9. Bind to `127.0.0.1` by default and update the documentation to recommend SSH tunneling or an authenticated TLS reverse proxy for remote access. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
clawstatus.py:2561
Finding
Unauthenticated APIs Expose Sessions, Cron Payloads, Task Instructions, and Local Operational Metadata<![CDATA[ ## Vulnerability Details **File Location**: `clawstatus.py:2561-2625`, with disclosure routes at `clawstatus.py:2785-3011` and cron payload construction at `clawstatus.py:2185-2272` **Vulnerability Type**: Sensitive information exposure caused by missing access control **Risk Level**: High ### Vulnerable Code The dashboard aggregates sensitive local information into one response: ```python def _build_dashboard_payload() -> Dict[str, Any]: status_data, status_err = _run_openclaw_status() status_ts_ms = _cache_ts_ms(_status_cache, _status_lock) subagents = _collect_subagent_runs() subagents_ts_ms = int(time.time() * 1000) agents = _collect_agents_data(status_data or {}, subagents) agents_ts_ms = int(time.time() * 1000) crons = _collect_cron_data() crons_ts_ms = int(time.time() * 1000) models = _collect_models_usage() models_ts_ms = _cache_ts_ms(_models_cache, _models_lock) or int(time.time() * 1000) memory_data = _collect_memory_data(status_data=status_data or {}, crons_data=crons) memory_ts_ms = _cache_ts_ms(_memory_cache, _memory_lock) or int(time.time() * 1000) openclaw_summary = _collect_openclaw_summary(status_data, status_err) openclaw_ts_ms = status_ts_ms or int(time.time() * 1000) sessions = (status_data or {}).get("sessions", {}) recent_sessions = sessions.get("recent", []) if isinstance(sessions, dict) else [] main_model = "unknown" main_tokens = 0 for rs in recent_sessions: if not isinstance(rs, dict): continue if rs.get("agentId") == "main": main_model = str(rs.get("model") or main_model) main_tokens = int(rs.get("totalTokens") or 0) break generated_at_ms = int(time.time() * 1000) source_timestamps = { "status": status_ts_ms, "openclaw": openclaw_ts_ms, "agents": agents_ts_ms, "subagents": subagents_ts_ms, "crons": crons_ts_ms, "models": ...[truncated 5861 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require authentication for every API endpoint, including monitoring, health, and legacy compatibility routes. 2. Do not return raw status or configuration objects. Construct explicit response schemas containing only fields required by the user interface. 3. Remove the full cron `payload` from `/api/crons`. Return only non-sensitive status fields such as job name, enabled state, next execution time, and last result. 4. Protect cron prompt and task content behind a separate privileged permission. Consider disabling `/api/cron-monitor/<job_id>` by default. 5. Redact or pseudonymize: - Session and agent identifiers. - Workspace and memory filenames. - Local filesystem paths. - Gateway URLs and service details. - Provider/account metadata. - Task prompts and operational notes. 6. Add role-based authorization so read-only users cannot access sensitive task content or administrative metadata. 7. Use `127.0.0.1` as the recommended deployment binding. For remote access, require TLS and authentication through a properly configured reverse proxy. 8. Add security-focused response tests that verify anonymous requests receive `401 Unauthorized` and that authorized responses do not contain raw secrets, credentials, prompts, or unnecessary filesystem details. 9. Review all fields returned by the external `openclaw status --json` command before forwarding them. Apply an allowlist rather than returning complete upstream structures. ]]>
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 Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (19)

Context-Inappropriate Capability

Critical
Confidence
100% confidence
Finding
The application explicitly sets `required_token = None`, disabling authentication even though token-loading support exists elsewhere in the code. Because the server exposes admin endpoints that modify config, restart services, trigger jobs, and delete jobs, any network-reachable user can perform those actions.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The code rewrites `openclaw.json` and then restarts OpenClaw, meaning a web request can persistently modify system configuration and apply it immediately. In context, this is highly dangerous because the app is a local admin dashboard with direct access to the user's OpenClaw home directory and service lifecycle.

Intent-Code Divergence

High
Confidence
99% confidence
Finding
The comment states the page should work without token input, normalizing unauthenticated access despite the presence of privileged mutation endpoints. That design choice makes the overall system more dangerous because it removes friction and encourages deployment of an admin interface with no access control.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
This dashboard is not purely observational; it exposes administrative mutation endpoints for model switching, cron execution, and cron deletion. A status dashboard that can change runtime state becomes a control surface, and in this file that control surface is available without auth, dramatically increasing the attack surface.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
stdin=subprocess.DEVNULL,
            start_new_session=True,
            cwd=os.getcwd(),
            env=os.environ.copy(),
        )
        _write_pid(proc.pid)
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def _run_status_cmd(cmd: List[str], timeout_sec: int) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
    try:
        proc = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# 1. Try systemctl user service
    for svc in ("openclaw.service", "openclaw"):
        try:
            proc = subprocess.run(
                ["systemctl", "--user", "restart", svc],
                capture_output=True, text=True, timeout=10,
            )
Confidence
95% confidence
Finding
This allows the application to restart a user-level systemd service from a web-exposed code path. The command itself is fixed, but in this application's context it is part of an unauthenticated administrative workflow, enabling remote service disruption or forced restarts.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
or str(HOME / ".npm-global" / "bin" / "openclaw")
    )
    try:
        proc = subprocess.run(
            [openclaw_bin, "restart"],
            capture_output=True, text=True, timeout=10,
        )
Confidence
98% confidence
Finding
This endpoint path can restart OpenClaw by executing a binary whose path may come from the OPENCLAW_BIN environment variable. In this file, admin APIs are exposed without authentication, so a remote caller can trigger privileged operational changes, and if the runtime environment is attacker-influenced, the invoked executable may be replaced with an arbitrary program.

Tainted flow: 'openclaw_bin' from os.environ.get (line 955, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
or str(HOME / ".npm-global" / "bin" / "openclaw")
    )
    try:
        proc = subprocess.run(
            [openclaw_bin, "restart"],
            capture_output=True, text=True, timeout=10,
        )
Confidence
96% confidence
Finding
The binary path used for restart can be sourced from an environment variable, creating a tainted path into code execution. In a privileged service context, if an attacker can influence environment configuration or service startup settings, they can redirect this call to an arbitrary executable and gain code execution under the dashboard's privileges.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
This path updates agent model configuration and triggers a restart without any backend-enforced warning, approval, or safety check. In an exposed dashboard, silent state changes can cause downtime, behavioral drift, or unexpected billing impacts with little traceability.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if not found:
        raise KeyError(job_id)
    try:
        subprocess.Popen(
            ["openclaw", "cron", "run", job_id, "--timeout", "30000"],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
Confidence
97% confidence
Finding
This launches `openclaw cron run` from an HTTP-triggerable endpoint, causing server-side state changes and task execution. Because the dashboard explicitly disables auth, any reachable user can trigger cron jobs, which may execute arbitrary downstream actions configured in OpenClaw.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The backend performs destructive cron deletion immediately and does not enforce any server-side confirmation, safety interlock, or soft-delete behavior. Client-side confirmation dialogs are insufficient because an attacker can call the API directly and bypass the UI entirely.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
or str(HOME / ".npm-global" / "bin" / "openclaw")
    )
    try:
        proc = subprocess.run(
            [openclaw_bin, "cron", "delete", job_id],
            capture_output=True, text=True, timeout=15,
        )
Confidence
98% confidence
Finding
This subprocess deletes cron jobs through an unauthenticated HTTP API. Even though shell injection is not present, the capability is destructive and allows remote tampering with automation state, potentially disabling monitoring, backups, or security-related jobs.

Tainted flow: 'openclaw_bin' from os.environ.get (line 955, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
or str(HOME / ".npm-global" / "bin" / "openclaw")
    )
    try:
        proc = subprocess.run(
            [openclaw_bin, "cron", "delete", job_id],
            capture_output=True, text=True, timeout=15,
        )
Confidence
95% confidence
Finding
The cron deletion command also trusts `OPENCLAW_BIN` from the environment, creating a path-to-execution issue for a privileged action. This is especially risky in service deployments where environment variables may be configurable through unit files, wrappers, or orchestration metadata.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
This endpoint edits cron model configuration immediately through the backend with no server-side warning or confirmation semantics. While not a memory-safety flaw, it is a risky design because direct API callers can induce costly or behavior-changing automation updates invisibly.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
or str(HOME / ".npm-global" / "bin" / "openclaw")
    )
    try:
        proc = subprocess.run(
            [openclaw_bin, "cron", "edit", job_id, "--model", model_id],
            capture_output=True, text=True, timeout=15,
        )
Confidence
98% confidence
Finding
This subprocess edits cron job model configuration via an HTTP endpoint, changing system behavior at runtime. In the context of this dashboard, the endpoint is reachable without auth, so unauthorized users can alter model selection and indirectly affect cost, output quality, or behavior of scheduled jobs.

Tainted flow: 'openclaw_bin' from os.environ.get (line 955, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
or str(HOME / ".npm-global" / "bin" / "openclaw")
    )
    try:
        proc = subprocess.run(
            [openclaw_bin, "cron", "edit", job_id, "--model", model_id],
            capture_output=True, text=True, timeout=15,
        )
Confidence
95% confidence
Finding
The cron edit command inherits the same tainted executable-path problem through `OPENCLAW_BIN`. An attacker who can control that environment value can turn a routine config change into arbitrary program execution by the service.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if not debug:
            args.append("--no-debug")

        proc = subprocess.Popen(
            args,
            stdout=logf,
            stderr=logf,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
cmd = [sys.executable, self_path, "--host", host, "--port", str(port), "--debug"]
        
        try:
            proc = subprocess.Popen(
                cmd,
                cwd=os.path.dirname(self_path),
                stdout=sys.stdout,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Static analysis

No suspicious patterns detected.