CVE-2026-74783
Scriban 6.6.0 through 7.2.0 Parser Recursion Denial of Service
Scriban versions 6.6.0 through 7.2.0 contain a non-enforcing ExpressionDepthLimit guard that fails to stop recursive descent parsing of deeply nested expressions. Attackers can supply templates with deeply nested parentheses, array initializers, object initializers, or unary operators to trigger an uncatchable StackOverflowException that immediately terminates the host process.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- None
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
8.7
Scriban 6.6.0 through 7.2.0 Parser Recursion Denial of Service
Scriban versions 6.6.0 through 7.2.0 contain a non-enforcing ExpressionDepthLimit guard that fails to stop recursive descent parsing of deeply nested expressions. Attackers can supply templates with deeply nested parentheses, array initializers, object initializers, or unary operators to trigger an uncatchable StackOverflowException that immediately terminates the host process.
- Record
CVE-2026-74783- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N - CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-674
- Updated
- 2026-11-02
EUVDEUVD-2026-59949
8.7
Scriban versions 6.6.0 through 7.2.0 contain a non-enforcing ExpressionDepthLimit guard that fails to stop recursive descent parsing of deeply nested expressions. Attackers can supply templates with deeply nested parentheses, array initializers, object initializers, or unary operators to trigger an uncatchable StackOverflowException that immediately terminates the host process.
- Record
EUVD-2026-59949- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-6q7j-xr26-3h2c
5.5
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)
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:
// 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:
// 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:
// src/Scriban/Parsing/Parser.Expressions.cs:113 + 181-182
EnterExpression();
try
{
...
case TokenType.OpenParen:
leftOperand = ParseParenthesis(); // recurses back into ParseExpression
// 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…
- Record
GHSA-6q7j-xr26-3h2c- CVSS 4.0
- 5.5 Medium
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 - Weakness
- CWE-674
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-jg4x-p32p-q6xr
8.7
Scriban versions 6.6.0 through 7.2.0 contain a non-enforcing ExpressionDepthLimit guard that...
Scriban versions 6.6.0 through 7.2.0 contain a non-enforcing ExpressionDepthLimit guard that fails to stop recursive descent parsing of deeply nested expressions. Attackers can supply templates with deeply nested parentheses, array initializers, object initializers, or unary operators to trigger an uncatchable StackOverflowException that immediately terminates the host process.
- Record
GHSA-jg4x-p32p-q6xr- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-674
- Updated
- 2026-11-02
NVD
8.7
Scriban versions 6.6.0 through 7.2.0 contain a non-enforcing ExpressionDepthLimit guard that fails to stop recursive descent parsing of deeply nested expressions. Attackers can supply templates with deeply nested parentheses, array initializers, object initializers, or unary operators to trigger an uncatchable StackOverflowException that immediately terminates the host process.
- Record
CVE-2026-74783- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-674
- Updated
- 2026-11-02
OSV
8.7
Scriban 6.6.0 through 7.2.0 Parser Recursion Denial of Service
Scriban versions 6.6.0 through 7.2.0 contain a non-enforcing ExpressionDepthLimit guard that fails to stop recursive descent parsing of deeply nested expressions. Attackers can supply templates with deeply nested parentheses, array initializers, object initializers, or unary operators to trigger an uncatchable StackOverflowException that immediately terminates the host process.
- Record
CVE-2026-74783- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N - Weakness
- CWE-674
- Updated
- 2026-11-02
OSVGHSA-6q7j-xr26-3h2c
5.5
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)
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:
// 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:
// 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:
// src/Scriban/Parsing/Parser.Expressions.cs:113 + 181-182
EnterExpression();
try
{
...
case TokenType.OpenParen:
leftOperand = ParseParenthesis(); // recurses back into ParseExpression
// 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…
- Record
GHSA-6q7j-xr26-3h2c- CVSS 4.0
- 5.5 Medium
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 - Weakness
- CWE-674
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
ScribanNuGet |
6.6.0 to 7.2.0 | 7.2.1 |
Scriban.SignedNuGet |
6.6.0 to 7.2.0 | 7.2.1 |
ScribanNuGet |
6.6.0 up to 7.2.1, plus listed versions 7.0.07.0.17.0.27.0.37.0.4 Show 4 moreShow less7.0.57.0.67.1.07.2.0 |
7.2.1 |
Scriban.SignedNuGet |
6.6.0 up to 7.2.1, plus listed versions 7.0.07.0.17.0.27.0.37.0.4 Show 4 moreShow less7.0.57.0.67.1.07.2.0 |
7.2.1 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-74783 is a high vulnerability (CVSS 8.7) in Scriban, published June 26, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.34%, the 27th percentile. Fixed in Scriban 7.2.1.
References
github.com/scriban/scriban/security/advisories/GHSA-6q7j-xr26-3h2c
vulncheck.com/advisories/scriban-through-parser-recursion-denial-of-service
github.com/advisories/GHSA-6q7j-xr26-3h2c
nvd.nist.gov/vuln/detail/CVE-2026-74783
github.com/advisories/GHSA-jg4x-p32p-q6xr
github.com/CVEProject/cvelistV5/tree/main/cves/2026/74xxx/CVE-2026-74783.json
github.com/scriban/scriban
Left out here: the rest of 2 source descriptions. The JSON record has them.