Mar 20, 2026·5 min read·4 visits
Scriban versions prior to 6.6.0 fail to enforce default recursion depth limits during template parsing and object evaluation. Deeply nested template syntax exhausts the thread stack, causing the .NET host application to terminate immediately. The vulnerability is remediated in version 6.6.0 by enforcing safe default limits on expression depth, object recursion, and output buffer size.
The Scriban templating engine prior to version 6.6.0 contains an uncontrolled recursion vulnerability in its recursive-descent parser. Attackers providing maliciously nested templates can trigger a StackOverflowException, resulting in an unrecoverable process crash and complete denial of service.
The Scriban templating engine prior to version 6.6.0 contains an uncontrolled recursion vulnerability (CWE-674). Applications parsing untrusted templates using affected versions expose an unauthenticated denial-of-service attack vector. The vulnerability originates from the engine's recursive-descent parser, which processes template syntax without enforcing default structural depth limits.
When the parser encounters specific syntactic elements, it allocates stack frames proportional to the nesting depth of the input. Without a boundary limit, malicious inputs rapidly consume the available thread stack space. This mechanism reliably triggers a process-level crash in the hosting .NET application.
The attack surface extends beyond template parsing into the object graph evaluation phase. The evaluation logic lacked default constraints on object recursion and output buffer sizes. Attackers exploiting this secondary vector induce memory exhaustion, compounding the availability impact on the host system.
Scriban implements a recursive-descent parser to evaluate template expressions. The parser relies on method recursion to handle nested syntactical structures, such as parenthesized groups or member access chains. Each nested element invokes the EnterExpression() method, adding a new frame to the execution call stack.
In versions prior to 6.6.0, the ExpressionDepthLimit property within ParserOptions was defined as a nullable integer and initialized to null. The parser logic did not perform boundary checks against the recursion depth unless developers explicitly configured a limit. Consequently, the parser unconditionally traversed maliciously nested input strings.
The .NET runtime handles thread stack exhaustion by throwing a StackOverflowException. This specific exception is non-recoverable and terminates the host process immediately without allowing exception handling blocks to execute. A separate flaw in TemplateContext.cs allowed unbounded object graph traversal during string serialization, as ObjectRecursionLimit and LimitToString defaulted to zero.
The vulnerability stems from missing default limits in configuration classes and the absence of boundary enforcement in the parsing routines. The fix spans two primary commits to enforce safety boundaries globally. The first commit (a6fe6074199e5c04f4d29dc8d8e652b24d33e3e4) establishes safe default limits within the configuration objects.
// Vulnerable state: ParserOptions.cs
public int? ExpressionDepthLimit { get; set; } = null;
// Patched state: ParserOptions.cs
public int? ExpressionDepthLimit { get; set; } = 250;The second commit (b5ac4bf30459fdc76964e3f751e16f7e96079ea7) modifies the parser itself to enforce the depth limit actively, ensuring backward compatibility for users who explicitly passed null to bypass checks.
// Patched state: Parser.Expressions.cs
private void EnterExpression() {
var limit = Options.ExpressionDepthLimit ?? 250;
if (CurrentDepth > limit) {
// Throw safe parser error instead of StackOverflowException
}
CurrentDepth++;
}Additionally, limits were applied to the evaluation context to prevent Out-Of-Memory (OOM) conditions during object resolution. The LimitToString property now caps output buffers at 1,048,576 characters (1MB), and ObjectRecursionLimit restricts graph traversal depth to 20 levels.
Exploitation requires the attacker to submit a crafted template string to an endpoint that processes input using Template.Parse(). No authentication is necessary if the application exposes template functionality to anonymous users. The attack relies solely on creating deep syntactic nesting.
The core exploitation technique leverages consecutive grouping characters. By opening thousands of parentheses without closing them, the attacker forces the parser into deep recursion. The following proof-of-concept constructs an excessively nested string that crashes the parsing process.
// Proof of Concept generating 10,000 nested layers
var templateText = new string('(', 10000) + "1" + new string(')', 10000);
Template.Parse(templateText);Upon executing Template.Parse(), the host process immediately throws a StackOverflowException. The application terminates, dropping all current connections and ceasing to process further requests. Alternative payloads include nested if statements or recursive wrap blocks to achieve identical denial-of-service conditions.
The vulnerability yields a complete loss of availability, reflected in the CVSS score of 7.5 (High). An attacker requires minimal resources to crash the targeted service. The impact is systemic, affecting the entire application hosting the Scriban engine.
The use of StackOverflowException is particularly lethal in the .NET runtime environment. Unlike standard runtime exceptions, stack overflows cannot be caught using standard try-catch blocks. The CLR enforces immediate process termination to preserve memory integrity.
Applications functioning as multi-tenant platforms or rendering user-supplied markdown and templates are highly susceptible. The secondary OOM vectors similarly result in process termination or extreme performance degradation, requiring manual restarts of the affected service containers or processes.
The primary remediation strategy requires upgrading the Scriban NuGet package to version 6.6.0 or later. This release introduces enforced safe defaults for both parsing depth and evaluation buffer sizes. The update does not introduce breaking API changes, allowing seamless integration for existing implementations.
If immediate patching is not technically feasible, developers must manually enforce configuration limits before parsing untrusted input. The ParserOptions and TemplateContext objects must be instantiated with explicit boundaries prior to invocation.
// Manual Mitigation Pattern
var options = new ParserOptions { ExpressionDepthLimit = 250 };
var context = new TemplateContext {
ObjectRecursionLimit = 20,
LimitToString = 1048576
};Security teams should implement static code analysis rules to detect instances of Template.Parse() or Template.Render() operating without custom configuration limits on older package versions. Monitoring for unexpected process terminations accompanied by stack overflow event logs serves as a reactive detection mechanism.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Scriban Alexandre Mutel (xoofx) | < 6.6.0 | 6.6.0 |
| Attribute | Detail |
|---|---|
| Vulnerability Class | Uncontrolled Recursion (CWE-674) |
| Attack Vector | Network (Remote) |
| CVSS v3.1 Score | 7.5 (High) |
| Impact | Denial of Service (Process Crash) |
| Authentication Required | None |
| Exploit Status | Proof of Concept Available |
The program does not properly control the amount of recursion that takes place, consuming excessive resources, such as allocated memory or the program stack.