Back to skill

Security audit

OpenClaw Capture

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent capture-and-notification wrapper, but it can send user content and credentials through broad, lightly constrained local and network paths without enough user control.

Review this skill before installing. It is not clearly malicious, but only use it with trusted backend URLs, HTTPS for any remote backend, tightly scoped API keys and bot tokens, and a known-safe local STT command. Avoid implicit or automatic use for private text, local image paths, videos, chat identifiers, or proprietary content unless you are comfortable sending the resulting payloads and summaries to the configured services.

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 (3)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/runtime/openclaw_capture_skill/dispatcher.py:222
Finding
Complete capture payload may be transmitted over unencrypted HTTP<![CDATA[ ## Vulnerability Details **File Location**: `scripts/runtime/openclaw_capture_skill/config.py:44, 94`; `scripts/runtime/openclaw_capture_skill/dispatcher.py:222-229` **Vulnerability Type**: Unencrypted transmission of sensitive data to a configurable endpoint **Risk Level**: Medium ### Vulnerable Code ```python # scripts/runtime/openclaw_capture_skill/config.py backend_url: str = "http://127.0.0.1:8765" # ... backend_url=_env( "OPENCLAW_CAPTURE_BACKEND_URL", "http://127.0.0.1:8765", ) or "http://127.0.0.1:8765", ``` ```python # scripts/runtime/openclaw_capture_skill/dispatcher.py def _dispatch_http(self, payload: dict) -> dict: request = urlrequest.Request( f"{self.settings.backend_url.rstrip('/')}/ingest", data=json.dumps(payload).encode("utf-8"), headers={"Content-Type": "application/json"}, method="POST", ) with urlrequest.urlopen(request, timeout=30) as resp: accepted = json.loads(resp.read().decode("utf-8")) ``` ### Technical Analysis HTTP backend mode serializes and transmits the complete normalized capture payload to `OPENCLAW_CAPTURE_BACKEND_URL`. Depending on the request, that payload can contain: - Pasted text in `raw_text` - Source URLs - Image references - Chat and reply identifiers - Request identifiers and platform metadata The default endpoint uses HTTP on a loopback address, which is generally acceptable for a strictly local service. However, the configuration accepts an arbitrary URL without validating its scheme or restricting plaintext HTTP to loopback destinations. Consequently, an operator error or hostile environment configuration can direct sensitive content to a non-loopback HTTP server. The backend URL is an explicitly documented configuration option, so remote dispatch itself is within the declared functionality. The security defect is the absence of transport and destination validation, not the existence of HTTP dispatch. ### Attack Path 1. An attacker ...[truncated 998 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse `OPENCLAW_CAPTURE_BACKEND_URL` before use and allow only `http` or `https`. 2. Permit plaintext HTTP only when the resolved destination is a verified loopback address such as `127.0.0.1`, `::1`, or `localhost`. 3. Require HTTPS for every non-loopback destination. 4. Reject URLs containing unexpected user-info components, fragments, or unsupported schemes. 5. Consider maintaining an explicit backend host allowlist. 6. Document that remote backend mode sends the complete payload outside the local process. 7. Require explicit configuration or user confirmation before first use of a remote backend. 8. Add tests verifying that non-loopback HTTP endpoints are rejected. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/runtime/openclaw_capture_skill/video_audio_bridge.py:53
Finding
Model API key is exposed through child-process command-line arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/runtime/openclaw_capture_skill/dispatcher.py:120-128`; `scripts/runtime/openclaw_capture_skill/video_audio_bridge.py:53-69` **Vulnerability Type**: Credential exposure through process arguments **Risk Level**: Medium ### Vulnerable Code ```python # scripts/runtime/openclaw_capture_skill/dispatcher.py bridge_script = self.settings.skill_root / "scripts" / "video_audio_bridge.py" config.extractors.video_audio_command = ( f'python3 "{bridge_script}" --url "{{url}}" --max-seconds "{{max_seconds}}" ' '--api-key "{api_key}" --api-base-url "{api_base_url}"' ) ``` ```python # scripts/runtime/openclaw_capture_skill/video_audio_bridge.py def _call_legacy_audio( legacy_project_root: Path, *, backend: str, url: str, max_seconds: str, api_key: str, api_base_url: str, ) -> str: script_path = _legacy_audio_script(legacy_project_root) return _run( [ sys.executable, str(script_path), "--url", url, "--max-seconds", str(max_seconds), "--api-key", api_key, "--api-base-url", api_base_url, "--backend", backend, ] ) ``` ### Technical Analysis The model API key is inserted into a command template as the value of `--api-key`. The bridge then forwards the same key as a command-line argument to the legacy ASR process. Although the subprocess API receives an argument list and does not invoke a shell, avoiding direct shell-metacharacter injection at this boundary, command-line arguments are not an appropriate transport for secrets. Depending on the operating system and monitoring configuration, argv values may be visible through process-inspection interfaces, administrative tools, audit systems, crash reports, or diagnostic telemetry. The credential is passed through more than one process boundary, increasing the ti ...[truncated 1132 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `--api-key` from all process command templates and argument lists. 2. Prefer a narrowly scoped environment variable inherited only by the intended child process. 3. Where practical, pass the credential through a protected file descriptor or another operating-system secret-delivery mechanism. 4. If a temporary configuration file is unavoidable, create it with owner-only permissions, avoid predictable names, and delete it immediately after use. 5. Ensure exceptions, debug logs, and child-process output redact API keys. 6. Minimize credential propagation by having only the process that performs the remote API call receive the secret. 7. Use short-lived, restricted credentials and configure provider-side quotas and rotation. 8. Add tests that inspect constructed argv values and fail if secrets appear in them. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/runtime/openclaw_capture_skill/notifiers.py:149
Finding
Telegram bot token may be exposed through unsanitized notification errors<![CDATA[ ## Vulnerability Details **File Location**: `scripts/runtime/openclaw_capture_skill/notifiers.py:149-155`; `scripts/runtime/openclaw_capture_skill/dispatcher.py:158-164`; `scripts/runtime/openclaw_capture_skill/cli.py:31-32` **Vulnerability Type**: Potential credential disclosure through exception propagation **Risk Level**: Low ### Vulnerable Code ```python # scripts/runtime/openclaw_capture_skill/notifiers.py def _send_telegram_payload(self, payload: dict[str, Any]) -> None: if not self.telegram_bot_token: raise RuntimeError("telegram output selected but OPENCLAW_CAPTURE_TELEGRAM_BOT_TOKEN is missing") _post_urlencoded( f"https://api.telegram.org/bot{self.telegram_bot_token}/sendMessage", payload, ) ``` ```python # scripts/runtime/openclaw_capture_skill/dispatcher.py try: notifier = self._build_fanout_notifier(legacy_cfg=legacy_cfg) notifier.send_from_job_result(payload, job, skip_outputs=skip_outputs) except Exception as exc: job.setdefault("warnings", []).append(f"wrapper_notification_error: {exc}") result = job.setdefault("result", {}) if isinstance(result, dict): result["notification_error"] = str(exc) ``` ```python # scripts/runtime/openclaw_capture_skill/cli.py job = dispatcher.dispatch(payload) print(json.dumps(job, ensure_ascii=False, indent=2)) ``` ### Technical Analysis Telegram's Bot API requires the bot token to appear in the request path. The implementation constructs a URL containing the token and passes it directly to `urllib`. Notification exceptions are caught and copied verbatim into two externally visible job fields. The complete job is then printed by the CLI. If a transport implementation, proxy, runtime wrapper, or diagnostic layer includes the request URL in an exception message, the token-bearing URL may consequently be persisted in logs or returned to callers. The current code does not guarantee that every exception includes the URL, so disclosure depe ...[truncated 1208 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Never return raw notification exception text to callers. 2. Replace externally visible errors with stable, generic error codes such as `telegram_delivery_failed`. 3. Redact Telegram bot tokens and token-bearing URL paths before logging any exception. 4. Keep detailed diagnostics only in access-controlled logs after sanitization. 5. Implement a central secret-redaction utility covering Telegram tokens, model API keys, and webhook credentials. 6. Add unit tests using exceptions that contain the full request URL and verify that returned jobs and CLI output do not contain the token. 7. Rotate the Telegram token immediately if it is ever observed in logs or returned output. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (22)

Credential Access

High
Category
Privilege Escalation
Content
elif legacy_project_root and (legacy_project_root / "config.json").exists():
            legacy_config_path = (legacy_project_root / "config.json").resolve()
        legacy_env_path = None
        if legacy_project_root and (legacy_project_root / ".env").exists():
            legacy_env_path = (legacy_project_root / ".env").resolve()
        return cls(
            skill_root=skill_root,
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
elif legacy_project_root and (legacy_project_root / "config.json").exists():
            legacy_config_path = (legacy_project_root / "config.json").resolve()
        legacy_env_path = None
        if legacy_project_root and (legacy_project_root / ".env").exists():
            legacy_env_path = (legacy_project_root / ".env").resolve()
        return cls(
            skill_root=skill_root,
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill instructs use of shell execution, environment variables, local file paths, networked backends, and external output channels, but it does not declare any explicit tool scope or permission boundaries. This creates an over-privileged integration surface where an agent may access sensitive local resources or transmit data externally without clear least-privilege constraints or user visibility.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill is designed to capture user-provided links, text, images, and videos, then send data through a backend and fan results out to Telegram and Feishu, but the description does not warn users that their content may be transmitted to external services and notification endpoints. This lack of disclosure undermines informed consent and can lead to unintended leakage of sensitive content, credentials, personal data, or proprietary media.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The skill enables implicit invocation without any visible activation constraints, which can cause the agent to trigger this skill in response to loosely related user input. Because this skill dispatches local capture jobs and routes results to external channels, over-broad triggering increases the risk of unintended local workflow execution and unintended data exfiltration to Telegram or Feishu.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The documentation explicitly describes sending capture payloads to a remote backend and using remote STT fallback, but it does not warn users that captured content, links, text, images, videos, or audio may leave the local system. In the context of a capture-and-fanout skill, this omission increases the risk of unintended disclosure of sensitive user data to third-party services.

External Transmission

Medium
Category
Data Exfiltration
Content
## Model Profile

- `openai_direct`
  - Default base URL: `https://api.openai.com/v1`
- `aihubmix_gateway`
  - Default base URL: `https://aihubmix.com/v1`
Confidence
82% confidence
Finding
The file documents default model API endpoints for external providers, which indicates captured or derived content may be sent to third-party services. While using external APIs is not inherently malicious, in this skill's context it is security-relevant because the skill processes potentially sensitive captured data and the documentation does not pair these endpoints with privacy, consent, or data-handling warnings.

External Transmission

Medium
Category
Data Exfiltration
Content
def _default_model_api_base_url(model_profile: str) -> str:
    if model_profile == "openai_direct":
        return "https://api.openai.com/v1"
    return "https://aihubmix.com/v1"
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The payload normalization unconditionally defaults requested_output_lang to "zh-CN" when the user does not provide a language. This imposes a specific locale by default rather than offering a neutral default or explicit user choice.

Context-Inappropriate Capability

Medium
Confidence
81% confidence
Finding
The manifest describes a wrapper skill that captures content, routes STT, and fans results out, but this code also configures command-line execution of helper scripts for video subtitle, audio, and keyframe extraction. Spawning external commands is a stronger capability than simple dispatch/orchestration and is not explicitly justified by the stated wrapper purpose.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The code performs a POST request to an external/backend URL with the entire payload, which may include user content such as raw_text, source_url, chat identifiers, or image references. While there is a module docstring about HTTP mode, there is no confirmation prompt, logging, or explicit user-facing warning here that data will be transmitted over the network.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This code emits fixed Chinese strings such as "未命名内容", "一句话总结", and other section headings regardless of user preference or environment. That creates a language/locale policy issue because the skill enforces a specific language without offering a choice or documenting a justified regional scope.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
This code constructs multiple user-visible summary fields entirely in Chinese, including bullets, follow-up actions, and default titles/conclusions. That imposes a specific language on all users without opt-in, which matches the language/locale policy violation criteria.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The ingest namespace assigns "zh-CN" as the default requested_output_lang when no user preference is provided. This forces a specific locale by default rather than offering a choice or requiring explicit opt-in, which matches the language/locale policy violation criteria.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This code posts notification payloads to Telegram and Feishu, and the transmitted text is built from ingest, summary, note path, structure map, open URL, and evidence data. There is no confirmation prompt, print/log statement, or explanatory comment/docstring near the outbound requests to disclose that user or system-derived content will be sent to external services.

External Transmission

Medium
Category
Data Exfiltration
Content
if not self.telegram_bot_token:
            raise RuntimeError("telegram output selected but OPENCLAW_CAPTURE_TELEGRAM_BOT_TOKEN is missing")
        _post_urlencoded(
            f"https://api.telegram.org/bot{self.telegram_bot_token}/sendMessage",
            payload,
        )
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def _run(args: list[str]) -> str:
    try:
        result = subprocess.run(args, check=True, capture_output=True, text=True)
    except subprocess.CalledProcessError as exc:
        stderr = (exc.stderr or "").strip()
        stdout = (exc.stdout or "").strip()
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The code forwards the API key and base URL directly to child processes, including legacy scripts and potentially configurable local tooling. This broadens exposure of sensitive credentials to other executables and code paths, and if those subprocesses are untrusted, compromised, or verbose in logging/error handling, the key may be disclosed or misused.

Context-Inappropriate Capability

Medium
Confidence
98% confidence
Finding
The skill executes a configurable local_stt_command after formatting it with runtime values and splitting it into argv, which allows arbitrary local program execution under the agent's privileges. In a skill whose stated purpose is transcription routing, this materially expands capability from STT selection to unrestricted command execution, making hostile or tampered configuration able to run arbitrary binaries and potentially exfiltrate data.

Natural-Language Policy Violations

Low
Confidence
96% confidence
Finding
The instruction requires the skill to immediately tell the user `已收到,开始处理。`, which enforces a Chinese response regardless of the user's language preference. This is a natural-language locale policy concern because no opt-in or language-selection behavior is described.

Missing User Warnings

Low
Confidence
77% confidence
Finding
This code file calls Settings.from_env(), which indicates access to environment-provided configuration and potentially sensitive credentials. The CLI provides no confirmation, log message, comment, or docstring warning that environment values may be read during execution.

Missing User Warnings

Low
Confidence
82% confidence
Finding
The settings loader reads a model API key, Telegram bot token, and Feishu webhook from environment variables. This is access to sensitive credentials, but the file provides no confirmation, logging, comment, or docstring explaining that the skill consumes these secrets.

Static analysis

No suspicious patterns detected.