T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/translator_engine_v10.py:130
- Finding
- Untrusted Model-Generated HTML Is Processed by a Network-Capable PDF Renderer<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/translator_engine.py:129-131, 175` - `scripts/translator_engine_v4.py:164-166, 179` - `scripts/translator_engine_v5.py:156-158, 171` - `scripts/translator_engine_v6.py:160-162, 226` - `scripts/translator_engine_v7.py:147-149, 162` - `scripts/translator_engine_v8.py:63-65, 75` - `scripts/translator_engine_v9.py:118-120, 144` - `scripts/translator_engine_v9_final.py:90-92, 111` - `scripts/translator_engine_v10.py:130-144, 155-159` **Vulnerability Type**: Unsanitized model-generated HTML, server-side request forgery, and local resource inclusion **Risk Level**: High **Classification**: T09: Insecure Skill Coding Practices ### Vulnerable Code Representative code from `scripts/translator_engine_v10.py`: ```python h_html = markdown2.markdown(h_md, extras=["tables"]) b_html = markdown2.markdown(b_md, extras=["tables"]) f_html = markdown2.markdown(f_md, extras=["tables"]) # 4. Assemble image HTML fig_html = "" if fig_b64s: fig_html = "<div class='app'><h3>[ Original Figures/Diagrams ]</h3>" for b64 in fig_b64s: fig_html += f"<div style='text-align:center;'><img src='{b64}' class='extracted-fig'/></div>" fig_html += "</div>" app_html = "" if annotations: app_html = f"<div class='app'><h3>[ Diagram Symbol Description ]</h3>{markdown2.markdown(annotations)}</div>" ``` The generated HTML is subsequently passed to WeasyPrint: ```python def build_pdf(html_segments, out_pdf): css = """...""" html = f"<html><head><style>{css}</style></head><body>{''.join(html_segments)}</body></html>" html = final_sanitizer(html) HTML(string=html, base_url=os.getcwd()).write_pdf(out_pdf) ``` The same data flow exists in the other affected translator versions: Gemini output is converted through `markdown2.markdown()` and then supplied to `HTML(...).write_pdf()`. ### Technical Analysis PDF page images and extracted diagrams are uploaded to Gemini, whose response is treated as Mar ...[truncated 3663 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Sanitize model-generated content with a strict allowlist.** - Permit only the minimum required formatting elements, such as `p`, `br`, `strong`, `em`, `ul`, `ol`, `li`, `table`, `thead`, `tbody`, `tr`, `th`, and `td`. - Remove `img`, `svg`, `iframe`, `object`, `embed`, `link`, `style`, `script`, and other resource-loading or executable elements from model output. - Remove URL-bearing attributes, including `src`, `href`, `srcset`, `poster`, and inline `style`. - Do not rely on regular expressions as an HTML sanitizer. 2. **Escape raw HTML before Markdown conversion.** - Configure the Markdown pipeline to reject or escape embedded HTML. - Treat every Gemini response as untrusted, even when it passes XML structural validation. 3. **Use a restrictive WeasyPrint URL fetcher.** - Allow only internally generated `data:image/png;base64,...` and other explicitly required data URIs. - If remote formula rendering remains necessary, allow only HTTPS requests to the exact approved host. - Reject `file:`, `ftp:`, `gopher:`, and other unnecessary schemes. - Reject loopback, private, carrier-grade NAT, multicast, reserved, and link-local addresses, including cloud metadata endpoints. - Revalidate the destination after DNS resolution and after every redirect to prevent DNS rebinding and redirect bypasses. - Enforce response-size, media-type, redirect-count, and timeout limits. 4. **Avoid remote formula loading during final rendering.** - Fetch formula SVGs through a controlled client with strict destination validation. - Validate and sanitize returned SVG data. - Embed the validated result as a data URI rather than allowing WeasyPrint to perform unrestricted network retrieval. - Prefer a local, sandboxed LaTeX renderer where operationally feasible. 5. **Remove the broad filesystem base URL.** - Do not use `base_url=os.getcwd()` with untrusted HTML. - If a base URL is required, use an ...[truncated 795 chars]
