T09 · Insecure Skill Coding Practices
Warning
- Location
- weather_digest.py:135
- Finding
- Unescaped Dynamic Data in Generated HTML<![CDATA[ ## Vulnerability Details **File Location**: `weather_digest.py:135-151, 211-221`; mirrored in `weather digest/weather_digest.py:135-151, 211-221` **Vulnerability Type**: HTML injection through unescaped configuration and remote API data **Risk Level**: Medium ### Vulnerable Code ```python def format_alerts_html(alerts: list) -> str: if not alerts: return "<div class=\"no-alerts\">No active alerts</div>" blocks = [] for alert in alerts: event = alert.get("event") or "Alert" severity = alert.get("severity") or "Unknown" expiry = _format_expiration(alert.get("expires")) headline = alert.get("headline") or "Details forthcoming" instructions = _trim_text(alert.get("instructions"), limit=220) expiry_html = f"<div class=\"expires\">Expires {expiry}</div>" if expiry else "" instructions_html = ( f"<div class=\"instructions\">{instructions}</div>" if instructions else "" ) blocks.append( f"<div class=\"alert\"><div class=\"alert-title\"><strong>{event}</strong>" f"<span class=\"pill\">{severity}</span></div>{expiry_html}" f"<div class=\"headline\">{headline}</div>{instructions_html}</div>" ) return "".join(blocks) ``` ```python def build_html(reports: list[dict], theme: str = "midnight") -> str: today = dt.datetime.now().strftime("%A, %B %d %Y") cards = [] for report in reports: city_meta = "" if report.get("city") and report.get("state"): city_meta = f"<div class=\"subhead\">Nearest location: {report['city']}, {report['state']}</div>" summary_li = "".join( f"<li>{line.replace('**', '')}</li>" for line in report["summary_lines"] ) alerts_html = format_alerts_html(report["alerts"]) cards.append( f"<section class=\"card\"><h2>{report['display_name']}</h2>{city_meta}" f"<h3>Outlook</h3><ul>{summary_li}</ul ...[truncated 2617 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Escape every dynamic value before inserting it into HTML: ```python from html import escape safe_event = escape(str(event), quote=True) safe_severity = escape(str(severity), quote=True) safe_expiry = escape(str(expiry), quote=True) safe_headline = escape(str(headline), quote=True) safe_instructions = escape(str(instructions), quote=True) ``` 2. Apply equivalent escaping to `display_name`, `city`, `state`, and every forecast summary: ```python display_name = escape(str(report["display_name"]), quote=True) city = escape(str(report.get("city") or ""), quote=True) state = escape(str(report.get("state") or ""), quote=True) summary_li = "".join( f"<li>{escape(str(line.replace('**', '')), quote=True)}</li>" for line in report["summary_lines"] ) ``` 3. Prefer a template engine with automatic escaping enabled instead of constructing HTML through f-strings. 4. Validate the configuration schema and enforce expected types and reasonable length limits for names and coordinates. 5. Add regression tests covering `<`, `>`, `&`, quotes, malformed tags, event-handler attributes, and script-like payloads. 6. Apply the same correction to both copies of `weather_digest.py` or remove the duplicate implementation to prevent future security fixes from diverging. ]]>
