T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/generate_html_report.py:87
- Finding
- Stored HTML and SVG Injection in Generated Payroll Reports<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_html_report.py:50-52, 87-120, 132-143, 152-159, 173, 241-246` **Related Data Flow**: `scripts/cross_validate.py:154, 169, 184-193` **Vulnerability Type**: Stored HTML/SVG injection caused by missing output encoding **Risk Level**: High ### Vulnerable Code The report generator inserts audit item names directly into SVG markup: ```python # Short-name label short_name = item[:4] if item else f"Item {i+1}" svg_parts.append( f'<text x="{x + bar_width}" y="{base_y + 16}" font-size="10" fill="#888" text-anchor="middle">{short_name}</text>' ) ``` It also inserts audit item names, summaries, details, priorities, and count values directly into HTML: ```python def generate_audit_cards(audit_results): """Generate audit-item status cards.""" cards = [] for r in audit_results: status = r.get("status", "unknown") status_icon = {"pass": "✅", "warning": "⚠️", "error": "❌", "unknown": "❓"}.get(status, "❓") priority = r.get("priority", "P1") priority_class = f"priority-{priority.lower().replace('(', '').replace(')', '')}" details_html = "" if r.get("details"): details_html = "<ul>" + "".join(f"<li>{d}</li>" for d in r["details"]) + "</ul>" card = f'''<details class="audit-card {priority_class}"> <summary> <div class="audit-card-header"> <span class="audit-card-title">{status_icon} {r.get("audit_item", "Unknown")}</span> <span class="badge badge-{"ok" if status == "pass" else "warn" if status == "warning" else "high"}">{status.upper()}</span> </div> </summary> <div class="audit-card-body"> <p>{r.get("summary", "")}</p> {details_html} {f"<p>Statistics: {r['counts']['total_unique']} total, {r['counts']['in_both']} matched</p>" if "counts" in r else ""} </div> </details>''' cards.append(card) return '\n'.join(cards) ``` Material names, risk descriptions, and processing-log values are similarly interpolated w ...[truncated 4592 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Escape every untrusted value before placing it in HTML or SVG: ```python from html import escape def html_text(value): return escape(str(value), quote=True) ``` Apply this function to audit item names, summaries, details, material names, risk fields, log entries, month, region, employee identifiers, and compared values. 2. Use contextual escaping: - Escape element text as HTML/XML text. - Escape attribute values with quote escaping enabled. - Do not construct CSS class names from unrestricted input. 3. Strictly allowlist enumerated fields: ```python ALLOWED_STATUS = {"pass", "warning", "error", "unknown"} ALLOWED_PRIORITY = {"P0", "P1", "P2"} ALLOWED_REGION = {"domestic", "overseas", "all"} ``` Reject or safely normalize values outside these sets. 4. Use a template engine with automatic HTML escaping enabled rather than assembling markup through formatted strings. 5. If rich text is required, sanitize it with a strict allowlist that rejects: - `script`, `iframe`, `object`, `embed`, and active SVG elements - Event-handler attributes such as `onerror` and `onclick` - `javascript:` and unsafe `data:` URLs - External resource-loading elements 6. Escape Markdown table delimiters, line breaks, and embedded HTML in `cross_validate.py`, especially for employee identifiers and imported CSV values. 7. Add regression tests covering payloads containing: - `<script>` - `<img src=x onerror=...>` - SVG closing tags - Quotes and malformed attributes - `javascript:` URLs - Markdown pipes and embedded HTML 8. Consider applying a restrictive Content Security Policy to generated reports as defense in depth, while not treating CSP as a substitute for output encoding. ]]>
