Back to skill

Security audit

Openclaw Skill

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real local sandboxed command runner, but its safety claims are overstated and its command-execution service is under-scoped.

Review before installing. This skill may be useful if you intentionally want a local Bubblewrap-backed command runner, but do not rely on its current claims of validation, confirmation, loop limiting, or strict read-only isolation. Run it only in a narrow project directory, avoid sensitive working directories, and prefer a release with pinned dependencies, workspace-bound writable mounts, authentication or strict socket permissions, and execution resource limits.

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

T05 · Unauthorized Access and Privilege Escalation

Error
Location
skillshieldd/src/sandbox.rs:52
Finding
Arbitrary Host Directory Is Mounted Read-Write Inside the Sandbox<![CDATA[ ## Vulnerability Details **File Location**: `skillshieldd/src/sandbox.rs:52-55`, with attacker-controlled input propagated through `skillshieldd/src/main.rs:103-131` and `skillshield-exec.sh:91-92` **Vulnerability Type**: Insufficient sandbox filesystem isolation **Risk Level**: High ### Vulnerable Code `skillshield-exec.sh:91-92`: ```bash REQUEST_JSON="$(python3 -c 'import json,os,sys; print(json.dumps({"command": sys.argv[1], "cwd": os.getcwd()}))' "$COMMAND")" RESPONSE_JSON="$(curl -fsS --unix-socket "$SOCKET_PATH" -H 'Content-Type: application/json' -X POST http://localhost/v1/execute -d "$REQUEST_JSON")" ``` `skillshieldd/src/main.rs:103-131`: ```rust async fn execute( State(state): State<AppState>, Json(payload): Json<ExecuteRequest>, ) -> Json<ExecuteResponse> { let request = ActionRequest { request_id: "exec".into(), session_id: "exec".into(), timestamp: timestamp_now(), actor: Actor { agent_name: "skillshield-wrapper".into(), tool_name: Some("skillshield-exec.sh".into()), run_id: None, }, action: Action::ShellExec(ShellExecAction { command: payload.command.clone(), args: vec![], env_diff: vec![], }), context: RequestContext { cwd: payload.cwd.clone(), workspace_root: payload.cwd.clone(), requires_approval: false, }, }; let decision = policy::evaluate(&request); let executor_name = state.executor.name().to_string(); match decision.execution_plan { models::ExecutionPlan::Execute | models::ExecutionPlan::Sandbox => { match state .executor .execute_shell(&payload.command, payload.cwd.as_deref()) .await ``` `skillshieldd/src/sandbox.rs:52-55`: ```rust if let Some(dir) = working_dir { cmd.arg("--bind").arg(dir).arg(dir); cmd.current_dir(dir); } ``` ### Technic ...[truncated 2237 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Configure a trusted workspace root independently of request data. 2. Canonicalize both the configured root and requested working directory before execution. 3. Reject any path that is not a strict descendant of the configured workspace root. 4. Explicitly reject sensitive or overly broad paths such as `/`, the user's home directory, and system configuration directories. 5. Resolve symlinks before authorization to prevent path traversal through symlinked components. 6. Use `--ro-bind` for the workspace by default. 7. Expose only narrowly scoped output or temporary directories as writable bind mounts. 8. Do not derive `workspace_root` directly from the untrusted `cwd` field. 9. Apply restrictive ownership and permissions to the cache directory and Unix socket so other local users cannot submit requests. 10. Add tests covering `/`, `..`, symlink escapes, nonexistent paths, and paths outside the approved workspace. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
skillshieldd/src/policy.rs:9
Finding
Shell Policy Performs No Command Validation and Labels Every Command Low Risk<![CDATA[ ## Vulnerability Details **File Location**: `skillshieldd/src/policy.rs:9-16` and `skillshieldd/src/main.rs:118-131` **Vulnerability Type**: Missing command-level policy enforcement **Risk Level**: Medium ### Vulnerable Code `skillshieldd/src/policy.rs:9-16`: ```rust pub fn evaluate(request: &ActionRequest) -> InterceptResponse { match &request.action { Action::ShellExec(_) => response( ExecutionPlan::Sandbox, Effect::Sandbox, RiskLevel::Low, "shell.sandbox", "Shell command will run inside the Bubblewrap sandbox".to_string(), ), ``` `skillshieldd/src/main.rs:118-131`: ```rust let decision = policy::evaluate(&request); let executor_name = state.executor.name().to_string(); match decision.execution_plan { models::ExecutionPlan::Execute | models::ExecutionPlan::Sandbox => { match state .executor .execute_shell(&payload.command, payload.cwd.as_deref()) .await { ``` ### Technical Analysis The policy matches only the action type and ignores the shell command's contents. Every shell command receives the same `Sandbox`, `Low` risk decision. The evaluator does not inspect commands, arguments, shell operators, target paths, actor identity, environment changes, workspace boundaries, or approval requirements. Consequently, destructive commands, persistence-related commands, fork bombs, and commands targeting the writable host bind mount are treated identically to harmless commands such as `echo`. The execute handler immediately runs any command receiving the `Sandbox` decision. The shell is intentionally invoked through `sh -c`, so shell metacharacters and compound commands are expected to execute. The defect is not shell parsing by itself; it is the absence of the validation and approval policy that the component claims to provide. ### Attack Path 1. An attacker supplies a destructive or otherwise high-risk shell com ...[truncated 1017 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Implement command-aware policy evaluation rather than accepting every `ShellExec` action. 2. Treat unknown or unclassified shell commands as requiring confirmation or denial. 3. Detect high-risk utilities and shell constructs, including destructive file operations, redirections, command substitution, background execution, and process-spawning loops. 4. Evaluate canonical target paths against a trusted workspace policy. 5. Honor `RequestContext.requires_approval` and do not hardcode it to `false`. 6. Use authenticated actor and session information rather than fixed `"exec"` identities where decisions depend on origin. 7. Prefer structured executable-and-argument requests over unrestricted `sh -c` strings where practical. 8. Maintain explicit allow, deny, and confirmation rules with tests for bypasses using quoting, pipelines, substitutions, and nested shells. 9. Assign risk levels according to command behavior and exposed resources rather than the mere presence of a sandbox. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
skillshieldd/src/sandbox.rs:57
Finding
Sandbox Execution Has No Timeout or Resource Limits<![CDATA[ ## Vulnerability Details **File Location**: `skillshieldd/src/sandbox.rs:57-64` **Vulnerability Type**: Unbounded command execution and resource consumption **Risk Level**: Medium ### Vulnerable Code ```rust cmd.arg("--").arg("sh").arg("-c").arg(command); let output = cmd.output().await?; Ok(ExecutionOutcome { exit_code: output.status.code().unwrap_or(-1), stdout: String::from_utf8_lossy(&output.stdout).to_string(), stderr: String::from_utf8_lossy(&output.stderr).to_string(), }) ``` ### Technical Analysis The daemon waits for the child process with `cmd.output().await` without an execution deadline, cancellation mechanism, output-size bound, or operating-system resource quota. Bubblewrap's `--unshare-all` creates namespaces but does not inherently limit CPU time, memory consumption, process count, disk usage, or output volume. The use of `output()` also buffers the complete standard output and standard error streams in memory before returning. A command that continuously produces output can therefore consume substantial daemon memory even if it does not otherwise escape the sandbox. The documentation states that repetition is limited, but the reviewed implementation contains no repetition counter, per-session quota, rate limit, or loop detection. ### Attack Path 1. An attacker submits a command that runs indefinitely, creates many processes, consumes memory or CPU, or emits unbounded output. 2. The policy automatically approves it for sandbox execution. 3. Bubblewrap starts the command without cgroup or `rlimit` restrictions. 4. The daemon waits indefinitely for completion and buffers all output. 5. Host resources are exhausted or the request remains blocked until an administrator manually terminates the process or daemon. ### Impact Assessment A successful exploit can cause denial of service against the daemon and may degrade the entire host. Potential effects include CPU saturation, memory exhaustion, process-table exhaustion ...[truncated 280 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Wrap process execution in `tokio::time::timeout` with a configurable, conservative deadline. 2. On timeout or client cancellation, terminate the complete process group rather than only the immediate shell. 3. Apply cgroup v2 limits for memory, CPU, process count, and I/O where available. 4. Apply operating-system resource limits such as `RLIMIT_CPU`, `RLIMIT_AS`, `RLIMIT_NPROC`, `RLIMIT_FSIZE`, and `RLIMIT_NOFILE`. 5. Stream output through bounded buffers and terminate or truncate execution after a configured byte limit. 6. Limit writable temporary storage and clean it after every execution. 7. Add per-session concurrency, rate, and repetition limits. 8. Return a distinct policy outcome when a timeout or quota is exceeded. 9. Test infinite loops, fork-heavy commands, memory allocation, large output, and cancellation behavior. ]]>

T08 · Insecure Dependencies

Warning
Location
skillshield-exec.sh:48
Finding
First-Run Build Uses Unlocked Third-Party Dependency Resolution<![CDATA[ ## Vulnerability Details **File Location**: `skillshield-exec.sh:48-51`, `skillshieldd/Cargo.toml:8-19`, and `SKILL.md:24` **Vulnerability Type**: Unpinned dependency resolution during installation and first execution **Risk Level**: Medium ### Vulnerable Code `skillshield-exec.sh:48-51`: ```bash if [[ "$needs_build" -eq 1 ]]; then echo "[SkillShield] Building bundled Rust enforcement daemon..." >&2 CARGO_TARGET_DIR="$TARGET_DIR" cargo build --release --manifest-path "$MANIFEST_PATH" >&2 stop_daemon fi ``` `skillshieldd/Cargo.toml:8-19`: ```toml [dependencies] anyhow = "1" async-trait = "0.1.89" axum = { version = "0.7", features = ["macros", "json"] } hyper = { version = "1.8.1", features = ["full"] } hyper-util = { version = "0.1.20", features = ["tokio", "server", "server-auto"] } serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal", "net", "process"] } tower = { version = "0.5.3", features = ["util"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } ``` `SKILL.md:24`: ```bash npx clawhub@latest install skillshield-openclaw ``` ### Technical Analysis The supplied project structure contains no `Cargo.lock`, while the manifest permits dependency versions through semver ranges. The wrapper invokes `cargo build` without `--locked`, allowing Cargo to resolve and download dependency versions available at build time. Rust dependencies can contain build scripts or proc macros that execute during compilation. Therefore, the effective code executed during the first build is not completely represented by the reviewed files and can change as compatible dependency releases are published. The installation documentation also invokes `npx clawhub@latest`, which intentionally retrieves the current release rather than a fixed, reviewed version. This is not evidence that any listed dependency is malicious, but it cr ...[truncated 1175 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Generate, review, and commit `skillshieldd/Cargo.lock`. 2. Build with `cargo build --release --locked` so dependency resolution fails if the lockfile cannot be honored. 3. Pin the documented npm installer to a specific reviewed version instead of using `@latest`. 4. Verify downloaded package integrity and document trusted registry sources. 5. Review transitive dependencies, proc macros, and build scripts. 6. Use dependency auditing tools such as `cargo audit` and policy tools such as `cargo deny` in CI. 7. Produce reproducible, signed release binaries where practical instead of compiling dependencies during first execution. 8. Re-audit and deliberately update the lockfile when dependency upgrades are required. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (10)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The skill claims strict shell sandboxing with Bubblewrap, but the analyzed content does not substantiate that boundary and suggests broader file and network decision capabilities. This mismatch is dangerous because users and agents may over-trust the skill's safety guarantees and permit sensitive operations under a false assumption of isolation.

Lp3

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding
The skill advertises and appears to enable shell, environment, and network-capable behavior, but it declares no explicit permission or allowed-tool scope. That creates an authorization gap where an agent or user may assume the skill is narrowly constrained when it is not, increasing the chance of unintended command execution or data exposure.

Rp1

Medium
Category
MCP Rug Pull
Confidence
92% confidence
Finding
Using `npx clawhub@latest install ...` pulls and executes an unpinned package version, which introduces supply-chain risk. If the upstream package is compromised or changes behavior, users may run attacker-controlled install logic with the skill's trust context.

External Transmission

Medium
Category
Data Exfiltration
Content
fi

daemon_healthy() {
    curl -fsS --unix-socket "$SOCKET_PATH" http://localhost/health 2>/dev/null
}

start_daemon() {
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The script packages a user-supplied command and submits it to the local execution daemon, which results in shell-command execution. While the file logs daemon startup/build events, it does not clearly disclose to the user that the provided command will be executed, nor does it prompt for confirmation at the execution point.

Session Persistence

Medium
Category
Rogue Agent
Content
use std::sync::Arc;
#[cfg(unix)]
use tokio::net::UnixListener;
use tokio::net::TcpListener;

use anyhow::Context;
use axum::{extract::State, routing::{get, post}, Json, Router};
Confidence
75% 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.

Session Persistence

Medium
Category
Rogue Agent
Content
use std::sync::Arc;
#[cfg(unix)]
use tokio::net::UnixListener;
use tokio::net::TcpListener;

use anyhow::Context;
use axum::{extract::State, routing::{get, post}, Json, Router};
Confidence
75% 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.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The /v1/execute endpoint accepts a command from the request body and executes it server-side after only an internal policy check, with no authentication, authorization, or user-consent mechanism visible in this file. In the context of an agent skill exposing command execution as a network service, this creates a remote command execution surface that can be abused by any party able to reach the socket or TCP listener.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
The code executes a provided command string via `sh -c` inside bubblewrap, which is a safety-relevant subprocess operation. Although the sandbox behavior is documented in comments, there is no user-facing confirmation, logging, or disclosure at the execution site that a shell command is being run.

Missing User Warnings

Low
Confidence
88% confidence
Finding
The startup path unconditionally removes the configured Unix socket file with `std::fs::remove_file(path)` before binding. This is a file-deletion operation, and this file contains no visible warning comment or user-facing disclosure explaining that startup may delete an existing filesystem entry at that path.

Static analysis

No suspicious patterns detected.