Scriban has Uncontrolled Recursion in `object.to_json` Causing Unrecoverable Process Crash via StackOverflowException
## Summary The `object.to_json` builtin function in Scriban performs recursive JSON serialization via an internal `WriteValue()` static local function that has no depth limit, no circular reference detection, and no stack overflow guard. A Scriban template containing a self-referencing object passed to `object.to_json` triggers unbounded recursion, causing a `StackOverflowException` that terminates the hosting .NET process. This is a fatal, unrecoverable crash — `StackOverflowException` cannot be caught by user code in .NET. ## Details The vulnerable code is the `WriteValue()` static local function at `src/Scriban/Functions/ObjectFunctions.cs:494`: ```csharp static void WriteValue(TemplateContext context, Utf8JsonWriter writer, object value) { var type = value?.GetType() ?? typeof(object); if (value is null || value is string || value is bool || type.IsPrimitiveOrDecimal() || value is IFormattable) { JsonSerializer.Serialize(writer, value, type); } else if (value is IList || type.IsArray) { writer.WriteStartArray(); foreach (var x in context.ToList(context.CurrentSpan, value)) { WriteValue(context, writer, x); // recursive, no depth check } writer.WriteEndArray(); } else { writer.WriteStartObject(); var accessor = context.GetMemberAccessor(value); foreach (var member in accessor.GetMembers(context, context.CurrentSpan, value)) { if (accessor.TryGetValue(context, context.CurrentSpan, value, member, out var memberValue)) { writer.WritePropertyName(member); WriteValue(context, writer, memberValue); // recursive, no depth check } } writer.WriteEndObject(); } } ``` This function has **none** of the safety mechanisms present in other recursive paths: - `ObjectToString()` at `TemplateContext.Helpers.cs:98` checks `ObjectRecursionLimit` (default 20) - `EnterRecursive()` at `TemplateContext.cs:957` calls `RuntimeHelpers.EnsureSufficientExecutionStack()` - `CheckAbort()` at `TemplateContext.cs:464` also calls `EnsureSufficientExecutionStack()` The `WriteValue()` function bypasses all of these because it is a static local function that only takes the `TemplateContext` for member access — it never calls `EnterRecursive()`, never checks `ObjectRecursionLimit`, and never calls `EnsureSufficientExecutionStack()`. **Execution flow:** 1. Template creates a ScriptObject: `{{ x = {} }}` 2. Sets a self-reference: `x.self = x` — stores a reference in `ScriptObject.Store` dictionary 3. Pipes to `object.to_json`: `x | object.to_json` → calls `ToJson()` at line 477 4. `ToJson()` calls `WriteValue(context, writer, value)` at line 488 5. `WriteValue` enters the `else` branch (line 515), gets members via accessor, finds "self" 6. `TryGetValue` returns `x` itself, `WriteValue` recurses with the same object — infinite loop 7. `StackOverflowException` is thrown — **fatal, cannot be caught, process terminates** ## PoC ```scriban {{ x = {}; x.self = x; x | object.to_json }} ``` In a hosting application: ```csharp using Scriban; // This will crash the entire process with StackOverflowException var template = Template.Parse("{{ x = {}; x.self = x; x | object.to_json }}"); var result = template.Render(); // FATAL: process terminates here ``` Even without circular references, deeply nested objects can exhaust the stack since no depth limit is enforced: ```scriban {{ a = {} b = {inner: a} c = {inner: b} d = {inner: c} # ... continue nesting ... result = deepest | object.to_json }} ``` ## Impact - **Process crash DoS**: Any application embedding Scriban for user-provided templates (CMS platforms, email template engines, report generators, static site generators) can be crashed by a single malicious template. The crash is unrecoverable — `StackOverflowException` terminates the .NET process. - **No try/catch protection possible**: Unlike most exceptions, `StackOverflowException` cannot be caught by application code. The hosting application cannot wrap `template.Render()` in a try/catch to survive this. - **No authentication required**: `object.to_json` is a default builtin function (registered in `BuiltinFunctions.cs`), available in all Scriban templates unless explicitly removed. - **Trivial to exploit**: The PoC is a single line of template code. ## Recommended Fix Add a depth counter parameter to `WriteValue()` and check it against `ObjectRecursionLimit`, consistent with how `ObjectToString` is protected. Also add `EnsureSufficientExecutionStack()` as a safety net: ```csharp static void WriteValue(TemplateContext context, Utf8JsonWriter writer, object value, int depth = 0) { if (context.ObjectRecursionLimit != 0 && depth > context.ObjectRecursionLimit) { throw new ScriptRuntimeException(context.CurrentSpan, $"Exceeding object recursion limit `{context.ObjectRecursion
AI Analysis
Technical Summary
The `object.to_json` builtin function in Scriban recursively serializes objects to JSON via a static local function `WriteValue()` that does not enforce any recursion depth limit, circular reference detection, or stack overflow protection. When a self-referencing object or deeply nested structure is passed to `object.to_json`, `WriteValue()` recurses infinitely, causing a StackOverflowException that terminates the hosting .NET process. Unlike other recursive paths in Scriban, `WriteValue()` bypasses safeguards such as `ObjectRecursionLimit` checks and `EnsureSufficientExecutionStack()`. This results in a fatal, unrecoverable crash that cannot be caught by user code. The vulnerability affects all Scriban versions before 7.0.0 and can be triggered by a simple template snippet.
Potential Impact
This vulnerability causes a denial-of-service condition by crashing the entire hosting process due to an unrecoverable StackOverflowException. Applications embedding Scriban for user-provided templates (such as CMS platforms, email template engines, report generators, or static site generators) can be terminated by a single malicious template using `object.to_json` with self-referencing or deeply nested objects. There is no way for the hosting application to catch or recover from this exception, making the crash fatal. No authentication or special privileges are required to exploit this vulnerability, and the exploit is trivial to execute.
Mitigation Recommendations
Patch status is not yet confirmed — check the vendor advisory for current remediation guidance. The recommended fix is to add a recursion depth counter and enforce the `ObjectRecursionLimit` in the `WriteValue()` function, along with calling `EnsureSufficientExecutionStack()` to prevent stack overflow. Until an official fix is available, consider removing or disabling the `object.to_json` builtin function in Scriban templates to prevent exploitation.
Scriban has Uncontrolled Recursion in `object.to_json` Causing Unrecoverable Process Crash via StackOverflowException
Description
## Summary The `object.to_json` builtin function in Scriban performs recursive JSON serialization via an internal `WriteValue()` static local function that has no depth limit, no circular reference detection, and no stack overflow guard. A Scriban template containing a self-referencing object passed to `object.to_json` triggers unbounded recursion, causing a `StackOverflowException` that terminates the hosting .NET process. This is a fatal, unrecoverable crash — `StackOverflowException` cannot be caught by user code in .NET. ## Details The vulnerable code is the `WriteValue()` static local function at `src/Scriban/Functions/ObjectFunctions.cs:494`: ```csharp static void WriteValue(TemplateContext context, Utf8JsonWriter writer, object value) { var type = value?.GetType() ?? typeof(object); if (value is null || value is string || value is bool || type.IsPrimitiveOrDecimal() || value is IFormattable) { JsonSerializer.Serialize(writer, value, type); } else if (value is IList || type.IsArray) { writer.WriteStartArray(); foreach (var x in context.ToList(context.CurrentSpan, value)) { WriteValue(context, writer, x); // recursive, no depth check } writer.WriteEndArray(); } else { writer.WriteStartObject(); var accessor = context.GetMemberAccessor(value); foreach (var member in accessor.GetMembers(context, context.CurrentSpan, value)) { if (accessor.TryGetValue(context, context.CurrentSpan, value, member, out var memberValue)) { writer.WritePropertyName(member); WriteValue(context, writer, memberValue); // recursive, no depth check } } writer.WriteEndObject(); } } ``` This function has **none** of the safety mechanisms present in other recursive paths: - `ObjectToString()` at `TemplateContext.Helpers.cs:98` checks `ObjectRecursionLimit` (default 20) - `EnterRecursive()` at `TemplateContext.cs:957` calls `RuntimeHelpers.EnsureSufficientExecutionStack()` - `CheckAbort()` at `TemplateContext.cs:464` also calls `EnsureSufficientExecutionStack()` The `WriteValue()` function bypasses all of these because it is a static local function that only takes the `TemplateContext` for member access — it never calls `EnterRecursive()`, never checks `ObjectRecursionLimit`, and never calls `EnsureSufficientExecutionStack()`. **Execution flow:** 1. Template creates a ScriptObject: `{{ x = {} }}` 2. Sets a self-reference: `x.self = x` — stores a reference in `ScriptObject.Store` dictionary 3. Pipes to `object.to_json`: `x | object.to_json` → calls `ToJson()` at line 477 4. `ToJson()` calls `WriteValue(context, writer, value)` at line 488 5. `WriteValue` enters the `else` branch (line 515), gets members via accessor, finds "self" 6. `TryGetValue` returns `x` itself, `WriteValue` recurses with the same object — infinite loop 7. `StackOverflowException` is thrown — **fatal, cannot be caught, process terminates** ## PoC ```scriban {{ x = {}; x.self = x; x | object.to_json }} ``` In a hosting application: ```csharp using Scriban; // This will crash the entire process with StackOverflowException var template = Template.Parse("{{ x = {}; x.self = x; x | object.to_json }}"); var result = template.Render(); // FATAL: process terminates here ``` Even without circular references, deeply nested objects can exhaust the stack since no depth limit is enforced: ```scriban {{ a = {} b = {inner: a} c = {inner: b} d = {inner: c} # ... continue nesting ... result = deepest | object.to_json }} ``` ## Impact - **Process crash DoS**: Any application embedding Scriban for user-provided templates (CMS platforms, email template engines, report generators, static site generators) can be crashed by a single malicious template. The crash is unrecoverable — `StackOverflowException` terminates the .NET process. - **No try/catch protection possible**: Unlike most exceptions, `StackOverflowException` cannot be caught by application code. The hosting application cannot wrap `template.Render()` in a try/catch to survive this. - **No authentication required**: `object.to_json` is a default builtin function (registered in `BuiltinFunctions.cs`), available in all Scriban templates unless explicitly removed. - **Trivial to exploit**: The PoC is a single line of template code. ## Recommended Fix Add a depth counter parameter to `WriteValue()` and check it against `ObjectRecursionLimit`, consistent with how `ObjectToString` is protected. Also add `EnsureSufficientExecutionStack()` as a safety net: ```csharp static void WriteValue(TemplateContext context, Utf8JsonWriter writer, object value, int depth = 0) { if (context.ObjectRecursionLimit != 0 && depth > context.ObjectRecursionLimit) { throw new ScriptRuntimeException(context.CurrentSpan, $"Exceeding object recursion limit `{context.ObjectRecursion
CVSS v3.1
Score 7.5high
Affected software
Run on your own infrastructure? Check whether these packages are installed with threat-finder — our free open-source scanner.
Weaknesses
AI-Powered Analysis
Machine-generated threat intelligence
Technical Analysis
The `object.to_json` builtin function in Scriban recursively serializes objects to JSON via a static local function `WriteValue()` that does not enforce any recursion depth limit, circular reference detection, or stack overflow protection. When a self-referencing object or deeply nested structure is passed to `object.to_json`, `WriteValue()` recurses infinitely, causing a StackOverflowException that terminates the hosting .NET process. Unlike other recursive paths in Scriban, `WriteValue()` bypasses safeguards such as `ObjectRecursionLimit` checks and `EnsureSufficientExecutionStack()`. This results in a fatal, unrecoverable crash that cannot be caught by user code. The vulnerability affects all Scriban versions before 7.0.0 and can be triggered by a simple template snippet.
Potential Impact
This vulnerability causes a denial-of-service condition by crashing the entire hosting process due to an unrecoverable StackOverflowException. Applications embedding Scriban for user-provided templates (such as CMS platforms, email template engines, report generators, or static site generators) can be terminated by a single malicious template using `object.to_json` with self-referencing or deeply nested objects. There is no way for the hosting application to catch or recover from this exception, making the crash fatal. No authentication or special privileges are required to exploit this vulnerability, and the exploit is trivial to execute.
Mitigation Recommendations
Patch status is not yet confirmed — check the vendor advisory for current remediation guidance. The recommended fix is to add a recursion depth counter and enforce the `ObjectRecursionLimit` in the `WriteValue()` function, along with calling `EnsureSufficientExecutionStack()` to prevent stack overflow. Until an official fix is available, consider removing or disabling the `object.to_json` builtin function in Scriban templates to prevent exploitation.
Technical Details
- Gcve Source
- db.gcve.eu
- Osv Id
- GHSA-xcx6-vp38-8hr5
- Osv Schema Version
- 1.4.0
- Aliases
- []
- Ecosystems
- ["NuGet"]
- Database Specific Severity
- HIGH
- Cvss Version
- 3.1
Threat ID: 6a4c346127e9c79719600acf
Added to database: 07/06/2026, 23:04:01 UTC
Last enriched: 07/06/2026, 23:33:23 UTC
Last updated: 07/31/2026, 12:27:30 UTC
Views: 7
Community Reviews
0 reviewsCrowdsource mitigation strategies, share intel context, and vote on the most helpful responses. Sign in to add your voice and help keep defenders ahead.
Want to contribute mitigation steps or threat intel context? Sign in or create an account to join the community discussion.
Actions
Updates to AI analysis require Pro Console access. Upgrade inside Console → Billing.
External Links
Need more coverage?
Upgrade to Pro Console for AI refresh and higher limits.
For incident response and remediation, OffSeq services can help resolve threats faster.
Latest Threats
Check if your credentials are on the dark web
Instant breach scanning across billions of leaked records. Free tier available.