Skip to content

[Frontend][Rust] Bound chat-template evaluation to prevent DoS (#52025) - #52163

Open
jaideeppyne wants to merge 2 commits into
vllm-project:mainfrom
jaideeppyne:fix/rust-chat-template-eval-budget
Open

[Frontend][Rust] Bound chat-template evaluation to prevent DoS (#52025)#52163
jaideeppyne wants to merge 2 commits into
vllm-project:mainfrom
jaideeppyne:fix/rust-chat-template-eval-budget

Conversation

@jaideeppyne

Copy link
Copy Markdown

Purpose

Fixes #52025.

The Rust frontend renders a caller-supplied (or model-supplied) Jinja chat_template from POST /v1/chat/completions with no evaluation budget. Rendering cost is O(N^depth) in caller-controlled loop bounds, and minijinja's per-range() element limit is trivially walked around by nesting three individually-legal range() calls. A 116-byte request body can occupy a request-runtime worker thread for tens of seconds:

{% for x0 in range(999) %}{% for x1 in range(999) %}{% for x2 in range(999) %}{% endfor %}{% endfor %}{% endfor %}ok

The request runtime is a bounded pool (worker_threads = min(available_parallelism, 32)), so a small number of such requests can stall the chat-completions plane while /health stays green. The request-supplied gate (trust_request_chat_template, default off in Python vLLM) is not yet implemented in the Rust frontend, and the model-supplied template path has no gate at all.

This wires up minijinja's existing fuel feature and sets a total evaluation budget in build_environment (rust/src/chat/src/renderer/hf/template.rs). The budget bounds total work regardless of who supplied the template, so a hostile template is rejected quickly while legitimate templates — which only iterate over the request's messages/tools/documents — are unaffected. For reference, minijinja itself already refuses a single range() above ~1M elements; the budget here (20M fuel units) is ~20× that, well above any real chat template.

This is the primary remediation (item 1) from the issue. Implementing trust_request_chat_template and spawn_blocking-ing the render (items 2 and 4) are follow-ups and out of scope here.

Test Plan

cargo test -p vllm-chat — added two tests in template.rs:

  • test_chat_template_evaluation_is_bounded: the nested-range() bomb from the issue is now rejected (and returns quickly instead of running for ~55s).
  • test_chat_template_within_budget_renders: a legitimate template doing non-trivial work (range(50000)) still renders.

Test Result

$ cargo test -p vllm-chat
test result: ok. 267 passed; 0 failed
test result: ok. 17 passed; 0 failed
test result: ok. 16 passed; 0 failed

All existing vllm-chat tests pass (the budget does not affect any real template); cargo fmt --check and cargo clippy -p vllm-chat are clean.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@github-actions

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment /ci run whenever CI signals are needed.

Once the PR is approved or has the ready label, the PR author can also use /ci run, /ci retry, or /ci cancel. New commits do not start CI automatically.

If you have any questions, please reach out to us on Slack at https://slack.vllm.ai.

Agent Guidelines

IMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban.

🚀

@mergify mergify Bot added the rust label Aug 13, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c264e3b53f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rust/src/chat/src/renderer/hf/template.rs Outdated
@mergify

mergify Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @jaideeppyne.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Aug 18, 2026
@jaideeppyne
jaideeppyne force-pushed the fix/rust-chat-template-eval-budget branch from 53af465 to 657d120 Compare August 18, 2026 04:43
@mergify mergify Bot removed the needs-rebase label Aug 18, 2026
The Rust frontend renders a caller- or model-supplied Jinja chat template
with no evaluation budget. Cost is O(N^depth) in caller-controlled loop
bounds, and minijinja's per-`range()` limit is walked around by nesting, so
a 116-byte request body can occupy a request-runtime worker thread for tens
of seconds. With the bounded request-runtime pool, a handful of such
requests can stall the chat-completions plane.

Enable minijinja's `fuel` feature and set a total evaluation budget in
`build_environment`. A legitimate chat template only iterates over the
request's messages/tools/documents and stays far below the budget, while a
hostile template is now rejected quickly. This also covers the
model-supplied template path, which no request-level gate protects.

Fixes vllm-project#52025

Signed-off-by: jaideeppyne <jaideeppyne1997@gmail.com>
The wall-clock bound could nondeterministically fail on slow/contended CI
workers even though the fuel budget works correctly. Assert on the returned
error (evaluation stopped) instead.

Signed-off-by: jaideeppyne <jaideeppyne1997@gmail.com>
@jaideeppyne
jaideeppyne force-pushed the fix/rust-chat-template-eval-budget branch from 657d120 to 8aa9f76 Compare August 21, 2026 07:32
@jaideeppyne

Copy link
Copy Markdown
Author

Rebased onto current main (branch was ~166 commits behind) and force-pushed — the mergify conflict/staleness should now be cleared; the branch is even with main again.

Re the codex P2 note on rust/src/chat/src/renderer/hf/template.rs: the fuel test no longer asserts on wall-clock time. test_chat_template_evaluation_is_bounded asserts only that an over-budget template render returns an error (result.is_err()), i.e. the fuel budget trips and evaluation is bounded — nothing about elapsed duration — so it won't flake on slow/contended CI workers. A companion test_chat_template_within_budget_renders confirms a legitimately-sized template still renders. Both pass locally (cargo test -p vllm-chat: 16 passed, 0 failed; cargo fmt --check and cargo clippy -p vllm-chat clean).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Rust frontend renders caller-supplied chat templates with no evaluation budget: a 116-byte request body costs 55 CPU-seconds

2 participants