T09 · Insecure Skill Coding Practices
- Location
scripts/render_translation.py:42- Finding
Unsanitized HTML Allows Local and Remote Resource Resolution During PDF Rendering
- Content
View full analysis
Vulnerability Details
File Location:
scripts/render_translation.py:42-47, 55-61, 94-95
Vulnerability Type: Unsanitized active HTML and unrestricted resource resolution
Risk Level: MediumVulnerable Code
python def render_markdown_fragment(markdown_text: str) -> str: markdown_renderer = load_markdown_renderer() return markdown_renderer( markdown_text, extensions=["extra", "sane_lists", "tables"], output_format="html5", )python def build_html_document(markdown_text: str, document_title: str, source_name: str) -> str: template = Template(TEMPLATE_PATH.read_text(encoding="utf-8")) css_text = CSS_PATH.read_text(encoding="utf-8") body_html = render_markdown_fragment(markdown_text) return template.substitute( document_title=escape(document_title), source_name=escape(source_name), css_text=css_text, body_html=body_html, )python html_class = load_weasyprint_html() html = html_class(string=html_document, base_url=str(SKILL_ROOT)) html.write_pdf(target=str(target_path))The output contract also expressly permits embedded HTML in
references/output-contract.md:19-20:markdown - `table`: Table structure preserved. Markdown tables are allowed when they faithfully represent the grid. - `table`: Embedded HTML is allowed when Markdown tables cannot preserve merged cells, multi-line cells, or clinically important alignment.Technical Analysis
The renderer accepts Markdown content and converts it to HTML with the Python Markdown package. Embedded HTML is permitted and is inserted into the fixed document template through the unescaped
body_htmlsubstitution.WeasyPrint subsequently processes this attacker-influenced HTML to generate a PDF. Resource-bearing HTML or CSS can cause WeasyPrint to resolve absolute local or remote URLs. The configured
base_url...[truncated 2124 chars]- Remediation
View remediation
Remediation Suggestions
- Sanitize generated HTML with a strict allowlist before passing it to WeasyPrint. Permit only the elements required for document structure, such as headings, paragraphs, lists, tables, and limited inline formatting.
- Remove or reject resource-loading elements and constructs unless explicitly required, including
img,link,iframe, SVG, embedded objects, and CSS declarations containing URLs. - Strip dangerous or unnecessary attributes, including
src,href,srcset,style, and event-handler attributes. If links are required, validate their schemes and prevent resource fetching. - Provide WeasyPrint with a custom
url_fetcherthat denies HTTP, HTTPS, FTP, and other network schemes by default. - If local assets are necessary, permit
file:access only for canonical paths beneath a dedicated immutable asset directory. Reject traversal and symbolic-link escapes after resolving the path. - Run PDF rendering in a sandboxed process with no network access, a read-only filesystem, a minimal allowlisted asset mount, resource limits, and a dedicated unprivileged account.
- Add regression tests using malicious Markdown containing remote URLs, loopback and link-local addresses, local-file references, CSS
url(...), SVG references, and path traversal attempts. - Update the output contract so that embedded HTML is restricted to an explicitly documented safe subset rather than being generally allowed.
