# PYTHON-LANG-SEC-093: Mako Template Usage Detected

> **Severity:** MEDIUM | **CWE:** CWE-94 | **OWASP:** A03:2021

- **Language:** Python
- **Category:** Python Core
- **URL:** https://codepathfinder.dev/registry/python/lang/PYTHON-LANG-SEC-093
- **Detection:** `pathfinder scan --ruleset python/PYTHON-LANG-SEC-093 --project .`

## Description

Mako is a Python template engine used by web frameworks including Pyramid and TurboGears.
Unlike some template engines with sandboxed expression evaluation, Mako templates execute
Python expressions in the server's Python environment with no security sandbox.

Server-Side Template Injection (SSTI) occurs when user-supplied content is rendered as
part of a template's code section (between <% %> or ${} delimiters) rather than being
safely HTML-escaped as data. In Mako, injected template code can access __builtins__,
import modules, and execute arbitrary Python.

The primary risks are: (1) using user input as the template string itself
(mako.Template(user_input)) and (2) insufficient escaping allowing template syntax
characters to reach template evaluation contexts.


## Vulnerable Code

```python
import xml.etree.ElementTree as ET
import xml.dom.minidom
import xml.sax
import xmlrpc.client
import csv

# SEC-093: mako
from mako.template import Template
tmpl = Template("Hello ${name}")
```

## Secure Code

```python
from mako.template import Template
from mako.lookup import TemplateLookup

# INSECURE: Template with user input as template string
# template = Template(user_provided_template)
# result = template.render(name=username)

# SECURE: Load templates from trusted filesystem directory only
lookup = TemplateLookup(
    directories=["/app/templates"],  # Trusted directory only
    strict_undefined=True,           # Fail on undefined variables
    default_filters=["h"],           # Auto-escape HTML by default
    input_encoding="utf-8",
)

def render_user_profile(username: str, user_data: dict) -> str:
    template = lookup.get_template("profile.html")
    return template.render(
        username=username,   # Passed as data, auto-escaped
        user_data=user_data
    )

# SECURE: Never include user input in template strings
# All user data must be passed as render() arguments, not embedded in templates

```

## Detection Rule (Python SDK)

```python
from rules.python_decorators import python_rule
from codepathfinder import calls, QueryType

class MakoModule(QueryType):
    fqns = ["mako.template"]


@python_rule(
    id="PYTHON-LANG-SEC-093",
    name="Mako Template Detected",
    severity="MEDIUM",
    category="lang",
    cwe="CWE-94",
    tags="python,mako,template,ssti,CWE-94",
    message="Mako templates do not sandbox expressions. Ensure templates are trusted.",
    owasp="A03:2021",
)
def detect_mako():
    """Detects mako.template.Template usage."""
    return MakoModule.method("Template")
```

## How to Fix

- Never use mako.Template(user_input) or any pattern where user input becomes the template string; load templates from a trusted directory using TemplateLookup.
- Always configure TemplateLookup with a fixed templates directory that only contains developer-controlled template files.
- Enable default_filters=["h"] on TemplateLookup to auto-escape HTML in all ${} expressions by default.
- Avoid the | n (no-escape) filter unless the template variable content has been explicitly validated as safe HTML.
- Pass all user data as template render() arguments, never concatenate user data into template strings.

## Security Implications

- **Server-Side Template Injection (SSTI):** If user-controlled content is passed as the template string to mako.Template(), or
if user content containing ${} or <% %> is rendered in an expression context, an
attacker can inject Python code executed on the server. Payloads like
${__import__("os").system("id")} achieve RCE.

- **Information Disclosure via Template Context:** Mako templates have access to all variables in the template rendering context. Injected
template code can access and exfiltrate any variable passed to template.render(), including
database connections, session objects, configuration dictionaries, and request objects.

- **Sandbox Escape via Python Built-ins:** Unlike Jinja2's sandboxed environment, Mako provides no sandbox. Injected Python in
a Mako template can directly access __builtins__, __import__(), and any Python object
reachable from the template context without restriction.

- **Cross-Site Scripting (XSS) via Insufficient Escaping:** Mako's ${variable} expression auto-escapes HTML by default with the h filter, but
developers who use ${ variable | n } (raw output) or construct HTML manually can
bypass XSS protection, enabling reflected or stored XSS.


## FAQ

**Q: Does Mako auto-escape user input to prevent XSS?**

Mako's ${expression} syntax applies the h (HTML escape) filter by default when
output_encoding is set and default_filters includes "h". However, this must be
explicitly configured via TemplateLookup. Without this configuration, ${expression}
outputs raw unescaped content. Always configure default_filters=["h"] explicitly.


**Q: How is Mako's security compared to Jinja2?**

Jinja2 provides a sandboxed execution environment (jinja2.sandbox.SandboxedEnvironment)
that restricts access to dangerous built-ins and attributes. Mako has no equivalent
sandbox. Both are vulnerable to SSTI if user input is used as the template string,
but Mako provides fewer mitigations. Jinja2 is generally preferred for new projects.


**Q: What is the safe pattern for Mako with user-provided template names?**

Use TemplateLookup to map template names to files in a trusted directory. Accept only
a template name (filename) from the user, not a template string. The TemplateLookup
directory acts as a chroot-like boundary: lookup.get_template(user_name) is safe if
user_name is validated to not contain path traversal sequences (../).


**Q: Is Pyramid/TurboGears affected if they use Mako internally?**

Pyramid uses Mako as one of its template engines via pyramid.mako_templating. When
Pyramid loads templates from trusted directories using its registry-based template
lookup, the risk is low. The risk is when developers call mako.Template() directly
with content derived from user input or untrusted sources.


**Q: How do I detect SSTI vulnerability in existing Mako code?**

Look for any code path where a string derived from user input (HTTP parameters,
database values, file contents) reaches the mako.Template() constructor or is
concatenated into a template string before rendering. Code Pathfinder's taint
analysis traces these flows from HTTP input sources to the Template() sink.


**Q: What Mako filter prevents XSS: h, u, or n?**

h applies HTML entity escaping (< becomes &lt;) and prevents XSS in HTML context.
u applies URL encoding for URL parameters. n means no filter (raw output) and should
only be used for already-safe HTML content. Always use h as the default filter and
explicitly mark content with n only after HTML sanitization (e.g., via bleach).


## References

- [CWE-94: Code Injection](https://cwe.mitre.org/data/definitions/94.html)
- [Mako templates documentation](https://docs.makotemplates.org/)
- [OWASP Server-Side Template Injection](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/18-Testing_for_Server_Side_Template_Injection)
- [PortSwigger: Server-Side Template Injection](https://portswigger.net/research/server-side-template-injection)
- [OWASP Top 10 A03:2021 Injection](https://owasp.org/Top10/A03_2021-Injection/)

---

Source: https://codepathfinder.dev/registry/python/lang/PYTHON-LANG-SEC-093
Code Pathfinder — Open source, type-aware SAST with cross-file dataflow analysis
