T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/frisbee_chart.py:297
- Finding
- Unescaped Garmin and User-Controlled Values Permit Script Injection in Generated Dashboards<![CDATA[ ## Vulnerability Details **File Location**: `scripts/frisbee_chart.py:297-315, 331-357, 462-469`; `scripts/frisbee_compare.py:370-394, 439-455` **Vulnerability Type**: Persistent/local HTML and JavaScript injection **Risk Level**: High ### Vulnerable Code ```python def _build_activity_table_html(activities): if not activities: return "<p style='text-align:center;opacity:0.6'>No activities found in this date range.</p>" rows = "" for i, a in enumerate(activities, 1): dur = int((a.get("duration_seconds") or 0) // 60) hrr_count = len(a.get("hrr", [])) hrr_note = f"✓ {hrr_count} pts" if hrr_count else "—" rows += f""" <tr> <td>Game {i}</td> <td>{a.get('date', '—')}</td> <td>{a.get('activity_name', '—')}</td> <td>{a.get('activity_type', '—')}</td> <td>{dur} min</td> <td>{a.get('avg_hr') or '—'}</td> <td>{a.get('max_hr') or '—'}</td> <td>{a.get('calories') or '—'}</td> <td>{hrr_note}</td> </tr>""" ``` ```python def generate_tournament_html(data): name = data.get("name", "Tournament") start = data.get("start_date", "") end = data.get("end_date", "") title = f"{name} | {start} → {end}" ... charts_json = json.dumps({"stats": stats, "charts": charts}) return f"""<!DOCTYPE html> <html lang="en"> <head> ... <title>{title}</title> <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script> ... <body> <div class="container"> <h1>🥏 {name}</h1> <div class="subtitle">{start} → {end} · Generated {generated}</div> ... {activity_table} ``` The comparison dashboard contains the same class of issue: ```python table_rows = "" for a in all_relevant: table_rows += f"""<tr> <td>{a['date']}</td> <td>{a['name'][:20]}</td> <td>{a.get('category','—')}</td> <td>{a['dur ...[truncated 2149 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Apply `html.escape(value, quote=True)` to every value inserted into HTML markup, including activity names, activity types, dates, tournament names, and titles. 2. Do not place ordinary `json.dumps()` output directly in an executable script block. 3. Store serialized data in a `<script type="application/json">` element and read it using `textContent`, while escaping `<`, `>`, `&`, U+2028, and U+2029. 4. Alternatively, serialize data to a separate local JSON file and parse it as data rather than executable source. 5. Add a restrictive Content Security Policy that disallows inline event handlers and limits outbound connections. 6. Add regression tests using values containing `<script>`, `</script>`, `<img onerror=...>`, quotes, ampersands, and Unicode line separators. ]]>
