{"id":"CVE-2026-74785","aliases":["EUVD-2026-59951","GHSA-q5qx-vc88-69hw","GHSA-xw6w-9jjh-p9cr"],"status":"published","title":"Scriban before 7.0.0 Denial of Service via Unbounded Resource Consumption","description":"Scriban before 7.0.0 contains three distinct denial-of-service vulnerabilities in expression evaluation that bypass existing safety controls through unbounded string multiplication, uncontrolled BigInteger shift operations, and LoopLimit bypass via range enumeration in builtin functions. Attackers who can supply templates can cause out-of-memory exceptions or CPU exhaustion, typically terminating the entire host process.","severity":{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-09","score":0.00476,"percentile":0.41408,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-400"],"published":"2026-03-24T22:16:01Z","modified":"2026-10-28T02:30:52Z","fixes":[{"ecosystem":"NuGet","package":"Scriban","fixed_in":["7.0.0"],"sources":["ghsa","osv"]},{"ecosystem":"NuGet","package":"Scriban.Signed","fixed_in":["7.0.0"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"NuGet","package":"Scriban","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"7.0.0"}]}]},{"source":"ghsa","ecosystem":"NuGet","package":"Scriban.Signed","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"7.0.0"}]}]},{"source":"osv","ecosystem":"NuGet","package":"Scriban","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"7.0.0"}]}],"versions":["0.1.0","0.10.0","0.11.0","0.12.0","0.12.1","0.13.0","0.14.0","0.15.0","0.16.0","0.2.0","0.2.1","0.2.2","0.3.0","0.3.1","0.3.1-pre028","0.4.0","0.5.0","0.6.0","0.7.0","0.9.0","0.9.0-pre100","0.9.1","1.0.0","1.0.0-beta-001","1.0.0-beta-002","1.0.0-beta-003","1.0.0-beta-004","1.0.0-beta-005","1.0.0-beta-006","1.1.0","1.1.1","1.2.0","1.2.1","1.2.2","1.2.3","1.2.4","1.2.5","1.2.6","1.2.7","1.2.8","1.2.9","2.0.0","2.0.0-alpha-001","2.0.0-alpha-002","2.0.0-alpha-003","2.0.0-alpha-004","2.0.0-alpha-005","2.0.0-alpha-006","2.0.1","2.1.0","2.1.1","2.1.2","2.1.3","2.1.4","3.0.0","3.0.1","3.0.2","3.0.3","3.0.4","3.0.5","3.0.6","3.0.7","3.1.0","3.2.0","3.2.1","3.2.2","3.3.0","3.3.1","3.3.2","3.3.3","3.4.0","3.4.1","3.4.2","3.5.0","3.6.0","3.7.0","3.8.0","3.8.1","3.8.2","3.9.0","4.0.0","4.0.1","4.0.2","4.1.0","5.0.0","5.1.0","5.10.0","5.11.0","5.12.0","5.12.1","5.2.0","5.3.0","5.4.0","5.4.1","5.4.2","5.4.3","5.4.4","5.4.5","5.4.6","5.5.0","5.5.1","5.5.2","5.6.0","5.7.0","5.8.0","5.9.0","5.9.1","6.0.0","6.1.0","6.2.0","6.2.1","6.3.0","6.4.0","6.5.0","6.5.1","6.5.2","6.5.3","6.5.4","6.5.5","6.5.6","6.5.7","6.5.8","6.6.0"]},{"source":"osv","ecosystem":"NuGet","package":"Scriban.Signed","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"7.0.0"}]}],"versions":["0.10.0","0.11.0","0.12.0","0.12.1","0.13.0","0.14.0","0.15.0","0.16.0","0.9.0","0.9.1","1.0.0","1.0.0-beta-001","1.0.0-beta-002","1.0.0-beta-003","1.0.0-beta-004","1.0.0-beta-005","1.0.0-beta-006","1.1.0","1.1.1","1.2.0","1.2.1","1.2.2","1.2.3","1.2.4","1.2.5","1.2.6","1.2.7","1.2.8","1.2.9","2.0.0","2.0.0-alpha-001","2.0.0-alpha-002","2.0.0-alpha-003","2.0.0-alpha-004","2.0.0-alpha-005","2.0.0-alpha-006","2.0.1","2.1.0","2.1.1","2.1.2","2.1.3","2.1.4","3.0.0","3.0.1","3.0.2","3.0.3","3.0.4","3.0.5","3.0.6","3.0.7","3.1.0","3.2.0","3.2.1","3.2.2","3.3.0","3.3.1","3.3.2","3.3.3","3.4.0","3.4.1","3.4.2","3.5.0","3.6.0","3.7.0","3.8.0","3.8.1","3.8.2","3.9.0","4.0.0","4.0.1","4.0.2","4.1.0","5.0.0","5.1.0","5.10.0","5.11.0","5.12.0","5.12.1","5.2.0","5.3.0","5.4.0","5.4.1","5.4.2","5.4.3","5.4.4","5.4.5","5.4.6","5.5.0","5.5.1","5.5.2","5.6.0","5.7.0","5.8.0","5.9.0","5.9.1","6.0.0","6.1.0","6.2.0","6.2.1","6.3.0","6.4.0","6.5.0","6.5.1","6.5.2","6.5.3","6.5.4","6.5.5","6.5.6","6.5.7","6.5.8","6.6.0"]}],"products":[{"vendor":"scriban","product":"scriban"}],"references":[{"url":"https://github.com/scriban/scriban/security/advisories/GHSA-xw6w-9jjh-p9cr","tags":["vendor-advisory","exploit","advisory","web"]},{"url":"https://www.vulncheck.com/advisories/scriban-before-denial-of-service-via-unbounded-resource-consumption","tags":["third-party-advisory","advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-74785","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-q5qx-vc88-69hw"},{"url":"https://github.com/advisories/GHSA-xw6w-9jjh-p9cr"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/74xxx/CVE-2026-74785.json","tags":["advisory"]},{"url":"https://github.com/scriban/scriban","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-74785","title":"Scriban before 7.0.0 Denial of Service via Unbounded Resource Consumption","description":"Scriban before 7.0.0 contains three distinct denial-of-service vulnerabilities in expression evaluation that bypass existing safety controls through unbounded string multiplication, uncontrolled BigInteger shift operations, and LoopLimit bypass via range enumeration in builtin functions. Attackers who can supply templates can cause out-of-memory exceptions or CPU exhaustion, typically terminating the entire host process.","status":"published","published":"2026-08-16T13:14:11.187Z","modified":"2026-08-17T15:34:11.11Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high","assessor":"cna"},{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium","assessor":"cna"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T14:20:03.081354Z"}],"euvd":[{"id":"EUVD-2026-59951","description":"Scriban before 7.0.0 contains three distinct denial-of-service vulnerabilities in expression evaluation that bypass existing safety controls through unbounded string multiplication, uncontrolled BigInteger shift operations, and LoopLimit bypass via range enumeration in builtin functions. Attackers who can supply templates can cause out-of-memory exceptions or CPU exhaustion, typically terminating the entire host process.","status":"published","published":"2026-08-16T13:14:11Z","modified":"2026-08-17T15:34:11Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"}],"updated_at":"2026-11-02T18:01:49.335011Z"}],"ghsa":[{"id":"GHSA-q5qx-vc88-69hw","title":"Scriban before 7.0.0 contains three distinct denial-of-service vulnerabilities in expression...","description":"Scriban before 7.0.0 contains three distinct denial-of-service vulnerabilities in expression evaluation that bypass existing safety controls through unbounded string multiplication, uncontrolled BigInteger shift operations, and LoopLimit bypass via range enumeration in builtin functions. Attackers who can supply templates can cause out-of-memory exceptions or CPU exhaustion, typically terminating the entire host process.","status":"published","published":"2026-08-16T15:30:26Z","modified":"2026-08-16T15:30:33Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/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","level":"high"},{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T15:10:32.287939Z"},{"id":"GHSA-xw6w-9jjh-p9cr","title":"Scriban has Multiple Denial-of-Service Vectors via Unbounded Resource Consumption During Expression Evaluation","description":"## Summary\n\nScriban's expression evaluation contains three distinct code paths that allow an attacker who can supply a template to cause denial of service through unbounded memory allocation or CPU exhaustion. The existing safety controls (`LimitToString`, `LoopLimit`) do not protect these paths, giving applications a false sense of safety when evaluating untrusted templates.\n\n## Details\n\n### Vector 1: Unbounded string multiplication\n\nIn `ScriptBinaryExpression.cs`, the `CalculateToString` method handles the `string * int` operator by looping without any upper bound:\n\n```csharp\n// src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs:319-334\nvar leftText = context.ObjectToString(left);\nvar builder = new StringBuilder();\nfor (int i = 0; i \u003c value; i++)\n{\n    builder.Append(leftText);\n}\nreturn builder.ToString();\n```\n\nThe `LimitToString` safety control (default 1MB) does **not** protect this code path. It only applies to `ObjectToString` output conversions in `TemplateContext.Helpers.cs` (lines 101-121), not to intermediate string values constructed inside `CalculateToString`. The `LoopLimit` also does not apply because this is a C# `for` loop, not a template-level loop — `StepLoop()` is never called here.\n\n### Vector 2: Unbounded BigInteger shift left\n\nThe `CalculateLongWithInt` and `CalculateBigIntegerNoFit` methods handle `ShiftLeft` without any bound on the shift amount:\n\n```csharp\n// src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs:710-711\ncase ScriptBinaryOperator.ShiftLeft:\n    return (BigInteger)left \u003c\u003c (int)right;\n```\n\n```csharp\n// src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs:783-784\ncase ScriptBinaryOperator.ShiftLeft:\n    return left \u003c\u003c (int)right;\n```\n\nIn contrast, the `Power` operator at lines 722 and 795 uses `BigInteger.ModPow(left, right, MaxBigInteger)` to cap results. The `MaxBigInteger` constant (`BigInteger.One \u003c\u003c 1024 * 1024`, defined at line 690) already exists but is never applied to shift operations.\n\n### Vector 3: LoopLimit bypass via range enumeration in builtin functions\n\nThe range operators `..` and `..\u003c` produce lazy `IEnumerable\u003cobject\u003e` iterators:\n\n```csharp\n// src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs:401-417\nprivate static IEnumerable\u003cobject\u003e RangeInclude(BigInteger left, BigInteger right)\n{\n    if (left \u003c right)\n    {\n        for (var i = left; i \u003c= right; i++)\n        {\n            yield return FitToBestInteger(i);\n        }\n    }\n    // ...\n}\n```\n\nWhen these ranges are consumed by builtin functions, `LoopLimit` is completely bypassed because `StepLoop()` is only called in `ScriptForStatement` and `ScriptWhileStatement` — it is never called in any function under `src/Scriban/Functions/`. For example:\n\n- `ArrayFunctions.Size` (line 609) calls `.Cast\u003cobject\u003e().Count()`, fully enumerating the range\n- `ArrayFunctions.Join` (line 388) iterates with `foreach` and appends to a `StringBuilder` with no size limit\n\n## PoC\n\n### Vector 1 — String multiplication OOM:\n```csharp\nvar template = Template.Parse(\"{{ 'AAAA' * 500000000 }}\");\nvar context = new TemplateContext();\n// context.LimitToString is 1048576 by default — does NOT protect this path\ntemplate.Render(context); // OutOfMemoryException: attempts ~2GB allocation\n```\n\n### Vector 2 — BigInteger shift OOM:\n```csharp\nvar template = Template.Parse(\"{{ 1 \u003c\u003c 100000000 }}\");\nvar context = new TemplateContext();\ntemplate.Render(context); // Allocates BigInteger with 100M bits (~12.5MB)\n// {{ 1 \u003c\u003c 2000000000 }} attempts ~250MB\n```\n\n### Vector 3 — LoopLimit bypass via range + builtin:\n```csharp\nvar template = Template.Parse(\"{{ (0..1000000000) | array.size }}\");\nvar context = new TemplateContext();\n// context.LoopLimit is 1000 — does NOT protect builtin function iteration\ntemplate.Render(context); // CPU exhaustion: enumerates 1 billion items\n```\n\n```csharp\nvar template = Template.Parse(\"{{ (0..10000000) | array.join ',' }}\");\nvar context = new TemplateContext();\ntemplate.Render(context); // Memory exhaustion: builds ~80MB+ joined string\n```\n\n## Impact\n\nAn attacker who can supply a Scriban template (common in CMS platforms, email templating systems, reporting tools, and other applications embedding Scriban) can cause denial of service by crashing the host process via `OutOfMemoryException` or exhausting CPU resources. This is particularly impactful because:\n\n1. Applications relying on the default safety controls (`LoopLimit=1000`, `LimitToString=1MB`) believe they are protected against resource exhaustion from untrusted templates, but these controls have gaps.\n2. A single malicious template expression is sufficient — no complex template logic is required.\n3. The `OutOfMemoryException` in vectors 1 and 2 typically terminates the entire process, not just the template evaluation.\n\n## Recommended Fix\n\n### Vector 1 — String multiplication: Check `LimitToString` before the loop\n\n```csharp\n// src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs, before line 330\nvar leftText = context.ObjectToString(left);\nif (context.LimitToString \u003e 0 \u0026\u0026 (long)value * leftText.Length \u003e context.LimitToString)\n{\n    throw new ScriptRuntimeException(span,\n        $\"String multiplication would exceed LimitToString ({context.LimitToString} characters)\");\n}\nvar builder = new StringBuilder();\nfor (int i = 0; i \u003c value; i++)\n```\n\n### Vector 2 — BigInteger shift: Cap the shift amount\n\n```csharp\n// src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs, lines 710-711 and 783-784\ncase ScriptBinaryOperator.ShiftLeft:\n    if (right \u003e 1048576) // Same as MaxBigInteger bit count\n        throw new ScriptRuntimeException(span,\n            $\"Shift amount {right} exceeds maximum allowed (1048576)\");\n    return (BigInteger)left \u003c\u003c (int)right;\n```\n\n### Vector 3 — Range + builtins: Add iteration counting to range iterators\n\nPass `TemplateContext` to `RangeInclude`/`RangeExclude` and enforce a limit:\n\n```csharp\nprivate static IEnumerable\u003cobject\u003e RangeInclude(TemplateContext context, BigInteger left, BigInteger right)\n{\n    var maxRange = context.LoopLimit \u003e 0 ? context.LoopLimit : int.MaxValue;\n    int count = 0;\n    if (left \u003c right)\n    {\n        for (var i = left; i \u003c= right; i++)\n        {\n            if (++count \u003e maxRange)\n                throw new ScriptRuntimeException(context.CurrentNode.Span,\n                    $\"Range enumeration exceeds LoopLimit ({maxRange})\");\n            yield return FitToBestInteger(i);\n        }\n    }\n    // ... same for descending branch\n}\n```\n\nAlternatively, validate range size eagerly at creation time: `if (BigInteger.Abs(right - left) \u003e maxRange) throw ...`","status":"published","published":"2026-03-24T22:16:01Z","modified":"2026-07-06T13:10:10Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T14:40:38.48196Z"}],"nvd":[{"id":"CVE-2026-74785","description":"Scriban before 7.0.0 contains three distinct denial-of-service vulnerabilities in expression evaluation that bypass existing safety controls through unbounded string multiplication, uncontrolled BigInteger shift operations, and LoopLimit bypass via range enumeration in builtin functions. Attackers who can supply templates can cause out-of-memory exceptions or CPU exhaustion, typically terminating the entire host process.","status":"published","published":"2026-08-16T14:16:56.39Z","modified":"2026-08-31T20:30:14.457Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/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","level":"high","assessor":"secondary"},{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium","assessor":"secondary"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T14:12:38.647684Z"}],"osv":[{"id":"CVE-2026-74785","title":"Scriban before 7.0.0 Denial of Service via Unbounded Resource Consumption","description":"Scriban before 7.0.0 contains three distinct denial-of-service vulnerabilities in expression evaluation that bypass existing safety controls through unbounded string multiplication, uncontrolled BigInteger shift operations, and LoopLimit bypass via range enumeration in builtin functions. Attackers who can supply templates can cause out-of-memory exceptions or CPU exhaustion, typically terminating the entire host process.","status":"published","published":"2026-08-16T13:14:11.187Z","modified":"2026-10-28T02:30:52.129846667Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T15:45:16.748692Z"},{"id":"GHSA-xw6w-9jjh-p9cr","title":"Scriban has Multiple Denial-of-Service Vectors via Unbounded Resource Consumption During Expression Evaluation","description":"## Summary\n\nScriban's expression evaluation contains three distinct code paths that allow an attacker who can supply a template to cause denial of service through unbounded memory allocation or CPU exhaustion. The existing safety controls (`LimitToString`, `LoopLimit`) do not protect these paths, giving applications a false sense of safety when evaluating untrusted templates.\n\n## Details\n\n### Vector 1: Unbounded string multiplication\n\nIn `ScriptBinaryExpression.cs`, the `CalculateToString` method handles the `string * int` operator by looping without any upper bound:\n\n```csharp\n// src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs:319-334\nvar leftText = context.ObjectToString(left);\nvar builder = new StringBuilder();\nfor (int i = 0; i \u003c value; i++)\n{\n    builder.Append(leftText);\n}\nreturn builder.ToString();\n```\n\nThe `LimitToString` safety control (default 1MB) does **not** protect this code path. It only applies to `ObjectToString` output conversions in `TemplateContext.Helpers.cs` (lines 101-121), not to intermediate string values constructed inside `CalculateToString`. The `LoopLimit` also does not apply because this is a C# `for` loop, not a template-level loop — `StepLoop()` is never called here.\n\n### Vector 2: Unbounded BigInteger shift left\n\nThe `CalculateLongWithInt` and `CalculateBigIntegerNoFit` methods handle `ShiftLeft` without any bound on the shift amount:\n\n```csharp\n// src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs:710-711\ncase ScriptBinaryOperator.ShiftLeft:\n    return (BigInteger)left \u003c\u003c (int)right;\n```\n\n```csharp\n// src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs:783-784\ncase ScriptBinaryOperator.ShiftLeft:\n    return left \u003c\u003c (int)right;\n```\n\nIn contrast, the `Power` operator at lines 722 and 795 uses `BigInteger.ModPow(left, right, MaxBigInteger)` to cap results. The `MaxBigInteger` constant (`BigInteger.One \u003c\u003c 1024 * 1024`, defined at line 690) already exists but is never applied to shift operations.\n\n### Vector 3: LoopLimit bypass via range enumeration in builtin functions\n\nThe range operators `..` and `..\u003c` produce lazy `IEnumerable\u003cobject\u003e` iterators:\n\n```csharp\n// src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs:401-417\nprivate static IEnumerable\u003cobject\u003e RangeInclude(BigInteger left, BigInteger right)\n{\n    if (left \u003c right)\n    {\n        for (var i = left; i \u003c= right; i++)\n        {\n            yield return FitToBestInteger(i);\n        }\n    }\n    // ...\n}\n```\n\nWhen these ranges are consumed by builtin functions, `LoopLimit` is completely bypassed because `StepLoop()` is only called in `ScriptForStatement` and `ScriptWhileStatement` — it is never called in any function under `src/Scriban/Functions/`. For example:\n\n- `ArrayFunctions.Size` (line 609) calls `.Cast\u003cobject\u003e().Count()`, fully enumerating the range\n- `ArrayFunctions.Join` (line 388) iterates with `foreach` and appends to a `StringBuilder` with no size limit\n\n## PoC\n\n### Vector 1 — String multiplication OOM:\n```csharp\nvar template = Template.Parse(\"{{ 'AAAA' * 500000000 }}\");\nvar context = new TemplateContext();\n// context.LimitToString is 1048576 by default — does NOT protect this path\ntemplate.Render(context); // OutOfMemoryException: attempts ~2GB allocation\n```\n\n### Vector 2 — BigInteger shift OOM:\n```csharp\nvar template = Template.Parse(\"{{ 1 \u003c\u003c 100000000 }}\");\nvar context = new TemplateContext();\ntemplate.Render(context); // Allocates BigInteger with 100M bits (~12.5MB)\n// {{ 1 \u003c\u003c 2000000000 }} attempts ~250MB\n```\n\n### Vector 3 — LoopLimit bypass via range + builtin:\n```csharp\nvar template = Template.Parse(\"{{ (0..1000000000) | array.size }}\");\nvar context = new TemplateContext();\n// context.LoopLimit is 1000 — does NOT protect builtin function iteration\ntemplate.Render(context); // CPU exhaustion: enumerates 1 billion items\n```\n\n```csharp\nvar template = Template.Parse(\"{{ (0..10000000) | array.join ',' }}\");\nvar context = new TemplateContext();\ntemplate.Render(context); // Memory exhaustion: builds ~80MB+ joined string\n```\n\n## Impact\n\nAn attacker who can supply a Scriban template (common in CMS platforms, email templating systems, reporting tools, and other applications embedding Scriban) can cause denial of service by crashing the host process via `OutOfMemoryException` or exhausting CPU resources. This is particularly impactful because:\n\n1. Applications relying on the default safety controls (`LoopLimit=1000`, `LimitToString=1MB`) believe they are protected against resource exhaustion from untrusted templates, but these controls have gaps.\n2. A single malicious template expression is sufficient — no complex template logic is required.\n3. The `OutOfMemoryException` in vectors 1 and 2 typically terminates the entire process, not just the template evaluation.\n\n## Recommended Fix\n\n### Vector 1 — String multiplication: Check `LimitToString` before the loop\n\n```csharp\n// src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs, before line 330\nvar leftText = context.ObjectToString(left);\nif (context.LimitToString \u003e 0 \u0026\u0026 (long)value * leftText.Length \u003e context.LimitToString)\n{\n    throw new ScriptRuntimeException(span,\n        $\"String multiplication would exceed LimitToString ({context.LimitToString} characters)\");\n}\nvar builder = new StringBuilder();\nfor (int i = 0; i \u003c value; i++)\n```\n\n### Vector 2 — BigInteger shift: Cap the shift amount\n\n```csharp\n// src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs, lines 710-711 and 783-784\ncase ScriptBinaryOperator.ShiftLeft:\n    if (right \u003e 1048576) // Same as MaxBigInteger bit count\n        throw new ScriptRuntimeException(span,\n            $\"Shift amount {right} exceeds maximum allowed (1048576)\");\n    return (BigInteger)left \u003c\u003c (int)right;\n```\n\n### Vector 3 — Range + builtins: Add iteration counting to range iterators\n\nPass `TemplateContext` to `RangeInclude`/`RangeExclude` and enforce a limit:\n\n```csharp\nprivate static IEnumerable\u003cobject\u003e RangeInclude(TemplateContext context, BigInteger left, BigInteger right)\n{\n    var maxRange = context.LoopLimit \u003e 0 ? context.LoopLimit : int.MaxValue;\n    int count = 0;\n    if (left \u003c right)\n    {\n        for (var i = left; i \u003c= right; i++)\n        {\n            if (++count \u003e maxRange)\n                throw new ScriptRuntimeException(context.CurrentNode.Span,\n                    $\"Range enumeration exceeds LoopLimit ({maxRange})\");\n            yield return FitToBestInteger(i);\n        }\n    }\n    // ... same for descending branch\n}\n```\n\nAlternatively, validate range size eagerly at creation time: `if (BigInteger.Abs(right - left) \u003e maxRange) throw ...`","status":"published","published":"2026-03-24T22:16:01Z","modified":"2026-08-17T03:55:58.857433459Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"cwes":["CWE-400"],"updated_at":"2026-11-02T15:47:00.604718Z"}]}}
