T09 · Insecure Skill Coding Practices
Error
- Location
- invoice_template.py:99
- Finding
- Stored HTML and Script Injection in Generated Invoices<![CDATA[ ## Vulnerability Details **File Location**: `invoice_template.py:99-113`; additional unescaped HTML sinks occur at `invoice_template.py:392-456` **Vulnerability Type**: Stored HTML injection / stored cross-site scripting **Risk Level**: High ### Vulnerable Code ```python # Build items HTML items_html = "" for item in items: desc = item.get("description", "") qty = item.get("quantity", 0) rate = item.get("rate", 0.0) amount = item.get("amount", 0.0) items_html += f""" <tr> <td>{desc}</td> <td class="text-right">{qty}</td> <td class="text-right">{fmt(rate)}</td> <td class="text-right amount">{fmt(amount)}</td> </tr> """ ``` The completed HTML document also directly interpolates other values without escaping: ```python <div class="business-info"> {logo_html} <h1>{business['name']}</h1> <p>{business['address'].replace(chr(10), '<br>')}</p> <p>{business['email']}</p> <p>{business['phone']}</p> {f"<p>{business['website']}</p>" if business['website'] else ""} </div> <div class="invoice-info"> <h2>INVOICE</h2> <p><strong>Invoice #:</strong> {invoice_number}</p> <p><strong>Date:</strong> {invoice_date}</p> <p><strong>Due Date:</strong> {due_date}</p> <span class="status-badge {status_class}">{status_text}</span> </div> ``` ```python <div class="party"> <h3>Bill To</h3> <p><strong>{client.get('name', '')}</strong></p> <p>{client.get('email', '')}</p> {f"<p>{client.get('address', '').replace(chr(10), '<br>')}</p>" if client.get('address') else ""} {f"<p>{client.get('phone', '')}</p>" if client.get('phone') else ""} </div> ``` ```python {f'''<div class="payment-details"> <h3>Payment Details</h3> <pre>{payment_details}</pre> </div>''' if payment_details else ''} {f'<div class="notes"><strong>Notes:</strong> {notes}</div>' if notes else ''} {f'<div class="terms"><strong>Terms:</strong> {terms}</div>' if terms else ''} ...[truncated 2711 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Apply contextual HTML escaping to every value inserted as text: ```python from html import escape def html_text(value) -> str: return escape(str(value), quote=True) ``` Use this helper for invoice numbers, descriptions, client fields, business fields, notes, terms, payment details, tax labels, discount information, and statuses. 2. Preserve intended line breaks only after escaping: ```python safe_address = html_text(address).replace("\n", "<br>") ``` Do not replace newlines before escaping, because that would also escape the intentionally inserted `<br>` elements. 3. Validate website and link values separately. Permit only explicitly supported schemes, such as `https`, rather than treating arbitrary configuration values as safe markup. 4. Prefer a maintained template engine with automatic HTML escaping if adding a dependency is acceptable. Keep automatic escaping enabled and use explicit safe-markup annotations only for internally generated markup. 5. Consider adding a restrictive Content Security Policy to generated HTML, for example one that blocks scripts and limits external resources. This is defense in depth and does not replace proper output encoding. 6. Add regression tests using payloads containing: ```text <script>alert(1)</script> <img src=x onerror=alert(1)> "><svg onload=alert(1)> & < > " ' ``` The tests should verify that these values appear as encoded text and do not create executable elements or attributes. ]]>
