Files
2026-02-25 10:06:27 +00:00

146 lines
4.3 KiB
Markdown

# Template Security
## Overview
The `cleveragents.templates.secure_renderer` module provides a sandboxed
template renderer that prevents template injection attacks while
supporting simple variable substitution.
## Threat Model
Template injection occurs when user-controlled input is embedded in
template strings and evaluated. Python's `str.format()` is particularly
dangerous because it allows:
- **Attribute access**: `{obj.__class__.__init__.__globals__}` can leak
sensitive module-level data.
- **Index access**: `{items[0]}` can read arbitrary collection entries.
- **Format specs**: `{val:>10}` can cause unexpected formatting or DoS.
- **Conversion flags**: `{val!r}` can change output representation.
The secure renderer rejects all of these, only allowing `{name}` style
substitution with simple identifiers.
## Allowed Syntax
Only the following template syntax is permitted:
```
{variable_name}
```
Where `variable_name` matches `\w+` (letters, digits, underscore).
Everything else is rejected:
| Pattern | Risk | Result |
|------------------|-------------------------------|----------|
| `{x.y}` | Attribute access | Rejected |
| `{x[0]}` | Index access | Rejected |
| `{x:>10}` | Format spec | Rejected |
| `{x!r}` | Conversion flag | Rejected |
| `{fn()}` | Function call | Rejected |
| `{{ x }}` | Jinja2 expression | Rejected |
| `{% block %}` | Jinja2 block | Rejected |
## Configuration
Use `TemplateConfig` to configure the renderer:
```python
from cleveragents.templates.secure_renderer import (
SecureTemplateRenderer,
TemplateConfig,
)
config = TemplateConfig(
allowed_keys=frozenset({"name", "description", "role"}),
max_template_length=10_000, # default
max_output_length=50_000, # default
reject_unknown_keys=True, # default
log_rejected=True, # default
)
renderer = SecureTemplateRenderer(config=config)
```
### Parameters
- **`allowed_keys`**: Set of permitted placeholder names. Empty means
any simple identifier is allowed (open mode).
- **`max_template_length`**: Maximum raw template string length
(default: 10,000). Prevents DoS via oversized templates.
- **`max_output_length`**: Maximum rendered output length
(default: 50,000). Prevents DoS via value expansion.
- **`reject_unknown_keys`**: Whether to raise on keys not in the
allowlist (default: `True`). Ignored when `allowed_keys` is empty.
- **`log_rejected`**: Log rejected keys at WARNING level (default:
`True`).
## Pre-validation
Use `validate_template()` at action/plan creation time to catch
issues early:
```python
from cleveragents.templates.secure_renderer import validate_template
errors = validate_template(
template_text,
allowed_keys=frozenset({"name", "description"}),
)
if errors:
raise ValueError(f"Invalid template: {errors}")
```
## Convenience Function
For one-off renders without creating a renderer instance:
```python
from cleveragents.templates.secure_renderer import render_template_secure
result = render_template_secure(
"Hello {name}",
{"name": "World"},
allowed_keys=frozenset({"name"}),
)
```
## Safe Substitution Behaviour
If a placeholder key is not found in the context, it is left as-is
in the output (safe-substitute behaviour):
```python
renderer.render("Hello {name}, {role}", {"name": "Alice"})
# => "Hello Alice, {role}"
```
## Legacy Compatibility
The existing `TemplateRenderer` in `cleveragents.templates.renderer`
now wraps `SecureTemplateRenderer` internally. It returns the raw
template on any rendering failure to preserve backward compatibility,
and logs a WARNING when doing so (enable DEBUG for the full template).
An optional `config` parameter allows callers to pass a
`TemplateConfig` through to the underlying secure renderer:
```python
from cleveragents.templates.renderer import TemplateRenderer
from cleveragents.templates.secure_renderer import TemplateConfig
renderer = TemplateRenderer(
config=TemplateConfig(allowed_keys=frozenset({"name"})),
)
```
## Exception Hierarchy
```
TemplateError (base)
├── TemplateSecurityError — unsafe constructs
├── TemplateSizeError — length limits exceeded
└── TemplateValidationError — allowlist violations
```