Back to skill

Security audit

Intelligent Triage Symptom Analysis

Security checks for vulnerabilities and agentic risk

Overview

This is a medical-triage skill with real code, but it needs review because it stores sensitive symptom history by default and makes safety and privacy claims that the implementation does not support.

Review this skill carefully before installing or using it with real patient information. Treat it as a limited rule-based demo unless clinically validated, avoid putting names, emails, patient IDs, or real medical details in user_id or symptoms, disable history with --no-save-history when possible, and do not run the auto-evolution daemon. Do not rely on its output for emergency or clinical decisions without qualified human review.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/triage.py:135
Finding
Path Traversal in Symptom History File Handling<![CDATA[ ## Vulnerability Details **File Location**: `scripts/triage.py:135-154` **Vulnerability Type**: Path traversal and arbitrary JSON file access **Risk Level**: High ### Vulnerable Code ```python class SymptomHistoryManager: def __init__(self, user_id: str): self.user_id = user_id self.history_dir = os.path.expanduser("~/.openclaw/symptom_history") self.history_file = os.path.join(self.history_dir, f"{user_id}.json") os.makedirs(self.history_dir, exist_ok=True) def save_assessment(self, assessment: Dict[str, Any]): history = self.load_history() history.append({ 'timestamp': datetime.now().isoformat(), 'assessment': assessment }) with open(self.history_file, 'w', encoding='utf-8') as f: json.dump(history[-50:], f, ensure_ascii=False, indent=2) def load_history(self) -> List[Dict[str, Any]]: if os.path.exists(self.history_file): try: with open(self.history_file, 'r', encoding='utf-8') as f: return json.load(f) except: pass return [] ``` ### Technical Analysis The caller-controlled `user_id` is inserted directly into a filesystem path without validation, normalization, or containment checking. A value containing traversal components such as `../` can cause the resolved history path to escape `~/.openclaw/symptom_history`. A value beginning with an absolute path can also cause `os.path.join()` to discard the intended base directory. The `.json` suffix limits target names but does not prevent access to JSON configuration or data files. The `--history` command exposes the contents of a selected JSON file through `load_history()`. During an assessment, `save_assessment()` can overwrite a selected file if the existing JSON is compatible with the expected list structure. ### Attack Path 1. An attacker who can invoke the CLI or API supplies a cr ...[truncated 1041 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not use a raw external identifier as a filename. Derive a fixed-length filename using a keyed hash or a cryptographic digest. 2. If identifiers must remain readable, enforce a strict allowlist such as `^[A-Za-z0-9_-]{1,64}$`. 3. Reject absolute paths, path separators, `.` components, and `..` components. 4. Resolve both the base directory and candidate path, then verify containment: ```python from pathlib import Path import hashlib base = Path("~/.openclaw/symptom_history").expanduser().resolve() safe_id = hashlib.sha256(user_id.encode("utf-8")).hexdigest() candidate = (base / f"{safe_id}.json").resolve() if base not in candidate.parents: raise ValueError("Invalid user identifier") ``` 5. Open files using restrictive permissions and avoid following symbolic links where supported. 6. Add tests covering `../`, nested traversal, absolute paths, path separators, symlinks, empty identifiers, and unusually long identifiers. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/triage.py:143
Finding
Plaintext Storage of Sensitive Medical Assessments by Default<![CDATA[ ## Vulnerability Details **File Location**: `scripts/triage.py:143-149`, `scripts/triage.py:440-442`, `scripts/triage.py:454-462`, and `scripts/triage.py:472-486` **Vulnerability Type**: Unencrypted persistent storage of protected medical information **Risk Level**: High ### Vulnerable Code ```python def save_assessment(self, assessment: Dict[str, Any]): history = self.load_history() history.append({ 'timestamp': datetime.now().isoformat(), 'assessment': assessment }) with open(self.history_file, 'w', encoding='utf-8') as f: json.dump(history[-50:], f, ensure_ascii=False, indent=2) ``` ```python def process(self, symptoms: str, age: int = None, gender: str = None, vital_signs: Dict = None, duration: str = None, user_id: str = "", save_history: bool = True) -> Dict[str, Any]: ``` ```python if trial_remaining > 0: self.trial.use_trial(user_id) analysis = self.analyze(symptoms, age, gender, vital_signs, duration) if save_history: self.history_manager = SymptomHistoryManager(user_id) self.history_manager.save_assessment(analysis) ``` ```python analysis = self.analyze(symptoms, age, gender, vital_signs, duration) if save_history: self.history_manager = SymptomHistoryManager(user_id) self.history_manager.save_assessment(analysis) ``` The stored assessment includes the raw input: ```python 'input': {'symptoms': symptoms, 'age': age, 'gender': gender, 'vital_signs': vital_signs, 'duration': duration}, ``` ### Technical Analysis History storage is enabled by default through `save_history=True`. Each successful non-demo assessment writes symptoms, age, gender, vital signs, duration, derived diagnoses, and recommendations to plaintext JSON. The implementation does not: - Encrypt the medical information at rest. - Explicitly set restrictive file or directory permissions. - Apply a time-based retention policy. - Obtain affirmative consen ...[truncated 1658 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable history storage by default and require explicit, informed opt-in. 2. Store only fields necessary for the user-selected history feature. 3. Encrypt stored assessments using authenticated encryption and a key held separately from the data. 4. Create history directories and files with restrictive permissions, such as directory mode `0700` and file mode `0600`. 5. Implement and enforce the documented retention interval rather than retaining entries indefinitely. 6. Provide deletion APIs for individual assessments, users, and all local medical data. 7. Avoid including raw symptoms and vital signs in history unless explicitly required. 8. Clearly disclose what is stored, where it is stored, how long it remains, and how it can be deleted. 9. Correct `SECURITY.md`, `FAQ.md`, and `README.md` so their privacy statements match actual behavior. 10. Add tests confirming that history is not written without opt-in and that retention and deletion controls operate correctly. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/triage.py:122
Finding
Raw User Identifiers Are Stored Locally and Sent to the Billing Provider<![CDATA[ ## Vulnerability Details **File Location**: `scripts/triage.py:81-96` and `scripts/triage.py:103-125` **Vulnerability Type**: Insufficient pseudonymization and misleading privacy documentation **Risk Level**: Medium ### Vulnerable Code ```python def get_trial_remaining(self, user_id: str) -> int: if not user_id: return 0 data = self._load_trial_data() user_data = data.get(user_id, {}) used_calls = user_data.get('used_calls', 0) return max(0, self.max_free_calls - used_calls) def use_trial(self, user_id: str) -> bool: if not user_id: return False data = self._load_trial_data() if user_id not in data: data[user_id] = {'used_calls': 0, 'first_use': datetime.now().isoformat()} data[user_id]['used_calls'] += 1 data[user_id]['last_use'] = datetime.now().isoformat() self._save_trial_data(data) return True ``` ```python class SkillPayBilling: def __init__(self, api_key: str = API_KEY, skill_id: str = SKILL_ID): self.api_key = api_key self.skill_id = skill_id self.headers = {'X-API-Key': api_key, 'Content-Type': 'application/json'} ``` ```python def charge_user(self, user_id: str) -> Dict[str, Any]: result = self._make_request('/charge', method='POST', data={ 'user_id': user_id, 'skill_id': self.skill_id, 'amount': 0, }) ``` ### Technical Analysis The user identifier is used verbatim as a key in the local trial-state JSON. It is also posted verbatim to the external SkillPay billing endpoint after the trial is exhausted. The billing request is an explicitly declared feature, uses HTTPS, and does not include symptoms or vital signs. Therefore, the audit did not identify covert medical-data exfiltration. However, no hashing or pseudonymization occurs despite documentation stating that the stored user ID is hashed. If callers use an email address, patient number, account name, or other real-world identifier, that information becomes linkabl ...[truncated 1142 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use a random, billing-specific pseudonymous identifier instead of a real-world user identifier. 2. If deterministic mapping is required, use an HMAC with a protected application secret rather than an unkeyed hash. 3. Separate local patient identifiers from identifiers disclosed to the billing provider. 4. Validate that the billing API cannot function with a less identifying account or transaction token. 5. Obtain informed consent and document the exact fields, recipient, purpose, and transmission frequency. 6. Update documentation to remove the incorrect claim that local identifiers are already hashed. 7. Define deletion and retention policies for trial-state records. 8. Avoid passing emails, patient record numbers, names, or other directly identifying values as `user_id`. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/triage.py:529
Finding
Billing API Key Can Be Exposed Through Command-Line Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/triage.py:529-531` **Vulnerability Type**: Secret exposure through process arguments and shell history **Risk Level**: Medium ### Vulnerable Code ```python parser.add_argument('--user-id', '-u', help='用户ID / User ID') parser.add_argument('--api-key', '-k', default=API_KEY, help='API Key') parser.add_argument('--skill-id', default=SKILL_ID, help='Skill ID') ``` ### Technical Analysis The application permits the billing API key to be supplied directly on the command line. Command-line arguments commonly appear in: - Shell command history. - Process listings. - Process-monitoring and observability systems. - Debugging and crash reports. - Job scheduler configuration. - Terminal session recordings. Environment variables are also supported, so accepting the secret as a command-line argument is unnecessary and increases exposure. ### Attack Path 1. An operator invokes the script using: `python scripts/triage.py --api-key <secret> ...`. 2. The shell records the complete command in its history. 3. While the process is active, a local process-monitoring user or system may inspect its arguments. 4. Logging or automation infrastructure may retain the command. 5. An attacker who gains access to one of these sources obtains the billing API key and can attempt unauthorized billing API operations within that key's permissions. ### Impact Assessment The maximum impact is determined by the permissions granted to the SkillPay API key. Potential consequences include: - Unauthorized requests to the billing provider. - Fraudulent or disruptive billing activity. - Exposure of billing-account metadata. - Service denial if the key is revoked or rate-limited after abuse. This does not itself grant broader host privileges, but it can compromise the external billing account represented by the key. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the `--api-key` command-line option. 2. Read the key from a protected secret manager, file descriptor, or environment variable supplied by a trusted runtime. 3. If interactive entry is required, use `getpass.getpass()` so the secret is not echoed. 4. Ensure configuration files containing secrets use restrictive filesystem permissions. 5. Redact secrets from exceptions, logs, diagnostics, and telemetry. 6. Document secure key rotation and revocation procedures. 7. Restrict the billing key to the minimum API permissions and transaction scope required. ]]>

other

Error
Location
scripts/triage.py:326
Finding
Safety-Critical Medical Recommendations Rely on Unvalidated Keyword Matching<![CDATA[ ## Vulnerability Details **File Location**: `scripts/triage.py:233-245`, `scripts/triage.py:326-349`, and `scripts/triage.py:379-407` **Vulnerability Type**: Unsafe medical decision logic and unsupported capability claims **Risk Level**: High ### Vulnerable Code ```python class SymptomAnalyzer: RED_FLAGS = { 'cardiac': ['胸痛', '胸闷', '心悸', '呼吸困难', '气短', 'chest pain', 'chest tightness', 'palpitations', 'shortness of breath', 'dyspnea'], 'neurological': ['昏迷', '抽搐', '偏瘫', '失语', '剧烈头痛', '意识模糊', 'coma', 'seizure', 'paralysis', 'aphasia', 'severe headache', 'confusion'], 'respiratory': ['窒息', '喘鸣', '血氧低', 'choking', 'wheezing', 'low oxygen'], 'trauma': ['大出血', '严重外伤', '骨折', '头部外伤', 'severe bleeding', 'severe trauma', 'fracture', 'head injury'], 'shock': ['面色苍白', '冷汗', '血压低', 'pale', 'cold sweat', 'low blood pressure'], } ``` ```python for category, keywords in self.RED_FLAGS.items(): for keyword in keywords: if keyword in symptoms or keyword in symptoms_lower: red_flags.append({'category': category, 'symptom': keyword, 'priority': 'CRITICAL'}) break ``` ```python def calculate_triage_level(self, symptoms: List[Dict], red_flags: List[Dict], age: int = None, vital_signs: Dict = None) -> int: if any(rf['priority'] == 'CRITICAL' for rf in red_flags): return 1 critical_systems = ['cardiac', 'neurological', 'respiratory'] if any(rf['category'] in critical_systems for rf in red_flags): return 2 max_severity = max([s['severity'] for s in symptoms], default=5) age_factor = 1 if age and (age < 5 or age > 65) else 0 if max_severity >= 7 or age_factor > 0: return 3 if max_severity >= 4: return 4 return 5 ``` ```python if triage_level == 1: recommendations.extend([ '立即呼叫急救/Call emergency services immedi ...[truncated 2668 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove unsupported accuracy, machine-learning, disease-coverage, and clinical-validation claims until independently demonstrated. 2. Clearly label the implementation as a limited rule-based demonstration and prohibit autonomous clinical use. 3. Require qualified clinical review before using results for patient-facing decisions. 4. Add negation detection, temporal context, symptom severity, onset, duration, age-specific ranges, and clinically appropriate vital-sign interpretation. 5. Implement all documented high-risk presentations, including mental-health emergencies, allergic reactions, sepsis indicators, and other applicable warning signs. 6. Redesign priority assignment so not every keyword automatically becomes critical. 7. Validate the algorithm prospectively against representative, independently labeled clinical cases. 8. Measure and publish sensitivity, specificity, false-negative rates, calibration, and performance across demographic groups. 9. Add tests for negation, misspellings, mixed languages, historical symptoms, ambiguous context, pediatric cases, geriatric cases, and adversarial phrasing. 10. Use conservative failure behavior: when input cannot be interpreted reliably, state the uncertainty and direct the user to an appropriate healthcare professional rather than presenting unsupported diagnostic probabilities. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (26)

Credential Access

High
Category
Privilege Escalation
Content
3. Copy the environment variables file and configure:
```bash
cp .env.example .env
# Edit .env with your actual API keys
```
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
3. Copy the environment variables file and configure:
```bash
cp .env.example .env
# Edit .env with your actual API keys
```

## Environment Variables Configuration
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Self-Modification

High
Category
Rogue Agent
Content
echo "🧬 Auto-Evolution Started: $(date)" > $LOG_FILE
while true; do
    echo "[$(date)] Evolving..." >> $LOG_FILE
    cd $SKILL_PATH && python3 scripts/self_evolve.py >> $LOG_FILE 2>&1
    sleep 1800
done
Confidence
98% confidence
Finding
The daemon repeatedly runs `scripts/self_evolve.py`, indicating self-modifying or self-updating behavior. Self-evolution is high risk because it can alter the skill's behavior outside normal review, create persistence, and amplify any existing compromise or unsafe logic over time; the infinite loop makes this especially dangerous in an agent skill environment.

Missing User Warnings

High
Confidence
97% confidence
Finding
This file provides specific emergency triage levels, response times, and red-flag symptom guidance but does not warn users that the content is informational only and not a substitute for professional medical assessment or emergency services. In a safety-critical medical context, users may rely on the document for self-triage or delay urgent care, which can lead to serious injury or death.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The FAQ makes strong safety and performance claims such as 'Never miss life-threatening conditions' and high sensitivity/accuracy figures without clear qualification, uncertainty bounds, or failure warnings adjacent to the claim. In a medical triage context, users may over-trust the tool and delay urgent care, creating a meaningful risk of physical harm if the system is wrong.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The FAQ encourages users to provide detailed symptom descriptions and vital signs but does not present a nearby privacy warning about the sensitivity of that information or the risks of entering personal health details. Even though the file later claims local processing and no medical data transmission, the omission can mislead users about privacy expectations at the point of collection in a healthcare-related workflow.

Session Persistence

Medium
Category
Rogue Agent
Content
```

### Permission denied errors
Create the required directory:
```bash
mkdir -p ~/.openclaw/skill_trial
chmod 755 ~/.openclaw
Confidence
78% confidence
Finding
The FAQ instructs users to create a persistent directory under ~/.openclaw/skill_trial, confirming local session or usage-state persistence. In context this aligns with the stated storage of free-trial usage counts, so it is not covert, but persistence still has security and privacy implications because local artifacts can reveal usage history or identifiers on shared systems.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
Create the required directory:
```bash
mkdir -p ~/.openclaw/skill_trial
chmod 755 ~/.openclaw
```

## Compliance Questions
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
Create the required directory:
```bash
mkdir -p ~/.openclaw/skill_trial
chmod 755 ~/.openclaw
```

## Compliance Questions
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Natural-Language Policy Violations

Medium
Confidence
80% confidence
Finding
Multiple examples use Chinese symptom descriptions, and some outputs are bilingual or Chinese-only, such as the free-call status message. The file does not explicitly state that the skill supports multiple languages by user choice or that it is intentionally limited to a Chinese-language workflow, which can violate language/locale policy expectations.

Intent-Code Divergence

Medium
Confidence
90% confidence
Finding
Lines L031-L035 explicitly state there are no external dependencies and only the Python standard library is used. Elsewhere, the documentation describes a free-trial quota, post-trial billing through skillpay.me, and environment variables for external API credentials, which contradicts the impression that the skill is entirely self-contained and dependency-free.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The documentation encourages submission of symptom descriptions, age, gender, vital signs, duration, and user identifiers, and references external billing/API-backed operation, but it does not clearly disclose storage, transmission, retention, or third-party processing of this potentially sensitive health data. In a medical-triage context, this omission is more serious because users may unknowingly transmit health-related personal information to remote services.

Session Persistence

Medium
Category
Rogue Agent
Content
### Permission Denied
If you see permission errors for `~/.openclaw/`:
```bash
mkdir -p ~/.openclaw/skill_trial
chmod 755 ~/.openclaw
```
Confidence
74% confidence
Finding
The documentation instructs users to create a persistent directory under ~/.openclaw for trial state, implying local session/trial persistence without explaining what is stored there or how it is protected. In the context of a health-related skill that also handles user IDs and symptoms, undocumented persistent local state can expose metadata, usage history, or identifiers to other local users or backups.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The sample symptom input and all printed user-facing output in the usage example are in Chinese, while the rest of the README is in English. This creates a natural-language locale constraint without opt-in or explanation, which can conflict with organizational language-choice policies.

Session Persistence

Medium
Category
Rogue Agent
Content
- **Retention**: Until user deletes the file or uninstalls the skill

### File System Access
- **Purpose**: Read/write trial tracking data
- **Scope**: User's home directory only (`~/.openclaw/`)
- **No access** to: System files, other applications' data, sensitive directories
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
98% confidence
Finding
The document presents conflicting free-trial terms, including 10 free calls in some sections and 200 free calls in others. In a paid medical triage skill, inconsistent billing and entitlement information can mislead users, create billing disputes, and undermine informed consent around service use.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill advertises automatic symptom history tracking for medical data but does not provide a clear privacy warning near that feature about retention, sensitivity, access controls, or consent implications. Because the data involves health symptoms and longitudinal history, inadequate disclosure can expose users to serious privacy harm and regulatory risk if sensitive records are stored unexpectedly.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The script launches an infinite background-style automation loop that repeatedly executes code every 30 minutes without any user confirmation, gating, or visible consent mechanism. In a skill context, unattended recurring execution increases the chance of unauthorized actions, persistence, and unnoticed harmful changes, especially because the invoked code is a self-evolution script.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The top-level docstring claims 'AI-powered medical triage with NLP and machine learning', which implies substantive ML/NLP analysis. In the actual code, symptom extraction and triage are implemented with fixed keyword lists and simple conditional logic, while additional code handles billing and local history persistence rather than AI/ML behavior.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The billing flow sends a user identifier to a remote third-party endpoint without any explicit user-facing notice in this execution path. Although the transport uses HTTPS, undisclosed transmission of identifiers from a health-related skill is a privacy concern because it links user activity and potentially medical-service usage to an external billing provider.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The skill stores symptom assessments, which can contain highly sensitive medical data, as plaintext JSON under the user's home directory by default. In a medical-triage context, silent local persistence increases privacy risk from other local users, backups, endpoint compromise, or accidental disclosure, especially because retention occurs automatically unless the user opts out.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The analyzer initializes self.lang to 'zh' and falls back to Chinese when an unsupported or unspecified language is provided. This enforces a specific locale by default rather than prompting for or preserving user preference, which matches the stated language/locale policy concern.

Natural-Language Policy Violations

Low
Confidence
90% confidence
Finding
The changelog entries from L5 to L8 are written in Chinese with no indication that the skill is region-specific or that users can choose their preferred language. This can violate language/locale policy when documentation implicitly forces a specific language without opt-in.

Intent-Code Divergence

Low
Confidence
96% confidence
Finding
The skill metadata says version 1.3.0 while the body prominently states version 1.1.0, creating ambiguity about which release users are evaluating. For a healthcare-related skill, version confusion can hide what features, safeguards, or fixes are actually present, weakening traceability and trust.

Natural-Language Policy Violations

Low
Confidence
83% confidence
Finding
The file’s natural-language strings and docstrings are written in Chinese, including the title and class/method descriptions, with no indication that users can choose another language. This can violate a language/locale policy when a skill implicitly constrains interaction language without documenting opt-in or regional justification.

Static analysis

No suspicious patterns detected.