GitHub Security Advisory: GHSA-xw6w-9jjh-p9cr
Package: Scriban (NUGET)
Severity: MODERATE (CVSS 6.5)
CWE: CWE-400: Uncontrolled Resource Consumption
Vulnerable versions: < 7.0.0
Patched in: 7.0.0

Summary: Scriban has Multiple Denial-of-Service Vectors via Unbounded Resource Consumption During Expression Evaluation

Details:
## Summary

Scriban'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.

## Details

### Vector 1: Unbounded string multiplication

In `ScriptBinaryExpression.cs`, the `CalculateToString` method handles the `string * int` operator by looping without any upper bound:

```csharp
// src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs:319-334
var leftText = context.ObjectToString(left);
var builder = new StringBuilder();
for (int i = 0; i < value; i++)
{
    builder.Append(leftText);
}
return builder.ToString();
```

The `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.

### Vector 2: Unbounded BigInteger shift left

The `CalculateLongWithInt` and `CalculateBigIntegerNoFit` methods handle `ShiftLeft` without any bound on the shift amount:

```csharp
// src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs:710-711
case ScriptBinaryOperator.ShiftLeft:
    return (BigInteger)left << (int)right;
```

```csharp
// src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs:783-784
case ScriptBinaryOperator.ShiftLeft:
    return left << (int)right;
```

In contrast, the `Power` operator at lines 722 and 795 uses `BigInteger.ModPow(left, right, MaxBigInteger)` to cap results. The `MaxBigInteger` constant (`BigInteger.One << 1024 * 1024`, defined at line 690) already exists but is never applied to shift operations.

### Vector 3: LoopLimit bypass via range enumeration in builtin functions

The range operators `..` and `..<` produce lazy `IEnumerable<object>` iterators:

```csharp
// src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs:401-417
private static IEnumerable<object> RangeInclude(BigInteger left, BigInteger right)
{
    if (left < right)
    {
        for (var i = left; i <= right; i++)
        {
            yield return FitToBestInteger(i);
        }
    }
    // ...
}
```

When 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:

- `ArrayFunctions.Size` (line 609) calls `.Cast<object>().Count()`, fully enumerating the range
- `ArrayFunctions.Join` (line 388) iterates with `foreach` and appends to a `StringBuilder` with no size limit

## PoC

### Vector 1 — String multiplication OOM:
```csharp
var template