Back to skill

Security audit

OpenClaw VLN Planner

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed robot navigation planner, but review is warranted because its bridge can send arbitrary local files and robot context to a configurable remote endpoint and misses a documented low-visibility stop condition.

Install only after reviewing and tightening deployment controls: restrict frame inputs to trusted camera directories, validate actual image content and size, require an HTTPS allowlisted gateway, protect the API key outside ordinary YAML, pin dependencies, and fix low_visibility so it deterministically forces STOP before any model call or executor action.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/vln_bridge.py:94
Finding

Unrestricted Local File Disclosure to a Configurable Network Endpoint

Content
View full analysis
str: mime, _ = mimetypes.guess_type(path) mime = mime or "image/jpeg" with open(path, "rb") as f: encoded = base64.b64encode(f.read()).decode("ascii") return f"data:{mime};base64,{encoded}" ``` The encoded file contents are incorporated into the model request: ```python content: List[Dict[str, Any]] = [{"type": "text", "text": text}] for hf in history_frames: content.append({"type": "image_url", "image_url": {"url": image_to_data_url(hf)}}) content.append({"type": "image_url", "image_url": {"url": image_to_data_url(current_frame)}}) return [{"role": "user", "content": content}] ``` The request is then sent to the endpoint supplied by the configuration: ```python def call_openai_compatible(cfg: PlannerConfig, messages: List[Dict[str, Any]]) -> str: headers = { "Authorization": f"Bearer {cfg.api_key}", "Content-Type": "application/json", } payload = { "model": cfg.model_id, "messages": messages, "temperature": 0, "response_format": {"type": "json_object"}, } chat_url = f"{cfg.base_url}/chat/completions" resp = requests.post(chat_url, headers=headers, json=payload, timeout=cfg.timeout_seconds) resp.raise_for_status() return extract_text_from_response(resp.json()) ``` ### Technical Analysis Sending current and historical camera frames to a multimodal gateway is part of the Skill's declared functionality. Base64 encoding is also a normal implementation mechanism for image data URLs and is not, by itself, evidence of covert exfiltration. However, the implementation does not verify t ...[truncated 2722 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/vln_bridge.py:292
Finding

Low-Visibility Safety Flag Does Not Force a Stop

Content
View full analysis
bool: return any(bool(safety_flags.get(k, False)) for k in ["blocked", "collision_risk", "lost"]) ``` The documented safety contract in `references/navigation-schema.md` states that `low_visibility` must also trigger the fallback stop, but the implementation omits that flag. ### Technical Analysis The bridge performs a local safety check before asking the remote model for a navigation action. It correctly forces a stop for `blocked`, `collision_risk`, and `lost`, but it does not enforce `low_visibility`. As a result, a request containing: ```json { "low_visibility": true } ``` continues to the multimodal gateway. If the gateway returns a syntactically valid, in-range movement with confidence above the configured threshold, `validate_action()` accepts it and `bridge_execute()` may forward it to the execution layer. Prompt instructions asking the model to stop when uncertain do not replace deterministic enforcement of an externally supplied safety flag. A model may misunderstand, ignore, or fail to weigh the flag correctly. Safety-critical conditions must be checked locally and fail closed. The bundled executor functions are placeholders and the default configuration enables dry-run mode, so the unmodified example does not actuate a robot. The Skill is expressly designed for those functions to be replaced and for dry-run mode to be disabled in deployment; the vulnerability becomes physically consequential in that intended integration. ### Attack Path 1. The robot or surrounding runtime reports `{"low_visibility": true}`. 2. `goal_reached()` returns false. 3. `should_force_stop()` checks only `blocked`, `collision_risk`, and `lost`, so it returns false. 4. The low-visibility flag ...[truncated 1031 chars]
Remediation
View remediation
bool: return any(safety_flags.get(flag) is True for flag in FORCE_STOP_FLAGS) ``` 2. Centralize the authoritative safety policy in one local function and invoke it immediately before every execution call, not only before the model request. 3. Use a fail-closed safety-state schema: - Validate that safety flags are booleans. - Treat malformed or unavailable safety state as unsafe where appropriate. - Consider stale safety telemetry unsafe. - Define how unknown safety flags are handled. 4. Keep model prompting as defense in depth only. Never rely on model confidence or prompt compliance to override an asserted safety condition. 5. Add automated tests confirming that every documented safety flag produces `STOP` without calling the gateway or movement executor. 6. Require explicit deployment approval before disabling dry-run mode, and place an independent collision-avoidance or emergency-stop controller below the language-model planning layer. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (7)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill explicitly instructs the runtime to read local files such as config/vln-config.yaml and references/navigation-schema.md, and to call an OpenAI-compatible multimodal gateway using base_url and api_key, which implies file-read and network capabilities. Because the skill does not declare any permissions or allowed-tools scope, a host may grant broader access than intended, making it easier for the skill or downstream code to access sensitive files or send data externally without clear policy boundaries.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

This code sends current and historical images, the user instruction, and optional robot state/safety data to an external OpenAI-compatible gateway via HTTP. Although the module docstring describes this behavior, there is no user-facing print, prompt, or warning at the point of transmission, so the potentially privacy-impacting upload is not clearly disclosed during execution.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/vln_bridge.py (reported line 271)May include surrounding context.

python
}

    chat_url = f"{cfg.base_url}/chat/completions"
    resp = requests.post(chat_url, headers=headers, json=payload, timeout=cfg.timeout_seconds)
    resp.raise_for_status()
    return extract_text_from_response(resp.json())

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
96% confidence
Finding

The dependency is specified with a lower bound only (requests>=2.31.0), which makes builds non-reproducible and allows future installs to resolve to unexpected versions. In a security-sensitive skill pipeline, this can result in silently pulling a vulnerable or breaking release, making it harder to verify what code is actually deployed.

Content

Scanner excerpt · scripts/requirements.txt (reported line 1)May include surrounding context.

text
requests>=2.31.0
PyYAML>=6.0.1

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
89% confidence
Finding

Because requests is not pinned, it is impossible to determine from this manifest alone whether deployment will use a version affected by one of the known advisories. The skill context suggests possible network communication with multimodal gateways or execution bridges, so depending on the resolved version, HTTP-related flaws could matter in practice.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
97% confidence
Finding

PyYAML>=6.0.1 is also unpinned, so the environment may install different versions over time without review. This is particularly relevant because YAML libraries have a history of unsafe parsing issues, and version drift can reintroduce exposure or make audits unverifiable.

Content

Scanner excerpt · scripts/requirements.txt (reported line 2)May include surrounding context.

text
requests>=2.31.0
PyYAML>=6.0.1

Unverifiable Dependency: PyYAML has 8 known advisory(ies) (CVE-2019-20477 (Deserialization of Untrusted Data in PyYAML); CVE-2020-1747 (Improper Input Validation in PyYAML); CVE-2020-14343 (Improper Input Validation in PyYAML) +5 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
91% confidence
Finding

The manifest does not prove which PyYAML version will be installed, so exposure to known PyYAML advisories cannot be ruled out. In this skill context, YAML is likely configuration-related; if any untrusted YAML is parsed, historical deserialization and input-validation issues could become more dangerous.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.