Skip to content

Settings::Limits: bounds on what one Load or Render may do (0262) - #601

Merged
rmorozov merged 10 commits into
masterfrom
claude/settings-limits-ul513f
Oct 19, 2026
Merged

rmorozov merged 10 commits into
masterfrom
claude/settings-limits-ul513f

Conversation

@rmorozov

@rmorozov rmorozov commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

Requested by Ruslan · project thread

Before: a host that renders untrusted templates has no way to bound them. The only limits are fixed depth limits and an internal 2^26 cap on sequence size, and both are compiled in. A {% for i in range(10**9) %} runs to completion. Nothing bounds range(), output size, template size, template loads or render time.

After: Settings::Limits has 11 fields, each enforced and tested: maxRange, maxSequenceSize, maxOutputSize, maxTemplateSize, maxTemplateLoads, maxRenderSteps, maxRenderTime, maxRenderDepth (256), maxExpressionDepth (64), maxExpressionOperators (400) and maxBlockNesting (128). The presets are UntrustedLimits() and UntrustedSettings(); the second also turns autoescape on. A limit that trips reports ErrorCode::ResourceLimitExceeded (18), with extraParams[0] the message and [1] the field name. The depth limits still report RecursionLimitExceeded. The defaults keep today's behaviour, and 0 means no limit.

The design is in docs/api-2.0.md §2.1 and docs/tasks/0262, which this PR marks done. docs/api-2.0.md §3.3 gains a paragraph.

How. With limits off, nothing new runs on the render path.

  • Steps and time. While a render with limits runs, the thread's stack-check limit is set to UINTPTR_MAX, so every existing CheckStack() takes its slow path. That path counts the step, checks the output size, and polls the clock every 16 steps, after host callables and at render end. Then it does the real stack check.
  • Parse limits. These live in a thread-local set around Parse(), and around the re-parse in FindUndeclaredVariables, so the parser's layout is unchanged.
  • Template size. It is checked before the source is copied. The stream overload, which the environment loaders also use, stops reading once the source passes the limit.
  • Other checks. range(), sequence size and template loads are checked where the old caps were.
  • Loop filters. When limits are set at Load, a loop's if filter counts the items it skips.
  • Nested renders. An RAII scope installs a render's limits and restores the previous ones, so nested renders from host callables keep their own.
  • Load comparison. Settings::operator== runs on every Load and compares the limits word by word without branches. A static_assert checks that the struct has no padding.

Perf. Measured with bench/count.py, in instructions, limits off.

  • Render: within ±0.5%, and the chat templates are −0.9% to −2.3%. The one exception is Render/plain_text at +8 instructions, the fixed cost per Render, which is recorded in the 0262 task file.
  • Load: down on every case in CI's "instruction counts vs base" job.
  • With limits on: renders are 2–4% slower. The chat templates take 550–1000 steps under UntrustedLimits().

The performance track approved the numbers on f3b25cd. The final head only adds the size check before the copy, and its preflight gate is clean.

Deliberately not here:

  • Memory is not fully bounded yet. Some string and list operations, and captured output, do not check maxSequenceSize. That is 0263; the header says the presets do not bound memory.
  • Other leftovers are filed as docs/tasks/0282:
    • one long filter may overrun the time limit;
    • parsing is quadratic in nesting depth with maxBlockNesting = 0;
    • depth spans nested renders;
    • older docs still name the old constants.

Ordering with #597 (0253). Both PRs add ResourceLimitExceeded = 18 with the same meaning, so the merge conflict is one line.

Files outside 0262's touches: src/value_visitors.h (string repeat), src/internal_value., src/load_settings.h, src/expression_parser., src/template_parser.cpp, src/template.cpp (Load overloads), CMakeLists.txt (new test file).

Roles and reviews:

  • The main session implemented the change.
  • Verifier, 2 rounds.
    • Round 1 found 4 blocking issues: a UINT64_MAX step count wrapped, a huge time limit overflowed, code 1 was reported past the 2^26 cap, and memory was not bounded. The last is deferred to 0263; the other three are fixed.
    • Round 2 found 1 blocking issue: a 10⁹-iteration row ran in the defaults pass. It is fixed, along with the non-blocking items (clock poll, an MSVC-safe max).
  • Codex: 2 findings, 2 fixed, 0 declined.
    • P1: the template size was checked only after the source was copied or read.
    • P2: FindUndeclaredVariables re-parsed without the template's limits.
    • The second review found nothing. The security review never started; per memory, Codex treats it as a normal review.
  • Red pushes in CI: none. One push got no CI because the PR had a merge conflict with master; after master was merged in, CI is green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01USAwpuAiFNpCMNpcRmwvsh

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USAwpuAiFNpCMNpcRmwvsh
…Load, output checked per step

With every limit at its default a render takes no new per-step work: a render with
limits makes CheckStack take its slow path, which counts the step and checks the
output size. Parse limits live in a thread-local set for the Load.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USAwpuAiFNpCMNpcRmwvsh
…nternal sequence cap

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USAwpuAiFNpCMNpcRmwvsh
…9 loop in the defaults pass

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USAwpuAiFNpCMNpcRmwvsh
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USAwpuAiFNpCMNpcRmwvsh
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USAwpuAiFNpCMNpcRmwvsh
@rmorozov rmorozov self-assigned this Oct 18, 2026
…s uint64

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USAwpuAiFNpCMNpcRmwvsh
…ds as uint64

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USAwpuAiFNpCMNpcRmwvsh
rmorozov pushed a commit that referenced this pull request Oct 18, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013sRZrdyAGVT1hBgARAkNAE
…elector and the cycle collector); close 0262

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USAwpuAiFNpCMNpcRmwvsh

Copy link
Copy Markdown
Member Author

@codex review

@codex security review


Generated by Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T08:29:56.003006Z 4068f2f Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: f3b25cd6d9

ℹ️ 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 src/template_impl.h Outdated
Comment thread src/template_impl.h
…rce; parse limits in FindUndeclaredVariables

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USAwpuAiFNpCMNpcRmwvsh

Copy link
Copy Markdown
Member Author

@codex review

Re-requesting after 4068f2f: the template-size check now runs before the source is copied or read, and parse limits now apply in FindUndeclaredVariables.


Generated by Claude Code

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: 4068f2fee5

ℹ️ 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".

rmorozov pushed a commit that referenced this pull request Oct 19, 2026
rmorozov added a commit that referenced this pull request Oct 19, 2026
@rmorozov
rmorozov merged commit 8b6d9ce into master Oct 19, 2026
41 checks passed
rmorozov pushed a commit that referenced this pull request Oct 19, 2026
One ResourceLimitError (src/engine_errors.h) serves Settings::Limits, named by
its field, and the built-in caps, without one; CurrentExceptionToError reports
both as ResourceLimitExceeded with the field as extraParams[1] when present,
so Render's own catch for it is gone. The built-in sequence cap throws it
instead of std::length_error. exception.h, engine_errors.h and
error_boundary.h are wrapped in v2 by scripts/abi_namespace.py.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013sRZrdyAGVT1hBgARAkNAE
rmorozov pushed a commit that referenced this pull request Oct 19, 2026
#601 brought TemplateDict with defaulted noexcept moves over an
OrderedMap, whose moves allocate on MSVC; a bad_alloc there in a dict
literal's render terminated (jinja2cpp_alloc_tests, Windows static jobs).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013sRZrdyAGVT1hBgARAkNAE
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants