Mar 20, 2026·5 min read·58 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.
CVE-2026-63349 is a critical privilege-dropping bypass vulnerability in the AnyIO asynchronous framework (versions 4.14.0 and 4.14.1) on POSIX platforms. Due to a variable assignment typo, supplementary groups specified by the developer are not correctly propagated to the execution backend, resulting in subprocesses retaining the parent process's elevated supplementary group permissions.
CVE-2026-63406 is an information disclosure vulnerability in AnyCable-go prior to version 1.6.15. The built-in telemetry client is enabled by default with a hardcoded public authentication token ('secret'). This client digests highly sensitive configuration parameters and command-line arguments, including JWT secrets and RPC secrets, into a stable SHA-256 fingerprint. This fingerprint is sent over public networks, exposing those administrative secrets to offline dictionary and brute-force attacks if intercepted.
CVE-2026-84992 is a Cross-Site Scripting (XSS) vulnerability affecting md-editor-v3 before version 6.5.4. It occurs because the fenced-code block language parser directly interpolates unescaped language metadata into unquoted HTML attributes inside the custom rendering callback. This bypasses the built-in XSSPlugin which runs during the parsing phase, before rendering.
CVE-2026-81505 is a high-severity Broken Object Level Authorization (BOLA) / Insecure Direct Object Reference (IDOR) vulnerability in Convoy, a cloud-native webhooks gateway. In affected versions prior to 26.6.8, the single-item Source retrieval API endpoint authorizes project access but fails to confirm if the requested Source belongs to that specific project. This logical flaw allows authenticated users or project-scoped API key holders to bypass tenant isolation boundaries and retrieve unredacted, plaintext message broker credentials for Apache Kafka, Amazon SQS, RabbitMQ, and Google Cloud Pub/Sub belonging to other tenants. This issue is fully patched in version 26.6.8.
CVE-2026-77339 is a critical security vulnerability in Process Compose before version 1.120.0. The Model Context Protocol (MCP) Server-Sent Events (SSE) listener transport subsystem fails to validate the HTTP Host and Origin headers, and does not enforce authentication. This omissions expose local loopback listeners to DNS rebinding attacks orchestrated by malicious remote websites visited by developers, enabling unauthorized process control and arbitrary command execution.
CVE-2026-77301 is a critical uncontrolled resource allocation vulnerability in the popular Node.js library adm-zip (versions prior to 0.6.1). During ZIP decompression of asynchronous entries, the library trusts the uncompressed size metadata declared in the central directory headers. Because Node.js's streaming zlib API completely ignores the maxOutputLength configuration, a crafted ZIP archive (decompression bomb) causes the application to continually allocate resident memory buffers on the heap without limits, causing rapid memory exhaustion and a process-level Out-of-Memory (OOM) crash.