T09 · Insecure Skill Coding Practices
- Location
scripts/render_receipt.py:136- Finding
Unescaped Meeting Content Enables Markdown Document Injection
- Content
View full analysis
Vulnerability Details
File Location:
scripts/render_receipt.py:136-145, 285-297, 565-588, 625-639, 665-685
Vulnerability Type: Unescaped untrusted content in Markdown output
Risk Level: MediumVulnerable Code
python def _md_list(items: list[str], empty: str) -> str: if not items: return f"- {empty}" return "\n".join(f"- {item}" for item in items) def _md_numbered(items: list[str], empty: str) -> str: values = items or [empty] return "\n".join(f"{index}. {item}" for index, item in enumerate(values, start=1))python for evidence in item["evidence"]: locator = evidence.get("timestamp") or evidence.get("paragraph") quotes.append( f"> [{locator}] {evidence['speaker']}:\n" f"> “{evidence['quote']}”" )python values = { "title": view["title"], "confirmed_decisions": _md_list( view["confirmed"], view["decision_empty"] ), "owned_commitments": _md_list( view["owned"], "暂无明确接下项。" ), "open_loops": _md_list( view["open_loops"], "当前没有待确认事项。" ), "confirmation_message": view["confirmation_message"], "evidence": _evidence_markdown(data, view["neutral"]), } return _replace(template, values).rstrip() + "\n"The same unsafe pattern is used by the personal and executive Markdown renderers:
python values = { "title": view["title"], "current_decisions": _md_list( view["current_decisions"], "本场未形成明确结论。" ), "commitments": _md_list( view["commitments"], "会议材料未提供可核对的执行事项。" ), "confirmation_message": view["confirmation_message"], "evidence": view["evidence"], } return _replace(template, values).rstrip() + "\n"python values = { "title": view["title"], "executive_summary": view["executive_summary"], "decisions": _md_numbered( view["decisions"], "本场未形成明确结论。" ), "progress": _md_numbered( view["progress"], "会议材料未提供已确认的责任事项。" ), "risks": ...[truncated 3129 chars]- Remediation
View remediation
Remediation Suggestions
-
Implement a context-aware Markdown escaping function for all untrusted scalar values. At minimum, neutralize backslashes, backticks, asterisks, underscores, braces, brackets, angle brackets, parentheses, hash signs, plus signs, hyphens, periods in list contexts, exclamation marks, pipes, and blockquote markers.
-
Normalize or reject embedded carriage returns and line feeds before inserting source-derived values into headings, list items, blockquotes, and numbered lists. If multiline evidence must be retained, prefix every resulting line with the intended quotation marker after sanitization.
-
Block raw HTML in source-derived fields rather than relying on downstream Markdown-renderer configuration.
-
Validate links and image targets with an explicit allowlist. Reject dangerous schemes such as
javascript:,data:, andfile:. Consider disabling all source-derived links and images because they are unnecessary for the meeting-audit workflow. -
Keep trusted template Markdown separate from untrusted text. Apply escaping when constructing
_decision_text,_commitment_text,_open_loop_text, and evidence blocks, or immediately before each value is inserted into a Markdown context. -
Add defense-in-depth validation that detects prohibited Markdown constructs in receipt fields. Rendering-time escaping must remain the primary control because schema validation alone is context-insensitive.
-
Add regression tests covering:
- Injected headings and horizontal rules.
- Multiline unordered and numbered lists.
- Markdown links and images.
- Raw HTML tags.
- Blockquote termination.
- Backtick and fenced-code injection.
- Unsafe URL schemes.
- Embedded newline and carriage-return payloads.
- Speaker, locator, title, confirmation-message, decision, task, and risk fields.
-
Preserve the existing use of
html.escape()for HTML rendering and add a restrictive Content Security Policy to standalone HTML as addit ...[truncated 23 chars]
-
