# Escaping

URL: /kida/docs/usage/escaping/
Section: usage
Description: HTML and Markdown escaping plus safe content handling

---

> For a complete page index, fetch /kida/llms.txt.

Kida auto-escapes output by default. HTML mode protects browser output from injection, Markdown mode protects CI reports and comments from accidental formatting, and terminal mode sanitizes ANSI control sequences.

## Autoescape

With `autoescape=True` (default), output is HTML-escaped:

```python
env = Environment(autoescape=True)
template = env.from_string("{{ content }}")
html = template.render(content="<script>alert('xss')</script>")
# Output: &lt;script&gt;alert('xss')&lt;/script&gt;
```

Special characters are replaced:

| Character | Escaped |
|-----------|---------|
| `<` | `&lt;` |
| `>` | `&gt;` |
| `&` | `&amp;` |
| `"` | `&quot;` |
| `'` | `&#39;` |

## Disable Autoescape

For specific templates:

```python
# Callable autoescape
def should_escape(template_name):
    if template_name is None:
        return True
    return template_name.endswith(".html")

env = Environment(autoescape=should_escape)
```

Globally (not recommended):

```python
env = Environment(autoescape=False)
```

## Safe Filter

Mark content as trusted for the active render surface:

```kida
{{ html_content | safe }}
```

With optional reason for code review:

```kida
{{ cms_block | safe(reason="sanitized by bleach library") }}
{{ admin_html | safe(reason="admin-only content") }}
```

Under `autoescape=True` / `autoescape="html"`, `safe` means trusted HTML. Under `autoescape="markdown"`, `safe` means trusted Markdown. Do not use it for untrusted issue text, PR bodies, tool output, user comments, or any value that has not already crossed a clear trust boundary.

## Markdown Autoescape

Use Markdown mode for GitHub step summaries, PR comments, release notes, and other Markdown output:

```python
from kida.markdown import markdown_env

env = markdown_env()
template = env.from_string("{{ text }}")
template.render(text="Use *literal* [text]")
# Use \*literal\* \[text\]
```

Markdown autoescape targets CommonMark/GFM formatting triggers. It escapes inline backslashes, backticks, emphasis markers, brackets, and angle brackets. Since 0.9.0, it no longer escapes punctuation that is harmless in normal inline text, such as hyphens, parentheses, hashes, pipes, and tildes. Block-leading Markdown markers like `#`, `>`, `-`, `+`, and ordered-list digits are still escaped when they appear at the start of a line.

`Markup` implements both `__html__` and `__markdown__`, so `safe` is honored by Markdown autoescape:

```python
from kida.markdown import markdown_env

env = markdown_env()
template = env.from_string("{{ body }}\n{{ body | safe }}")
template.render(body="## Trusted heading")
# \## Trusted heading
# ## Trusted heading
```

If you keep committed snapshots for Markdown reports, regenerate them after upgrading to 0.9.0. Expected diffs usually remove unnecessary backslashes from prose, dates, diagnostic codes, and function-call text.

## Markup Class

Create safe content in Python:

```python
from kida import Markup

# String marked as safe
safe_html = Markup("<b>Bold</b>")
template.render(content=safe_html)  # Not escaped
```

`Markup` preserves safety in HTML and Markdown modes. Treat it like `safe`: only wrap content that is already sanitized or authored by trusted code.

### Markup Operations

```python
# Concatenation escapes unsafe strings
safe = Markup("<b>")
result = safe + "<script>"  # <b>&lt;script&gt;
result = safe + Markup("<i>")  # <b><i>

# Format escapes arguments
Markup("<p>{}</p>").format("<script>")
# <p>&lt;script&gt;</p>
```

### Escape Function

```python
from kida import Markup

# Escape a string
escaped = Markup.escape("<script>")
# &lt;script&gt;
```

## Common Patterns

### Pre-Sanitized Content

When content is already sanitized:

```python
import bleach

cleaned = bleach.clean(user_html, tags=["b", "i", "a"])
template.render(content=Markup(cleaned))
```

### Rendered Markdown

```python
import markdown

html = markdown.markdown(source)
template.render(content=Markup(html))
```

### HTML in JSON

The `tojson` filter outputs JSON marked safe for the template engine. For **trusted** data, embedding that JSON inside a classic `<script>` block is often convenient:

```kida
<script>
const data = {{ user_data | tojson }};
</script>
```

Untrusted or attacker-controlled data can contain `</script>` (or `<`) and **close the script element early**, which breaks parsing and can enable XSS. Prefer `<script type="application/json">` plus `JSON.parse` on `textContent`, or serve JSON from an endpoint, instead of inlining raw `tojson` output in executable script for untrusted content.

### JSON in HTML attributes

Double-quoted attributes must not contain raw `"` in the value. Default `tojson` output includes double quotes, so this breaks the attribute:

```kida
{# Broken — quotes terminate the attribute early #}
<div x-data="setup({{ config | tojson }})">
```

Use `tojson(attr=true)` so the JSON is HTML-entity-encoded (`"` → `&quot;`, etc.). The browser decodes entities before JavaScript reads the attribute (e.g. Alpine `x-data`):

```kida
<div x-data="{{ config | tojson(attr=true) }}">
```

Alternatively, keep default `tojson` and wrap the attribute in single quotes, or put JSON in `<script type="application/json">` and read it from script.

## Security Best Practices

### Always Use Autoescape

```python
# ✅ Autoescape on (default)
env = Environment(autoescape=True)

# ❌ Never disable globally
env = Environment(autoescape=False)
```

### Audit `safe` Usage

```kida
{# ✅ Document why it's safe #}
{{ content | safe(reason="sanitized by bleach") }}

{# ❌ Unmarked safe usage #}
{{ user_input | safe }}
```

### Validate Content

```python
# ✅ Sanitize before marking safe
cleaned = bleach.clean(content, tags=ALLOWED_TAGS)
Markup(cleaned)

# ❌ Never mark user input safe directly
Markup(request.form["content"])  # XSS vulnerability!
```

## Escape Filter

Explicitly escape content:

```kida
{{ content | escape }}
{{ content | e }}  {# Short alias #}
```

Useful when autoescape is disabled for a template.

## See Also

- [[docs/reference/filters|Filter Reference]] — escape, safe, striptags
- [[docs/syntax/variables|Variables]] — Output expressions
- [[docs/usage/error-handling|Error Handling]] — Debug template issues
