Back to skill

Security audit

Claw Config

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly coherent, but it gives agents persistent configuration-changing power and has under-scoped URL fetching and patch-guard weaknesses that users should review before installing.

Install only if you are comfortable giving this skill persistent access to inspect and modify OpenClaw configuration. Before using `apply`, require human review of the patch, avoid `--force-shared` unless an operator explicitly approves it, and do not pass full URLs to `docs`; use only known OpenClaw documentation slugs until the URL allowlist, cache path containment, and array-replacement guard are fixed.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • 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
  • 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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
claw-config.py:546
Finding
Unrestricted Documentation URL Fetching Enables Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Location**: `claw-config.py:458-461` and `claw-config.py:546-550` **Vulnerability Type**: Server-Side Request Forgery (SSRF) through unrestricted URL fetching **Risk Level**: High ### Complete Vulnerable Code ```python try: p = subprocess.run( ["curl", "-fsSL", "--max-time", "10", url], capture_output=True, text=True, timeout=12, ) ``` ```python # full URL or slug if topic.startswith("http://") or topic.startswith("https://"): url = topic else: slug = topic.lstrip("/") if slug.endswith(".md"): slug = slug[:-3] url = f"{DOCS_BASE}/{slug}.md" ``` ### Technical Analysis The `docs` subcommand accepts any URL beginning with `http://` or `https://` and passes it directly to `curl`. There is no restriction requiring the destination to be `docs.openclaw.ai`, even though retrieving official OpenClaw documentation is the declared reason for network access. There is also no validation of: - The destination hostname - The resolved IP address - Loopback, link-local, private, or reserved address ranges - Redirect destinations - Whether HTTPS is used The `-L` option instructs `curl` to follow redirects. Consequently, even an initially permitted-looking external destination could redirect the request to an internal service. The request does not automatically attach OpenClaw configuration values, credentials, tokens, or arbitrary environment variables. Nevertheless, arbitrary network access exceeds the minimum privilege required to retrieve official documentation. ### Attack Path 1. An attacker or untrusted instruction causes the Agent to invoke a command such as: ```bash claw-config docs http://127.0.0.1:PORT/internal ``` or: ```bash claw-config docs http://169.254.169.254/metadata-path ``` 2. `cmd_docs()` accepts the full URL without validating its hostname or resolved address. 3. `_fetch_docs()` executes `curl -fsSL` against the supplied destination. 4 ...[truncated 903 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove support for arbitrary full URLs. Accept only documentation slugs and construct the URL internally under `https://docs.openclaw.ai/`. 2. Parse destinations with `urllib.parse.urlsplit()` and require: - Scheme exactly equal to `https` - Hostname exactly equal to `docs.openclaw.ai` - No embedded credentials - An expected or empty port 3. Disable redirects or validate every redirect destination against the same restrictions. 4. Resolve the destination hostname and reject loopback, link-local, private, multicast, unspecified, and reserved IP addresses. 5. Do not rely solely on string-prefix checks such as `url.startswith(DOCS_BASE)`, because hostname confusion and redirect behavior can bypass weak checks. 6. Consider replacing the external `curl` process with a narrowly configured HTTP client that enforces destination and response-size policies. 7. Add a maximum response size to prevent an attacker-controlled endpoint from causing excessive memory or disk use. 8. Add tests covering localhost, private addresses, cloud metadata addresses, alternate ports, malformed hostnames, and cross-host redirects. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
claw-config.py:439
Finding
URL-Derived Cache Path Traversal Enables Writes Outside the Documentation Cache<![CDATA[ ## Vulnerability Details **File Location**: `claw-config.py:439-443` and `claw-config.py:475` **Vulnerability Type**: Path traversal and attacker-controlled file write **Risk Level**: High ### Complete Vulnerable Code ```python slug = url.replace(DOCS_BASE, "").strip("/") or "index" slug_safe = re.sub(r"[^A-Za-z0-9._/-]", "_", slug) cache_file = _docs_cache_dir() / slug_safe cache_file.parent.mkdir(parents=True, exist_ok=True) ``` ```python body = p.stdout cache_file.write_text(body) return body, None ``` ### Technical Analysis The cache filename is derived from the requested URL. The attempted sanitizer permits both forward slashes and periods: ```python r"[^A-Za-z0-9._/-]" ``` As a result, `../` traversal components remain unchanged. The code then joins the resulting value to the documentation cache directory without resolving the final path and verifying that it remains inside that directory. After the network request succeeds, the complete response body is written to the derived location with `Path.write_text()`. Because arbitrary URLs are accepted by `cmd_docs()`, an attacker can potentially control both: - Traversal components in the URL path - The response content returned by an attacker-controlled server The parent directories are created before the write, further facilitating writes to nested paths outside the intended cache. ### Attack Path 1. An attacker operates an HTTP or HTTPS server that returns chosen text content. 2. The attacker causes the Agent to invoke the `docs` command with a URL containing enough `../` components to escape the cache directory. 3. `_fetch_docs()` derives `slug_safe` from that URL while preserving the traversal segments. 4. `_docs_cache_dir() / slug_safe` produces a path outside `~/.openclaw/.maintenance-cache/docs/<version>/`. 5. `cache_file.parent.mkdir()` creates missing parent directories where permitted. 6. The attacker-controlled HTTP response is written to the escaped destination using `write_te ...[truncated 1162 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not use the raw URL path as a filesystem path. 2. Generate cache filenames from a cryptographic digest of the canonical URL, for example SHA-256, and store metadata separately if a human-readable source reference is needed. 3. If hierarchical slugs must be retained: - Parse the URL path into components. - Reject empty, `.` and `..` components. - Reject absolute paths and platform-specific separators. - Encode each component rather than merely replacing selected characters. 4. Resolve the cache root and candidate destination, then enforce containment before creating directories or writing: ```python cache_root = _docs_cache_dir().resolve() target = (cache_root / safe_name).resolve() target.relative_to(cache_root) ``` Treat a `ValueError` as an invalid path. 5. Use atomic writes through a temporary file created inside the verified cache directory, followed by `os.replace()`. 6. Apply restrictive file permissions where supported. 7. Combine this remediation with strict destination allowlisting; otherwise attacker-controlled remote content can still be placed in cache files. 8. Add regression tests for `../`, repeated traversal, absolute paths, encoded separators, backslashes, symlinked cache directories, and excessively long paths. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
claw-config.py:190
Finding
Cross-Agent Protection Can Be Bypassed Through Destructive Array Replacement<![CDATA[ ## Vulnerability Details **File Location**: `claw-config.py:190-206` and `claw-config.py:817-820` **Vulnerability Type**: Incomplete authorization validation for shared configuration arrays **Risk Level**: High ### Complete Vulnerable Code ```python # agents.list[*].id check if path == ["agents", "list"] and isinstance(node, list): for i, entry in enumerate(node): if isinstance(entry, dict): eid = entry.get("id") if eid and eid != self_id: other_agents.append(f"agents.list[{i}] (id={eid})") return # bindings — entries with agentId != self if path == ["bindings"] and isinstance(node, list): for i, b in enumerate(node): if isinstance(b, dict): aid = b.get("agentId") if aid and aid != self_id: other_agents.append(f"bindings[{i}] (agentId={aid})") elif not aid: shared_paths.append(f"bindings[{i}] (no agentId)") return ``` The submitted patch is later applied as follows: ```python # `openclaw config patch` validates against the schema internally before # writing — rc=0 means schema-clean. Our backup + rollback is defense in # depth in case patch's own atomicity ever fails (e.g. interrupted write). _, err, rc = ocw("config", "patch", "--file", str(patch_path), check=False) ``` The documented patch semantics state that arrays replace the entire existing array rather than merging individual entries. ### Technical Analysis The authorization check only inspects entries explicitly present in the submitted `agents.list` or `bindings` array. It rejects entries that directly identify another Agent, but it does not compare the replacement array with the current configuration. A patch containing only the caller’s Agent record therefore contains no explicitly foreign identifier and passes the guard. Because arrays replace the existing value, applying that patch can remove every omitted Agent. The same problem applies to `bindings`: ...[truncated 1719 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat `agents.list` and `bindings` as shared configuration sections and reject all whole-array updates from the normal single-Agent workflow. 2. Require explicit operator authorization for any operation that replaces a shared array. A simple `--force-shared` flag is insufficient when an autonomous Agent can choose to use it. 3. If whole-array updates must be supported: - Read the current configuration. - Identify all entries not owned by the caller. - Require every non-self entry to remain present and byte-for-byte or semantically unchanged. - Reject reordering if array position has semantic significance. 4. Prefer an index-specific or path-specific update mechanism that modifies only the caller’s existing entry. 5. Resolve the caller’s entry index from current configuration immediately before applying the patch to reduce stale-state errors. 6. Repeat authorization checks immediately before the live write rather than relying only on an earlier plan operation. 7. Bind `apply` to an approved plan by recording a digest of the patch and current configuration version. Refuse to apply a patch that was not planned or whose base configuration changed. 8. Add tests proving that omission, truncation, reordering, duplicate IDs, missing IDs, `null`, and empty replacement arrays cannot affect other Agents. ]]>

T01 · Skill Instruction Hijacking

Warning
Location
SKILL.md:49
Finding
Untrusted Remote Documentation Can Influence Security-Sensitive Agent Actions<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:49-52` and `claw-config.py:546-556` **Vulnerability Type**: Indirect prompt injection through remotely fetched documentation **Risk Level**: Medium ### Complete Vulnerable Code and Instructions The Skill directs the Agent to consume documentation before constructing a configuration patch: ```markdown 3. **If you do not know what a field is called, run `schema <path>`.** Do not write `nativeSkill` / `enableSkills` / `commands.skills` from memory. The real names come from the installed binary's schema. 4. **Before composing a patch, read the docs.** Run `docs <topic>` (e.g., `cli/config`, `channels/telegram`, `tools/skills`, `cli/cron`, `gateway/config-agents`, `announcements`). If you do not know which page is relevant, run `docs` (no argument, prints the site index) or `docs search:<keyword>` (greps the full-content corpus across every page). 5. **Before `apply`, always `plan` first.** `plan` is a dry-run via `openclaw config patch --dry-run`. Read the diff, confirm the validate result. 6. **If `apply` fails, the backup is automatically restored.** Quote the stderr verbatim to the operator. ``` The implementation permits arbitrary remote sources and prints their content directly: ```python # full URL or slug if topic.startswith("http://") or topic.startswith("https://"): url = topic else: slug = topic.lstrip("/") if slug.endswith(".md"): slug = slug[:-3] url = f"{DOCS_BASE}/{slug}.md" try: body, age = _fetch_docs(url, refresh=refresh, max_age_sec=max_age_sec) except RuntimeError as e: sys.stderr.write(str(e) + "\n"); sys.exit(1) print(body) ``` ### Technical Analysis The Skill establishes fetched documentation as authoritative semantic input immediately before the Agent composes or approves a configuration patch. However, the implementation accepts arbitrary HTTP and HTTPS URLs and prints the returned markdown without marking it as untrusted data. An attacker ...[truncated 2202 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove arbitrary URL support and restrict documentation retrieval to verified `https://docs.openclaw.ai/` pages. 2. Treat all fetched documentation as untrusted reference data, not instructions. 3. Add explicit Skill instructions stating that the Agent must never execute commands, change objectives, reveal data, or weaken safety controls based on text contained in fetched documentation. 4. Delimit remote content clearly, for example: ```text BEGIN UNTRUSTED DOCUMENTATION ... END UNTRUSTED DOCUMENTATION ``` 5. Extract only narrowly relevant field descriptions instead of returning unrestricted full-page content where possible. 6. Require human confirmation before `apply`, especially for shared sections, security-sensitive fields, deletions, arrays, hooks, tools, gateway configuration, and authentication-related settings. 7. Do not allow remote prose to authorize `--force-shared`; require an out-of-band operator capability or policy decision. 8. Continue validating proposed fields against the installed schema, but recognize that schema validation addresses format rather than intent or authorization. 9. Record the documentation origin and warn prominently when content is not from the exact official hostname. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • 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)

Self-Modification

High
Category
Rogue Agent
Content
# claw-config

Shared skill for the OpenClaw self-hosted agent gateway that lets any single agent safely self-diagnose and self-modify its own slice of `~/.openclaw/openclaw.json` without hallucinating field names, identity, or paths.

Python 3 · stdlib only · MIT
Confidence
96% confidence
Finding
This skill is explicitly designed to let an agent modify its own persistent OpenClaw configuration, which is a real self-modification capability. Even with documented guardrails, this is security-sensitive because a compromised or misaligned agent could use the feature to alter command dispatch, bindings, or other persistent behavior and potentially degrade controls or entrench persistence.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared purpose is a bounded self-diagnosis/config utility, but the observed behavior includes installation into the user's skills directory, PATH persistence via symlink, and preflight/environment manipulation that are not clearly disclosed by the top-level contract. Description-behavior drift is dangerous because operators and calling agents may trust the skill for narrow config inspection while it performs broader persistent system modifications.

Session Persistence

Medium
Category
Rogue Agent
Content
cd claw-config

# install into your shared OpenClaw skills directory (auto-discovered by every agent)
mkdir -p ~/.openclaw/skills/claw-config
cp SKILL.md claw-config.py ~/.openclaw/skills/claw-config/
chmod +x ~/.openclaw/skills/claw-config/claw-config.py
Confidence
91% confidence
Finding
The installation instructions place the skill in a shared auto-discovered skills directory and symlink it onto the user's PATH, creating durable session persistence for all agents on the host. This increases risk because any future compromise, prompt injection, or unintended invocation can repeatedly access the installed capability without additional user review.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill advertises shell, environment, file-read, and file-write capabilities but does not declare any explicit tool scope such as permissions or allowed-tools. That creates an over-privileged execution surface where a caller or downstream agent may invoke destructive operations like config writes, backups, symlink creation, or network-backed docs fetches without a clearly constrained policy boundary.

Session Persistence

Medium
Category
Rogue Agent
Content
> *"Who am I, what does my own config look like, and how do I change it safely?"*

**Design principle: no hallucination.** Every field name, every JSON pointer, every current value is read from the installed `openclaw` CLI at call time (`openclaw config schema`, `openclaw config get`). Every write goes through `openclaw config patch` (which validates against the schema internally). When the agent needs to know what a field *does*, the skill fetches the official documentation from `docs.openclaw.ai` (Mintlify `.md` raw + full-content index `llms-full.txt`) — never the model's training memory.

**Comparison with `openclaw doctor`**: `doctor` is a system-wide health check (gateway / secrets / channels), human-readable output. `claw-config` is **agent-scoped** (sliced by `$OPENCLAW_AGENT_ID` — only sees the caller's own config) and supports `--json` for downstream tooling. They are complementary, not duplicates.
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.

Intent-Code Divergence

Medium
Confidence
84% confidence
Finding
The module docstring states that identity verification makes wrong-agent writes impossible by construction, but later code allows patching shared/non-self sections such as `agents.defaults`, `gateway`, `secrets`, `auth`, and similar paths when `--force-shared` is used. That is an active contradiction between the documented safety guarantee and the implemented write behavior.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def ocw(*args, stdin=None, check=True, timeout=60):
    """Run `openclaw <args>`. Return (stdout, stderr, rc)."""
    try:
        p = subprocess.run(
            [OPENCLAW, *args],
            input=stdin,
            capture_output=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
raise RuntimeError(f"docs fetch disabled in cron and no cached copy at {cache_file}")

    try:
        p = subprocess.run(
            ["curl", "-fsSL", "--max-time", "10", url],
            capture_output=True, text=True, timeout=12,
        )
Confidence
92% confidence
Finding
Although `curl` is invoked without a shell, this still creates an outbound fetch primitive over attacker-influenced input (`url`). In a skill intended to retrieve only official docs, this can be abused for SSRF-like access to internal services, metadata endpoints, or unintended egress to arbitrary hosts.

Description-Behavior Mismatch

Medium
Confidence
99% confidence
Finding
The `docs` command accepts any full `http://` or `https://` URL, contradicting the stated trust boundary that it only fetches official documentation. This widens the skill from a bounded documentation reader into a generic network retrieval tool, enabling data exfiltration paths and access to sensitive internal endpoints if the environment has network reachability.

Context-Inappropriate Capability

Medium
Confidence
99% confidence
Finding
This code exposes a general outbound network capability through the ostensibly harmless `docs` interface. In agent skill context, that is more dangerous than in ordinary CLI tooling because another agent may be induced to use it as a proxy to reach attacker-chosen destinations, bypassing intended capability scoping.

Static analysis

No suspicious patterns detected.