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. ]]>
