Back to skill

Security audit

OpenClaw for Doctor

Security checks for vulnerabilities and agentic risk

Overview

The clinical assistant is coherent in purpose, but it needs Review because it can expose sensitive medical task data through unauthenticated APIs, plaintext storage, and unrestricted outbound callbacks or delivery channels.

Install only in a controlled, clinician-supervised environment. Set a strong OPENCLAW_DOCTOR_API_TOKEN before exposing the API, bind Docker to loopback or place it behind authenticated TLS, keep outbound sending disabled unless destinations are approved, avoid patient identifiers unless authorized, and add encryption, retention, deletion, and callback allowlisting before using it with real clinical data.

Vulnerability Patterns
  • 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
  • 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
Findings (3)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
app/main.py:40
Finding

Authentication Is Disabled by Default on Externally Published Task APIs

Content
View full analysis
None: if not settings.api_token: return if x_doctor_token != settings.api_token: ``` `app/config.py:9-11`: ```python class Settings: env: str = "dev" api_token: str = "" task_store: str = "sqlite" ``` `app/config.py:39`: ```python api_token=os.getenv("OPENCLAW_DOCTOR_API_TOKEN", "").strip(), ``` `docker-compose.yml:5-6`: ```yaml ports: - "8010:8010" ``` ### Technical Analysis The authentication dependency treats an empty API token as an instruction to allow every request. Because the default value is an empty string, all task endpoints are unauthenticated unless an operator explicitly supplies `OPENCLAW_DOCTOR_API_TOKEN`. The Docker Compose port mapping publishes port 8010 on all host interfaces by default, rather than limiting it to loopback. This contradicts the documentation’s implication that the service is available only at `127.0.0.1` and makes the fail-open authentication behavior reachable from other systems whenever host or network firewall rules permit it. The affected endpoints support creating and executing tasks, listing all stored tasks, retrieving individual task records, cancelling tasks, and retrying failed or cancelled tasks. Stored records can include `doctor_id`, clinical queries, case summaries, attachment references, generated results, and error details. ### Attack Path 1. An operator starts the service through Docker Compose without setting `OPENCLAW_DOCTOR_API_TOKEN`. 2. Docker publishes TCP port 8010 on every host interface. 3. An attacker with network access sends `GET /api/tasks` without an `X-Doct ...[truncated 968 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
app/services/async_executor.py:84
Finding

Request-Controlled Callback URL Enables Server-Side Request Forgery

Content
View full analysis
None: if not self.settings.enable_outbound_send: return task = self.task_manager.get(task_id) callback_url = (task.request.callback_url or "").strip() if not callback_url: return payload = { "task_id": task.task_id, "status": task.status.value, "result": task.result.model_dump(mode="json") if task.result else None, } request = urllib.request.Request( callback_url, data=json.dumps(payload).encode("utf-8"), headers={"Content-Type": "application/json"}, method="POST", ) try: with urllib.request.urlopen(request, timeout=self.settings.webhook_timeout_seconds): return except urllib.error.URLError: return ``` ### Technical Analysis `callback_url` is accepted as an unrestricted string. After an asynchronous task completes, the worker passes this value directly to `urllib.request.urlopen`. No controls restrict: - URL schemes. - Destination hostnames. - Resolved IP addresses. - Loopback, private, link-local, multicast, or reserved networks. - Destination ports. - Redirect destinations. - DNS rebinding between validation and connection. When `OPENCLAW_DOCTOR_ENABLE_OUTBOUND_SEND=true`, an API caller can therefore cause t ...[truncated 1831 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
app/services/sqlite_task_manager.py:42
Finding

Clinical Task Data Is Persisted as Unencrypted JSON Without Retention Controls

Content
View full analysis
None: payload = task.model_dump_json() updated_at = task.updated_at.isoformat() with self._conn: self._conn.execute( """ INSERT INTO tasks(task_id, task_json, updated_at) VALUES(?, ?, ?) ON CONFLICT(task_id) DO UPDATE SET task_json=excluded.task_json, updated_at=excluded.updated_at """, (task.task_id, payload, updated_at), ) ``` ### Technical Analysis SQLite is the default storage backend. The complete `StoredTask` model is serialized to JSON and written directly to the database. The stored object can include: - Doctor identifiers. - Original queries. - Patient case summaries. - Attachment references. - Execution profiles. - Generated clinical results. - Delivery receipts and error messages. No field-level encryption, database encryption, retention period, deletion workflow, redaction, or data-minimization policy is implemented. The application also does not explicitly establish restrictive permissions for the database file. The SQL operations themselves are parameterized, so this finding is not SQL injection. The issue is confidentiality and lifecycle protection for stored clinical information. ### Attack Path 1. A clinician submits a task containing patient or case details. 2. The application serializes the complete request and result into `task_json`. 3. The plaintext record remains in `doctor_tasks.db` indefinitely. ...[truncated 1023 chars]
Remediation
View remediation
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
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (15)

Tainted flow: 'request' from os.getenv (line 64, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
91% confidence
Finding

The code sends an HTTP request to a URL taken directly from an environment variable with no validation or allowlisting. If an attacker can influence environment configuration, this becomes an SSRF-style outbound request primitive that can exfiltrate task data in the payload or reach unintended internal/external endpoints.

Content

Scanner excerpt · app/channels/base.py (reported line 71)May include surrounding context.

python
method="POST",
        )
        try:
            with urllib.request.urlopen(request, timeout=settings.webhook_timeout_seconds) as resp:
                status_code = resp.getcode()
            if 200 <= status_code < 300:
                return DeliveryReceipt(

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · README.md (reported line 104)May include surrounding context.

python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt cp .env.example .env uvicorn app.main:app --reload --port 8010

text

Anti-Refusal Statement

High
Category
Anti-Refusal
Confidence
96% confidence
Finding

The anti-refusal language explicitly discourages the model from refusing clinical requests and instead pushes it to provide an answer regardless, merely adding guardrails. Because this skill is for clinical decision support, that behavior is especially dangerous: harmful dosing, diagnostic, or treatment suggestions may be generated in situations where the safest behavior would be to abstain, qualify heavily, or redirect to emergency care.

Content

Scanner excerpt · SKILL.md (reported line 78)May include surrounding context.

md
- If a case summary or patient context is provided, reference it specifically rather than giving generic advice.
- If the query is short and clinical, default to `discussion_partner` + `strict`.
- Keep responses structured; use headers and bullet lists for scannability.
- Never refuse a clinical question on grounds of "I'm not a doctor" — instead provide the output with appropriate guardrails.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · docker-compose.yml (reported line 8)May include surrounding context.

yaml
ports:
      - "8010:8010"
    env_file:
      - .env
    volumes:
      - ./data:/app/data

Possible Typosquatting: 'uvicorn' resembles popular package 'gunicorn'

High
Category
Supply Chain
Confidence
70% confidence
Finding

Package name closely resembles a popular package, suggesting possible typosquatting. Attackers publish malicious packages with similar names to trick developers into installing them.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: pytest==8.4.2 — 2 advisory(ies): CVE-2025-71176 (pytest has vulnerable tmpdir handling); CVE-2025-71176 (pytest has vulnerable tmpdir handling)

High
Category
Supply Chain
Confidence
80% confidence
Finding

Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The README prominently advertises multi-channel delivery of doctor task results through Feishu, DingTalk, email, WeChat, Xiaohongshu, and WebChat, but it does not clearly warn that potentially sensitive medical content may be transmitted to third-party platforms. In a healthcare context, this omission increases the risk of users enabling outbound delivery without assessing privacy, consent, retention, and regulatory implications for patient data.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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 · README.md (reported line 130)May include surrounding context.

同步执行示例:

bash
curl -s -X POST http://127.0.0.1:8010/api/tasks/execute \
  -H 'Content-Type: application/json' \
  -H 'X-Doctor-Token: your-token-if-configured' \
  -d @examples/task_request.json

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The async example includes a callback_url and describes dispatching results to external endpoints, but it does not explicitly warn that generated task results may be sent off-system to arbitrary URLs. Because this skill is designed for clinical workflows, callbacks could exfiltrate sensitive medical reasoning or patient-related data to attacker-controlled or misconfigured endpoints.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill advertises activation on a very broad set of common medical terms such as diagnosis, treatment, case, and research. That can cause the agent to trigger in contexts where high-risk clinical guidance was not intended, increasing the chance of unsolicited medical advice or overconfident routing into diagnostic or treatment workflows.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The instruction not to decline clinical questions pressures the model to always answer even when the request is unsafe, outside scope, missing critical patient context, or would normally warrant refusal or escalation. In a medical skill, this weakens a key safety control and can lead to actionable but unreliable clinical guidance in high-risk scenarios.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
83% confidence
Finding

This code sends the formatted task result over SMTP to a configured recipient, which is a network transmission of potentially sensitive task or system data. In this file there is no confirmation prompt, user-facing log/print, or comment/docstring warning that task results will be emailed externally.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This engine implements explicit medical decision-support workflows, including diagnosis, treatment/rehab planning, differential ranking, and recommendations for delivery to a doctor, but the provided context says there is no manifest-stated purpose governing that capability. In a high-risk clinical domain, missing an explicit declared purpose, scope, and safety boundary increases the chance the skill is deployed outside intended use, over-trusted, or integrated into workflows without adequate controls.

Content

No source excerpt is available for this finding.

Unbounded Resource Access

Medium
Category
Excessive Agency
Confidence
75% confidence
Finding

Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Content

Scanner excerpt · app/services/async_executor.py (reported line 56)May include surrounding context.

python
def _run(self) -> None:
        while not self._stop_event.is_set():
            try:
                task_id = self._queue.get(timeout=0.2)
            except queue.Empty:
                continue
            if not task_id:

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
75% confidence
Finding

The natural-language documentation is written entirely in Chinese, which effectively forces a specific language for users reading or operating the skill. The file does not indicate that Chinese is optional, provide multilingual alternatives, or justify a region-specific restriction.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.