Skip to content

PDD-CLI Bug: Incorrectly Applies HTML Escaping to Template Output #584

@jiaminc-cmu

Description

@jiaminc-cmu

PDD-CLI Bug: Incorrectly Applies HTML Escaping to Template Output

PDD-CLI applies HTML escaping (html.escape()) to content that's already been safely rendered by template engines, resulting in visible escape sequences like &lt; instead of < in output.

Why this matters: Rendered emails/HTML show escaped tags as text instead of rendering as HTML.

Concrete Example

For email template rendering:

# PDD generated (WRONG):
import html
from jinja2 import Template

def render_email(template_str: str, data: dict) -> str:
    template = Template(template_str)
    body = template.render(**data)
    
    # WRONG: Escaping already-safe template output!
    return html.escape(body)

Usage:

template = "<h1>Hello {{name}}</h1>"
email_body = render_email(template, {'name': 'Alice'})
# Result: "&lt;h1&gt;Hello Alice&lt;/h1&gt;"  ← Shows as text, not HTML!

What went wrong: PDD escaped the rendered template output. Template engines like Jinja2 already handle escaping of variables ({{name}}). Escaping the final output double-escapes everything.

Impact: Emails display with visible HTML tags: &lt;h1&gt;Hello Alice&lt;/h1&gt; instead of rendering as headings.

Why PDD Makes This Mistake

PDD-CLI currently:

  • Applies security escaping broadly "to be safe"
  • Doesn't distinguish between:
    • User input (needs escaping)
    • Template output (already safe)
  • Treats all string concatenation as potentially unsafe

But it should:

  1. Trust template engine output (Jinja2, React, etc. handle escaping)
  2. Only escape raw user input BEFORE passing to templates
  3. Understand context: templates vs raw strings

How to Prevent This in PDD-CLI

What PDD should do differently:

  1. Don't escape template engine output:

    # Template engines handle escaping
    body = template.render(**data)  # Already safe
    return body  # Don't escape again
  2. Escape user input BEFORE templates:

    # If passing raw HTML (rare case)
    from markupsafe import Markup
    safe_content = Markup.escape(user_input)
    body = template.render(content=safe_content)
  3. Learn template engine security models: Understand which frameworks auto-escape and which don't.

Example improvement:

Current: Render template → escape output
       → Shows HTML tags as text
       → Broken emails

Improved: Render template → return directly
        → (Template engine already escaped variables)
        → Proper HTML rendering

Severity

P2 - Medium Priority

  • Frequency: Low - only affects template rendering
  • Impact: High - completely breaks HTML rendering (shows source)
  • Detectability: High - immediately visible in output
  • Prevention cost: Low - just remove incorrect escaping

Category

incomplete-implementation

Related Issues


For Contributors: Discovered in email rendering where html.escape() was applied to Jinja2 template output, showing escaped HTML in emails, fixed in commit 34a651d5.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions