T09 · Insecure Skill Coding Practices
- Location
scripts/export_confirmation_form.py:13- Finding
Unescaped User-Controlled Content in Generated Markdown Confirmation Forms
- Content
View full analysis
str: if v is None: return "" return str(v) ``` ```python lines.append(f"- Input file: `{data.get('source', {}).get('input_file', '')}`") lines.append(f"- Input format: `{data.get('source', {}).get('input_format', '')}`") ``` ```python for k, v in fields: c = float(conf.get(k, 0.0)) need = "YES" if k in low else "NO" lines.append(f"| `{k}` | `{fmt(v)}` | `{c:.2f}` | `{need}` | |") ``` The first two strings above are translated representations of the source labels; the interpolation behavior is identical to the audited code. ### Technical Analysis Values read from an imported JSON, CSV, XLSX, or OCR result are converted directly to strings and interpolated into a Markdown document. The implementation does not escape: - Backticks, which can terminate inline code formatting - Pipe characters, which can create additional table columns - Carriage returns and line feeds, which can inject new Markdown blocks - HTML tags supported by some Markdown renderers - Other Markdown control sequences, such as links and headings The source input path is also inserted into Markdown without escaping. Because the Skill workflow directs an agent or user to inspect the generated confirmation form, attacker-controlled input can be presented as if it were trusted workflow content. This creates an indirect prompt-injection channel when an AI agent reads the generated Markdown. It also allows visual spoofing of confirmation fields when a human opens the document. The vulnerability does not independently bypass platform-level safety controls or execute operating-system commands. ### Attack Path 1. An attacker prepares an otherwise valid supported input file. 2. A field such as `name` contains ...[truncated 1432 chars]- Remediation
View remediation
str: if value is None: return "" text = str(value).replace("\r", "\\r").replace("\n", "\\n") text = text.replace("\\", "\\\\") text = text.replace("`", "\\`").replace("|", "\\|") text = text.replace("<", "<").replace(">", ">") return text ``` The order of escaping should be verified carefully so that newly added backslashes are not escaped twice. ]]>
