Back to skill

Security audit

Victron Power System Monitor - Boat, RV and Power Systems

Security checks for vulnerabilities and agentic risk

Overview

The skill appears intended to monitor Victron systems, but it needs Review because it handles sensitive API tokens and telemetry with under-scoped guidance and overstates email delivery behavior.

Install only after reviewing the token and report-handling risks. Use a dedicated read-only Victron token stored in an environment variable or secret store, do not paste real tokens into source files, restrict report recipients and file locations, and expect to fix the email-delivery gap, pg_data bug, HTML escaping, and dependency pinning before relying on unattended daily monitoring.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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 (4)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/boat-email-report.py:205
Finding
Unescaped Victron API Data Is Inserted into the Generated HTML Report<![CDATA[ ## Vulnerability Details **File Location**: `scripts/boat-email-report.py`, lines 129-154, 187-198, and 205-208 **Vulnerability Type**: HTML injection through unescaped external data **Risk Level**: Medium ### Vulnerable Code ```python def extract_solar_data(diagnostics): """Extract solar charger data from diagnostics""" solar_data = {} for record in diagnostics: if record.get("Device") != "Solar Charger": continue code = record.get("code", "") value = record.get("formattedValue", "") if code == "PVP": # PV Power solar_data["power"] = value.replace(" W", "").strip() + " W" elif code == "YT": # Yield today solar_data["yieldToday"] = value elif code == "MCPT": # Max charge power today solar_data["maxChargePower"] = value elif code == "PVV": # PV Voltage solar_data["pvVoltage"] = value elif code == "ScI": # Charger current solar_data["chargerCurrent"] = value ``` ```python html = html.replace( "{{boat1.solar.power}}", boat1_data.get("solar", {}).get("power", "0 W") ) html = html.replace( "{{boat1.solar.yieldToday}}", boat1_data.get("solar", {}).get("yieldToday", "0 kWh") ) html = html.replace( "{{boat1.solar.maxChargePower}}", boat1_data.get("solar", {}).get("maxChargePower", "0 W") ) html = html.replace( "{{boat1.solar.pvVoltage}}", boat1_data.get("solar", {}).get("pvVoltage", "0 V") ) ``` ```python if pg_data["alarms"]["alarms"]: alarms_html = "" for alarm in pg_data["alarms"]["alarms"]: alarms_html += f'<div class="alarm-item"><strong>{alarm["name"]}</strong><br/>{alarm["attribute"]}</div>' ``` ### Technical Analysis Values returned by the Victron API, including formatted diagnostic values, inverter state, alarm names, and alarm attributes, are treated as trusted HTML. They are inserted into the report using direct string replacement and f-string ...[truncated 1911 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Escape every externally sourced string before inserting it into HTML: ```python from html import escape safe_name = escape(str(alarm.get("name", "Unknown")), quote=True) safe_attribute = escape(str(alarm.get("attribute", "Unknown")), quote=True) alarms_html += ( '<div class="alarm-item">' f'<strong>{safe_name}</strong><br/>{safe_attribute}' '</div>' ) ``` 2. Use a maintained template engine with automatic HTML escaping, such as Jinja2 with autoescape enabled, instead of repeated string replacement. 3. Validate telemetry against strict expected types: - Convert numeric measurements to `float` before formatting. - Map status fields to an allowlist of recognized values. - Reject or encode unexpected textual values. 4. Avoid inserting external values into CSS or URL contexts unless separately validated for those contexts. 5. Add tests containing payloads such as `<img src="https://example.invalid/track">` and verify that the output contains encoded text rather than interpreted markup. 6. Apply defense-in-depth sanitization before handing generated HTML to an email delivery system. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:58
Finding
Setup Documentation Encourages Storing the VRM API Token in Source Code<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 58-72; also present in `README.md`, lines 87-93 and 185-188 **Vulnerability Type**: Insecure credential storage guidance **Risk Level**: Medium ### Vulnerable Documentation ```markdown Update these variables: ```python VRM_TOKEN = "your-token-here" INSTALLATIONS = { "boat1": { "id": 000000, "name": "Titanic", "batteryInstance": 279, }, "boat2": { "id": 000001, "name": "Endeavour", "batteryInstance": 279, } } ``` ``` The README repeats this pattern: ```python # Set your token and installation IDs: VRM_TOKEN = "your-token-here" INSTALLATIONS = { "boat1": {"id": 000000, "name": "My Boat", "batteryInstance": 279} } ``` ### Technical Analysis The implementation supports reading `VRM_TOKEN` from an environment variable, which is preferable to embedding it in code. However, prominent setup instructions explicitly direct users to replace a source-code string with a live personal token. A modified script may subsequently be committed to source control, uploaded as part of a Skill package, included in backups, or shared for troubleshooting. This creates a durable credential-exposure risk. The network use of the token itself is legitimate: the implementation sends it in the `X-Authorization` header only to Victron's official HTTPS API. The vulnerability is the recommended storage method, not the authenticated API request. ### Attack Path 1. A user follows the documented configuration example and writes a live VRM token into `boat-email-report.py`. 2. The user commits the modified file, shares the project, uploads a diagnostic archive, or exposes a backup. 3. An attacker obtains the source file and extracts the token. 4. The attacker sends authenticated requests to the Victron API. 5. The attacker accesses all installations and telemetry permitted by that token until it is revoked or expires. ### Impact Assessment The attac ...[truncated 582 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove all instructions that recommend placing the token directly in Python source. 2. Require the environment variable and fail closed when it is absent: ```python VRM_TOKEN = os.environ.get("VRM_TOKEN") if not VRM_TOKEN: raise RuntimeError("VRM_TOKEN must be provided through the environment") ``` 3. Document secure configuration for scheduled execution, such as: - A permission-restricted environment file readable only by the service account - An operating-system credential store - A dedicated secret manager 4. Ensure secret files are excluded from source control and created with restrictive permissions, such as mode `0600`. 5. Instruct users to create a dedicated, read-only token limited to the installations necessary for this report. 6. Document token rotation and immediate revocation if a token is committed or shared. 7. Add automated secret scanning to the contribution workflow. ]]>

T08 · Insecure Dependencies

Note
Location
requirements.txt:4
Finding
Open-Ended Dependency Version Prevents Reproducible Installation<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt`, line 4 **Vulnerability Type**: Unbounded third-party dependency **Risk Level**: Low ### Vulnerable Code ```text # Boat Daily Check Requirements # Python 3.7+ requests>=2.28.0 ``` The setup guide installs this dependency directly: ```bash pip install -r requirements.txt ``` ### Technical Analysis The lower-bound-only requirement allows any future `requests` release that satisfies `>=2.28.0`. Consequently, two users installing the same reviewed Skill at different times may receive different dependency code. There is no evidence that `requests` is malicious, misspelled, or currently sourced from an unauthorized package index. The risk is reduced supply-chain control and reproducibility rather than a confirmed malicious dependency. Python packages execute installation and runtime code within the privileges of the invoking user. If a future qualifying package release or configured package source is compromised, the current requirement provides no version or hash constraint to prevent its installation. ### Attack Path 1. A future qualifying dependency release or the user's configured package source is compromised. 2. A user follows the documented `pip install -r requirements.txt` command. 3. Package resolution selects the compromised version because the requirement has no upper bound, exact pin, or integrity hash. 4. Package-controlled code executes during installation or when imported by the report script. 5. The code operates with the privileges and environment of the user or scheduled service, potentially including access to `VRM_TOKEN`. ### Impact Assessment Potential impact is limited by the privileges of the account performing installation or executing the report. A compromised dependency could potentially: - Read the `VRM_TOKEN` environment variable - Read or modify files accessible to the service account - Alter API requests or report contents - Send data over the network - ...[truncated 165 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin an audited dependency version or use a carefully bounded compatible range. 2. Generate a lock file containing transitive dependency versions. 3. Use package hashes, for example through `pip-compile --generate-hashes`, and install with hash enforcement. 4. Install only from a trusted, explicitly configured package index. 5. Use an isolated virtual environment under a non-privileged service account. 6. Periodically update pinned versions after vulnerability and compatibility review rather than allowing automatic adoption of every future release. ]]>

other

Note
Location
scripts/boat-email-report.py:205
Finding
Undefined Variable Causes Scheduled Monitoring to Fail Before Report Creation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/boat-email-report.py`, line 205 **Vulnerability Type**: Monitoring availability failure **Risk Level**: Low ### Vulnerable Code ```python # Handle alarms import re if pg_data["alarms"]["alarms"]: alarms_html = "" for alarm in pg_data["alarms"]["alarms"]: alarms_html += f'<div class="alarm-item"><strong>{alarm["name"]}</strong><br/>{alarm["attribute"]}</div>' ``` ### Technical Analysis `pg_data` is not defined anywhere in the script. Every execution that reaches `generate_report()` therefore raises a `NameError` while processing alarms. The exception occurs before the report is written. Because the project recommends unattended daily execution through OpenClaw cron, crontab, or systemd, the defect can cause persistent silent loss of monitoring unless job failures are actively observed. This is primarily a reliability and availability problem rather than a privilege-escalation vulnerability. It becomes security-relevant because active power-system alarms may not be delivered as expected. ### Attack Path No attacker action is required: 1. A user configures the Skill according to the documentation. 2. The user enables one of the recommended daily scheduling mechanisms. 3. The job successfully fetches telemetry from Victron. 4. `generate_report()` evaluates the undefined `pg_data` variable. 5. Python terminates with `NameError`. 6. No current HTML report is produced, and downstream email delivery cannot provide the expected status or alarm notification. An attacker who can repeatedly trigger execution cannot obtain additional privileges through this defect, but can rely on the existing failure to reduce monitoring availability. ### Impact Assessment No additional system or API privileges can be obtained directly. The scope is availability and operational awareness: - Daily reports fail to generate - Active alarms may be missed - Existing reports may become stale - Scheduled jo ...[truncated 133 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace `pg_data` with the intended data object and define how alarms from multiple installations should be rendered. 2. Refactor alarm generation to iterate explicitly over `data.items()` rather than relying on a boat-specific undeclared variable. 3. Add unit tests that invoke `generate_report()` with: - No alarms - One active alarm - Multiple installations - Missing or malformed alarm fields 4. Make scheduled failures visible through a nonzero exit status and an external failure notification. 5. Write reports atomically to a temporary file and rename them only after successful generation, preventing stale or partial output from appearing current. ]]>
Vulnerability Patterns
  • 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
  • Rogue AgentSelf-Modification, Session Persistence
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
Findings (23)

Self-Modification

High
Category
Rogue Agent
Content
## Documentation

- Update README.md for user-facing changes
- Update SKILL.md for feature additions
- Add references if introducing new API patterns
- Include examples for new features
Confidence
85% confidence
Finding
Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.

YARA rule 'backdoor_persistence': Backdoor persistence with malicious payloads (shell commands, SSH key injection, hidden root users) [malware]

High
Category
YARA Match
Content
atus.json` — Structured data
- `out/boat-status.csv` — Spreadsheet-friendly export

## Step 6: Automate (Optional)

### With OpenClaw
```bash
openclaw cron add -j '{
  "name": "boat-daily-check",
  "schedule": {"kind": "cron", "expr": "0 7 * * *"},
  "payload": {
    "kind": "agentTurn",
    "message": "python3 /path/to/boat-email-report.py"
  },
  "sessionTarget": "isolated"
}'
```

### With crontab
```bash
0 7 * * * cd /path/to/boat-daily-check && python3 scripts/boat-email-report.py >> cron.log 2>&1
```

### With systemd Timer
Create `/etc/systemd/system/boat-daily-check.service`:
```ini
[Unit]
Description=Boat Daily Check - Victron Monitoring
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/bin/python3 /path/to/boat-daily-check/scripts/boat-email-report.py
StandardOutput=journal
StandardError=journal
User=youruser

[Install]
WantedBy=multi-user.target
```

## Troubleshooting

### "ModuleNotFoundError: requests"
```bash
pip install requests
```

### "Child 'BatterySum
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The declared purpose says the skill monitors Victron systems and sends beautiful daily email reports using the VRM API. The code substantially matches the monitoring/data-fetching and HTML report generation portions: it calls VRM API endpoints for battery, diagnostics, and alarms across multiple configured installations and fills an HTML template. However, there is no email delivery logic at all—no SMTP, mail API, or handoff except saving HTML to `/home/jeanclaude/.../boat-daily-email.html` and printing JSON. That is a material mismatch because email sending is a primary declared capability. Additionally, the code is narrowly configured with placeholder/hardcoded installations and a fixed output path, which is more limited than the description implies. Finally, the `generate_report` function references `pg_data` for alarms, which is undefined, indicating the active-alarms section may fail and reducing fidelity to the declared report behavior.

Credential Access

High
Category
Privilege Escalation
Content
### 1. Get Your Victron VRM API Token

1. Visit https://vrm.victronenergy.com/access-tokens
2. Create a new access token
3. Copy the token (you'll need this for configuration)

### 2. Find Your Installation IDs
Confidence
93% confidence
Finding
The documentation instructs users to place a long-lived VRM access token directly into a Python script, which is an insecure secret-handling pattern. Hardcoded API tokens are easily leaked through source control, backups, logs, screenshots, or local file exposure, and compromise would grant access to sensitive monitoring data across installations.

Credential Access

High
Category
Privilege Escalation
Content
## Useful Links

- **VRM API Docs**: https://vrm-api-docs.victronenergy.com/
- **Access Tokens**: https://vrm.victronenergy.com/access-tokens
- **Your Installations**: https://vrm.victronenergy.com/
- **Community**: https://community.victronenergy.com/
- **Reference Implementation**: https://github.com/dirkjanfaber/victron-vrm-api
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The guide instructs users to create and copy a VRM API token but does not warn them to treat it as a secret, limit its scope, avoid sharing it, or store it securely. In onboarding documentation, omission of credential-handling guidance materially increases the chance of accidental leakage through screenshots, shell history, shared notes, or source control.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
The configuration example explicitly shows a fallback hardcoded token value in source code, which normalizes embedding live credentials directly in a script. This is dangerous because users commonly replace placeholders with real secrets and then save, back up, email, or commit the file, causing credential exposure and unauthorized access to VRM data.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The README promotes collecting Victron telemetry and emailing detailed system status but does not warn that these reports may reveal sensitive operational information such as installation identifiers, device state, uptime, and alarm conditions. In the context of remotely monitored boats, RVs, or off-grid assets, sharing this data carelessly can expose location-adjacent patterns, asset status, and targeting opportunities to unintended recipients.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The setup instructions show direct API-token handling without any warning that the token is a secret requiring secure storage and careful shell hygiene. Users may copy secrets into shell history, screenshots, shared terminals, or docs, which could allow an attacker to access VRM telemetry for all associated installations.

Session Persistence

Medium
Category
Rogue Agent
Content
### 1. Get API Token
```bash
# Visit: https://vrm.victronenergy.com/access-tokens
# Create a new token, copy it
export VRM_TOKEN="your-token-here"
```
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.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
The configuration example explicitly encourages embedding the VRM API token directly in the script, which materially increases the chance of accidental disclosure through source control, backups, support bundles, or local file compromise. Because the token grants access to remote power-system telemetry, exposure could leak sensitive monitoring data across one or more installations.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill documentation describes behavior that requires network access, reading configuration/secrets, and writing output files, but it declares no explicit tool scope or permissions. In an agent ecosystem, missing scope boundaries can cause overbroad execution privileges, reduce reviewability, and make it easier for a skill to access sensitive resources beyond what users expect.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
The skill encourages emailing operational telemetry and alarm data without warning users that these messages may expose sensitive information such as device names, installation identifiers, presence patterns, or power-system health. Email is often insecurely retained, forwarded, or delivered to the wrong recipient, so transmitting monitoring data without privacy guidance increases the risk of unintended disclosure.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The module docstring explicitly describes a 'Boat Daily Email Report Generator' that 'generates a formatted HTML email'. In the actual implementation, the script fetches data, renders HTML, and saves it to a filesystem path, but there is no mail delivery logic or SMTP/API call anywhere in the file.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This code accesses a sensitive credential via the VRM_TOKEN environment variable and uses it to authenticate outbound requests to a remote API. While comments explain configuration for developers, there is no runtime confirmation or user-facing warning that credentials and boat telemetry will be sent to Victron's service.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The manifest promises daily email reporting with formatted HTML emails, but this file only renders HTML and writes it to '/home/jeanclaude/.openclaw/workspace/skills/boat-daily-check/out/boat-daily-email.html'. No code in this file sends email or integrates with a mail transport, so the delivered behavior is narrower than the advertised capability.

Missing User Warnings

Low
Confidence
82% confidence
Finding
The email-recipient configuration is documented without warning that generated reports may contain sensitive operational details about monitored systems. Misconfiguration or overbroad recipient lists could expose alarm states, hardware details, and power availability information to unintended parties.

Missing User Warnings

Low
Confidence
89% confidence
Finding
This markdown file includes curl examples with an `X-Authorization: Token YOUR_TOKEN` header and shows responses containing device metadata such as serial number and firmware version. Under the markdown-file criteria for missing user warnings, the document should disclose that these requests use sensitive credentials and may expose private system/device information.

Missing User Warnings

Low
Confidence
89% confidence
Finding
This markdown file shows how to authenticate with a VRM access token and later includes queries that return user and installation information, but it does not warn readers to keep tokens secret or treat returned account/site data as sensitive. Under the markdown-specific warning rule, examples that affect privacy or expose credentials should include a brief user warning.

Unpinned Dependencies

Low
Category
Supply Chain
Content
# Boat Daily Check Requirements
# Python 3.7+

requests>=2.28.0
Confidence
98% confidence
Finding
The dependency is specified as `requests>=2.28.0`, which allows any future release to be installed and makes builds non-reproducible. This increases supply-chain risk and can unexpectedly introduce vulnerable or breaking versions without review, especially in a skill that performs network access to an external API and likely handles credentials or telemetry.

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
95% confidence
Finding
The manifest does not pin `requests`, so it is impossible to verify whether deployments will receive a version affected by known advisories. Because this skill integrates with the Victron VRM API and sends email reports, it likely processes authentication material and sensitive operational data, so installing a vulnerable `requests` release could expose credentials, leak data, or weaken TLS/request-handling protections.

Missing User Warnings

Low
Confidence
80% confidence
Finding
The script writes generated telemetry-derived HTML to a hard-coded absolute path under a specific user's home directory without configurability or confirmation. In agent environments, fixed-path writes can expose sensitive operational data to unintended locations, break tenant isolation assumptions, or overwrite files in predictable directories if the environment matches the expected path.

Natural-Language Policy Violations

Low
Confidence
92% confidence
Finding
The template sets the document language to English via `lang="en"`, which is a natural-language locale choice embedded in the file. There is no indication that users can opt into another language or that the English-only setting is required for a region-specific or compliance reason.

Static analysis

No suspicious patterns detected.