T09 · Insecure Skill Coding Practices
- Location
scripts/render_pdf.py:191- Finding
Unrestricted Custom Jinja Templates Enable Server-Side Template Injection and Arbitrary Resource Fetching
- Content
View full analysis
Vulnerability Details
File Location:
scripts/render_pdf.py:191-214, with attacker-selectable template handling atscripts/render_pdf.py:220-244
Vulnerability Type: Server-side template injection and unrestricted PDF resource fetching
Risk Level: HighVulnerable code:
python def build_environment(template_root: Path): from jinja2 import Environment, FileSystemLoader, select_autoescape return Environment( loader=FileSystemLoader(str(template_root)), autoescape=select_autoescape(("html", "xml")), trim_blocks=True, lstrip_blocks=True, ) def render_html(data: dict, template_path: Path) -> str: environment = build_environment(template_path.parent) template = environment.get_template(template_path.name) return template.render(**data) def write_pdf(html: str, output_path: Path, base_url: str) -> None: from weasyprint import HTML output_path.parent.mkdir(parents=True, exist_ok=True) HTML(string=html, base_url=base_url).write_pdf(str(output_path))The custom-template option and rendering flow are:
python parser.add_argument( "--template-file", help="Optional custom template path inside the workspace", )python template_path = ( resolve_workspace_path(args.template_file, workspace, must_exist=True) if args.template_file else default_template ) html_out_path = ( resolve_workspace_path(args.html_out, workspace, must_exist=False) if args.html_out else None ) payload = normalize_payload(load_payload(input_path), workspace) html = render_html(payload, template_path) if html_out_path: html_out_path.parent.mkdir(parents=True, exist_ok=True) html_out_path.write_text(html, encoding="utf-8") write_pdf(html, output_path, base_url=str(workspace))Technical Analysis
The script accepts a caller-selected Jinja template loc ...[truncated 2724 chars]
- Remediation
View remediation
Remediation Suggestions
- Do not render untrusted custom templates. Disable
--template-fileby default and require an explicit trusted-template policy when it is enabled. - Replace the standard Jinja environment with
jinja2.sandbox.SandboxedEnvironment. - Maintain strict allowlists for permitted filters, tests, globals, and template constructs. Remove unnecessary default globals and reject access to private or dangerous attributes.
- Consider replacing general-purpose Jinja templates with a fixed built-in template and a validated JSON schema.
- Add a custom WeasyPrint
url_fetcherthat permits only workspace-local files whose canonical paths remain under the workspace root. - Reject HTTP, HTTPS, FTP, data, and unsupported URI schemes unless a specific business requirement exists.
- If remote resources are required, allowlist exact hosts and ports and block loopback, private, link-local, multicast, and cloud metadata address ranges after DNS resolution. Revalidate redirects and resolved addresses.
- Run PDF generation in a low-privilege isolated process or container with a read-only filesystem where possible, no sensitive environment variables, restricted outbound networking, resource limits, and access only to required input and output directories.
- Add tests using malicious Jinja attribute-traversal expressions and resource URLs targeting localhost, private addresses, and link-local metadata services.
- Do not render untrusted custom templates. Disable
