T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/md_to_pdf.py:173
- Finding
- Unrestricted Local and Remote Resource Loading During PDF Rendering<![CDATA[ ## Vulnerability Details **File Location**: `scripts/md_to_pdf.py:173-227` and `scripts/md_to_pdf.py:289-295` **Vulnerability Type**: Server-Side Request Forgery and local resource disclosure through untrusted Markdown or CSS **Risk Level**: High ### Vulnerable Code ```python # Read markdown with open(input_path, 'r', encoding='utf-8') as f: md_content = f.read() # Convert markdown to HTML md = markdown.Markdown(extensions=[ 'tables', 'fenced_code', 'toc', 'nl2br' ]) html_body = md.convert(md_content) # Wrap in full HTML document html_content = f"""<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Document</title> </head> <body> {html_body} </body> </html>""" # Use custom CSS or default css_to_use = css_content if css_content else DEFAULT_CSS # Adjust page orientation if orientation == "landscape": css_to_use = css_to_use.replace("size: A4;", "size: A4 landscape;") # Convert to PDF font_config = FontConfiguration() html = HTML(string=html_content, base_url=str(Path(input_path).parent)) css = CSS(string=css_to_use, font_config=font_config) html.write_pdf(output_path, stylesheets=[css], font_config=font_config) ``` The custom CSS is also loaded without resource validation: ```python # Load custom CSS if provided css_content = None if args.css: if not os.path.exists(args.css): print(f"Warning: CSS file not found: {args.css}") else: with open(args.css, 'r', encoding='utf-8') as f: css_content = f.read() ``` ### Technical Analysis The converter passes Markdown-derived HTML and caller-selected CSS directly to WeasyPrint. No custom URL fetcher, URI allowlist, network restriction, or local-path containment check is configured. Python Markdown permits raw HTML in the source document under its normal behavior. Consequently, an untrusted document can introduce resource-loading elements such as images. Caller-controlled CSS can similarly contain resource directives such ...[truncated 2932 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Install a restrictive WeasyPrint URL fetcher** - Permit only explicitly approved URI schemes. - Resolve and validate hostnames before each request. - Block loopback, link-local, multicast, private, reserved, and cloud metadata address ranges. - Revalidate after redirects and DNS resolution to prevent redirect-based or DNS-rebinding bypasses. - Apply strict connection, read, response-size, and redirect limits. 2. **Restrict local file access** - Canonicalize every local path with `Path.resolve()`. - Require resources to remain under a dedicated document asset directory. - Reject absolute paths, unauthorized `file:` URLs, traversal outside the asset root, and symbolic-link escapes. 3. **Harden Markdown processing** - Disable or sanitize raw HTML when documents are not fully trusted. - Allow only a narrow set of safe tags and attributes. - Remove resource-bearing elements and attributes unless specifically required. 4. **Validate custom CSS** - Reject or sanitize `@import`, external `url(...)`, and remote `@font-face` declarations. - Prefer administrator-provided, pre-reviewed themes instead of arbitrary caller-supplied CSS. 5. **Isolate conversion** - Run rendering in a dedicated low-privilege container or sandbox. - Disable outbound network access by default. - Use a read-only filesystem and mount only the input and output directories required for the conversion. - Do not expose cloud credentials or metadata services to the conversion environment. 6. **Document the trust boundary** - Clearly state that untrusted Markdown, HTML, CSS, and remote resources require sandboxing. - Make remote resource loading opt-in rather than enabled implicitly. ]]>
