Skip to content

fix(t2i): validate template content to prevent Jinja2 SSTI injection#8077

Merged
RC-CHN merged 4 commits into
AstrBotDevs:masterfrom
RC-CHN:fix-template-injection-via-t2i
May 8, 2026
Merged

fix(t2i): validate template content to prevent Jinja2 SSTI injection#8077
RC-CHN merged 4 commits into
AstrBotDevs:masterfrom
RC-CHN:fix-template-injection-via-t2i

Conversation

@RC-CHN
Copy link
Copy Markdown
Member

@RC-CHN RC-CHN commented May 8, 2026

Fixes #7330

Modifications / 改动点

fix(t2i): prevent Jinja2 SSTI via template content validation

  • Added input validation for T2I template create/update operations to block malicious Jinja2 payloads (CWE-1336). Previously, templates submitted through the dashboard API were stored unsanitized and sent as-is to the remote Jinja2 renderer, allowing authenticated users to inject server-side template expressions.

  • Validation uses a two-tier approach:

    1. Blacklist: blocks dunder chains (class, globals, etc.), dangerous builtins (os.popen, eval, exec), and Flask/Quart context escapes (config, request, session).
    2. Allowlist: restricts stored templates to the three variables actually passed by the renderer (text, version, shiki_runtime).
  • Security warnings are logged when templates are blocked, and the dashboard editor now surfaces API errors as toast notifications instead of silently swallowing them.

  • Plugin-side rendering via html_render() is unaffected — it passes template strings directly and does not go through the TemplateManager path.

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

5f28ffbb6eb9c064d3adee815999df82 0b25a7b0405c58d981b127673751b442 image

Checklist / 检查清单

  • 😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
    / 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。

  • 👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
    / 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”

  • 🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
    / 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到 requirements.txtpyproject.toml 文件相应位置。

  • 😮 My changes do not introduce malicious code.
    / 我的更改没有引入恶意代码。

Summary by Sourcery

Validate T2I HTML templates to prevent unsafe Jinja2 usage and surface validation errors in the dashboard UI.

Bug Fixes:

  • Add server-side validation for T2I template creation and updates to block Jinja2 SSTI patterns and unauthorized variables.

Enhancements:

  • Show toast error notifications in the T2I template editor when template save, apply, delete, or reset operations fail instead of silently logging errors.

Tests:

  • Update dashboard integration tests to use the supported T2I template variable name in template content.

@auto-assign auto-assign Bot requested review from LIghtJUNction and Soulter May 8, 2026 07:25
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. area:webui The bug / feature is about webui(dashboard) of astrbot. labels May 8, 2026
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The SSTI blacklist regexes look quite broad (e.g., matching any os. or subprocess. usage and any __class__-like dunder chain), which may block legitimate safe templates; consider tightening patterns or scoping them to Jinja expression blocks only to reduce false positives.
  • The validation error messages returned from the backend are currently English-only and passed straight through to the toast UI; if you care about localization/UX consistency, you may want to map these to i18n-friendly keys or wrap them in a more user-oriented message on the frontend.
  • You now repeat the error?.response?.data?.message || error?.message || String(error) pattern in several places in T2ITemplateEditor.vue; consider pulling this into a small helper (e.g., extractAxiosErrorMessage(error)) to keep the error-handling logic consistent and easier to maintain.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The SSTI blacklist regexes look quite broad (e.g., matching any `os.` or `subprocess.` usage and any `__class__`-like dunder chain), which may block legitimate safe templates; consider tightening patterns or scoping them to Jinja expression blocks only to reduce false positives.
- The validation error messages returned from the backend are currently English-only and passed straight through to the toast UI; if you care about localization/UX consistency, you may want to map these to i18n-friendly keys or wrap them in a more user-oriented message on the frontend.
- You now repeat the `error?.response?.data?.message || error?.message || String(error)` pattern in several places in `T2ITemplateEditor.vue`; consider pulling this into a small helper (e.g., `extractAxiosErrorMessage(error)`) to keep the error-handling logic consistent and easier to maintain.

## Individual Comments

### Comment 1
<location path="astrbot/core/utils/t2i/template_manager.py" line_range="20" />
<code_context>
+    ("flask_context", re.compile(r"\{\{.*?\b(config|request|session|g)\b.*?\}\}")),
+]
+
+_VAR_RE = re.compile(r"\{\{\s*(\w+)\s*(\|[^}]*)?\}\}")
+
+
</code_context>
<issue_to_address>
**🚨 issue (security):** Current variable regex allows bypassing strict validation via more complex Jinja2 expressions.

