Files
cleveragents-core/docs/reference/template_security.md
T
2026-02-25 10:06:27 +00:00

4.3 KiB

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:

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:

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:

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):

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:

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