T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/supplier_perf.py:109
- Finding
- Unescaped User-Controlled Values Permit Markdown Report Injection<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/inventory_tracker.py:25, 32-34, 70, 78-79` - `scripts/supplier_eval.py:27, 86, 94` - `scripts/purchase_order.py:35-38, 63-66, 76-77` - `scripts/demand_forecast.py:58, 147-148` - `scripts/cost_optimize.py:29, 138, 152` - `scripts/inventory_optimize.py:20, 109, 120` - `scripts/supply_risk.py:30-31, 153, 165, 175` - `scripts/supplier_perf.py:19, 109-115, 124, 142` **Vulnerability Type**: Untrusted Markdown content injection **Risk Level**: Medium **Classification**: T09: Insecure Skill Coding Practices ### Vulnerable Code A representative vulnerable output path appears in `scripts/supplier_perf.py:109-115`: ```python for i, s in enumerate(data["suppliers"], 1): medal = {1: "🥇", 2: "🥈", 3: "🥉"}.get(i, f" {i}") lines.append( f"| {medal} | {s['name']} | {s['otd']}% | {s['defect']}% " f"| {s['response_h']}h | ¥{s['quarterly_spend']:,.0f} " f"| {s['total']} | {s['grade']} | {s['trend']} |" ) ``` The same pattern is used throughout the project. For example, `scripts/purchase_order.py:63-66` directly places user-controlled purchase-order fields into a Markdown table: ```python f"| **订单编号** | {po['po_no']} |", f"| **日期** | {po['date']} |", f"| **供应商** | {po['supplier']} |", f"| **采购方** | {po['buyer']} |", ``` ### Technical Analysis The scripts accept free-text fields from command-line arguments or JSON input and interpolate those values directly into Markdown reports. No output-encoding or normalization is applied before values are inserted into table cells, headings, bold text, or list entries. An attacker-controlled value can contain: - Pipe characters that create additional Markdown table cells. - Newline characters that terminate the current row and create arbitrary sections. - Markdown links or images that display deceptive links or request remote resources in permissive renderers. - Raw HTML interpreted by renderers that allow HTML. - Instruction-like ...[truncated 1960 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Introduce a centralized Markdown-escaping function for all untrusted text: ```python def escape_markdown_cell(value: object) -> str: text = str(value) text = text.replace("\r", " ").replace("\n", " ") text = text.replace("\\", "\\\\") text = text.replace("|", "\\|") text = text.replace("<", "<").replace(">", ">") return text ``` 2. Apply the function to every user-controlled string before inserting it into Markdown, including names, countries, units, specifications, dates, buyer names, supplier names, and purchase-order identifiers. 3. Use stricter output-specific handling: - Remove line breaks from table-cell values. - Escape pipe characters in tables. - Disable raw HTML in the Markdown renderer. - Reject dangerous URL schemes if user-controlled links are ever supported. 4. Prefer structured JSON when passing results between software components or AI Agents. Render Markdown only at the final presentation boundary. 5. Add regression tests using values containing pipes, newlines, links, images, HTML, and instruction-like text. 6. Clearly label user-provided text as untrusted when reports are supplied to downstream AI systems. ]]>