`_VAR_RE` only matches `{{ name }}` with an optional filter, so any more complex Jinja2 expression (e.g. attribute/index access, grouped expressions) won’t be checked against the allowlist and can slip through strict mode. To avoid bypasses and a false sense of safety, either inspect templates via a proper Jinja2 parser/sandbox (AST-level) or constrain the template surface so that only simple `{{ var }}` placeholders are allowed and reject anything else using `{{` that doesn’t match this rule.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread astrbot/core/utils/t2i/template_manager.py
@RC-CHN RC-CHN merged commit f29b339 into AstrBotDevs:master May 8, 2026
21 checks passed
@RC-CHN RC-CHN deleted the fix-template-injection-via-t2i branch May 8, 2026 07:30
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements Server-Side Template Injection (SSTI) protection for T2I templates by introducing a validation utility that uses blacklists and variable allowlisting. It also enhances the frontend dashboard by adding toast notifications for template operations and updates existing tests to comply with the new variable restrictions. Feedback focuses on critical security improvements to the validation logic to prevent bypasses via Jinja2 control structures or complex expressions and recommends adding dedicated unit tests for the security features.

Comment on lines +28 to +36
]

_VAR_RE = re.compile(r"\{\{\s*(\w+)\s*(\|[^}]*)?\}\}")


def validate_template_content(content: str, *, strict: bool = False) -> None:
for label, pattern in _SSTI_BLACKLIST:
if pattern.search(content):
logger.warning(f"SSTI validation blocked template: matched rule [{label}]")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

The strict validation logic is currently bypassable. It uses _VAR_RE.finditer(content), which only iterates over expressions that match the specific {{ var }} pattern. Any expression that does not match this regex (e.g., {{ config.DEBUG }} or {{ text + config }}) is simply skipped by the loop and thus accepted, leading to a security vulnerability.

In strict mode, the logic should ensure that every template expression found in the content is validated against the allowlist and restricted to simple variable access. Additionally, {% ... %} blocks should be disallowed if the goal is to only permit the three provided variables.

    if strict:
        # Block control structures {% ... %}
        if re.search(r"\{%.*?%\}", content, re.DOTALL):
            logger.warning("SSTI validation blocked template: matched control structure")
            raise ValueError("Control structures ({% ... %}) are not allowed in strict mode.")
            
        # Ensure all {{ ... }} blocks match the allowlist and are simple
        for expr in re.findall(r"\{\{.*?\}\}", content, re.DOTALL):
            m = _VAR_RE.fullmatch(expr)
            if not m:
                logger.warning(f"SSTI validation blocked template: complex expression '{expr}'")
                raise ValueError(f"Complex expressions are not allowed in templates: {expr}")
            
            var = m.group(1)
            if var not in _ALLOWED_VARS:
                logger.warning(f"SSTI validation blocked template: unauthorized variable '{var}'")
                raise ValueError(
                    f"Unauthorized Jinja2 variable '{var}'; "
                    f"allowed: {', '.join(sorted(_ALLOWED_VARS))}."
                )

_SSTI_BLACKLIST: list[tuple[str, re.Pattern]] = [
(
"dunder_chain",
re.compile(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The flask_context blacklist rule currently only checks for sensitive keywords within {{ ... }} blocks. An attacker could potentially bypass this by using Jinja2 control structures like {% if config.DEBUG %} or {% set x = config %}. It is safer to check for these keywords within any template tag.

Suggested change
re.compile(
("flask_context", re.compile(r"\{[\{%].*?\b(config|request|session|g)\b.*?[\}%]\}")),

Comment thread tests/test_dashboard.py
json={
"name": template_name,
"content": "<html><body>{{ content }}</body></html>",
"content": "<html><body>{{ text }}</body></html>",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While the existing tests have been updated to accommodate the new variable names, there are no new unit tests specifically verifying the SSTI protection logic. It is highly recommended to add test cases that attempt to bypass the validation using various payloads (e.g., dunder access, complex expressions, control blocks, and context variables like config) to ensure the security implementation is robust and to prevent regressions.

References
  1. New functionality should be accompanied by corresponding unit tests.

murphys7017 pushed a commit to murphys7017/AstrBot that referenced this pull request May 11, 2026
…strBotDevs#8077)

* fix(t2i): validate template content to prevent Jinja2 SSTI injection

* fix(t2i): add error feedback

* fix(test): update assertion to match previous commits

* style: format code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:webui The bug / feature is about webui(dashboard) of astrbot. size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Server-Side Template Injection via T2I Template Management

1 participant