{"id":"GHSA-x6m9-38vm-2xhf","summary":"Scriban has an authorization bypass due to stale include cache surviving TemplateContext.Reset() ","details":"## Summary\n\n`TemplateContext.Reset()` claims that a `TemplateContext` can be reused safely on the same thread, but it does not clear `CachedTemplates`. If an application pools `TemplateContext` objects and uses an `ITemplateLoader` that resolves content per request, tenant, or user, a previously authorized include can be served to later renders without calling `TemplateLoader.Load()` again.\n\n## Details\n\nThe relevant code path is:\n\n- `TemplateContext.Reset()` only clears output, globals, cultures, and source files in `src/Scriban/TemplateContext.cs` lines 877–902.\n- `CachedTemplates` is initialized once and kept on the context in `src/Scriban/TemplateContext.cs` line 197.\n- `include` resolves templates through `IncludeFunction.Invoke()` in `src/Scriban/Functions/IncludeFunction.cs` lines 29–43.\n- `IncludeFunction.Invoke()` calls `TemplateContext.GetOrCreateTemplate()` in `src/Scriban/TemplateContext.cs` lines 1249–1256.\n- If a template path is already present in `CachedTemplates`, Scriban returns the cached compiled template and does **not** call `TemplateLoader.Load()` again.\n\nThis becomes a security issue when `ITemplateLoader.Load()` returns request-dependent content. A first render can prime the cache with an admin-only or tenant-specific template, and later renders on the same reused `TemplateContext` will receive that stale template even after `Reset()`.\n\n---\n\n## Proof of Concept\n\n### Setup\n\n```bash\nmkdir scriban-poc1\ncd scriban-poc1\ndotnet new console --framework net8.0\ndotnet add package Scriban --version 6.6.0\n```\n\n### `Program.cs`\n\n```csharp\nusing Scriban;\nusing Scriban.Parsing;\nusing Scriban.Runtime;\n\nvar loader = new SwitchingLoader();\nvar context = new TemplateContext\n{\n    TemplateLoader = loader,\n};\n\nvar template = Template.Parse(\"{{ include 'profile' }}\");\n\nloader.Content = \"admin-only\";\nConsole.WriteLine(\"first=\" + template.Render(context));\n\ncontext.Reset();\n\nloader.Content = \"guest-view\";\nConsole.WriteLine(\"second=\" + template.Render(context));\n\nsealed class SwitchingLoader : ITemplateLoader\n{\n    public string Content { get; set; } = string.Empty;\n\n    public string GetPath(TemplateContext context, SourceSpan callerSpan, string templateName) =\u003e templateName;\n\n    public string Load(TemplateContext context, SourceSpan callerSpan, string templatePath) =\u003e Content;\n\n    public ValueTask\u003cstring\u003e LoadAsync(TemplateContext context, SourceSpan callerSpan, string templatePath)\n        =\u003e new(Content);\n}\n```\n\n### Run\n\n```bash\ndotnet run\n```\n\n### Actual Output\n\n```\nfirst=admin-only\nsecond=admin-only\n```\n\n### Expected Output\n\n```\nfirst=admin-only\nsecond=guest-view\n```\n\nThe second render should reload the template after `Reset()`, but it instead reuses the cached compiled template from the previous render.\n\n---\n\n## Impact\n\nThis is a cross-render data isolation issue. Any application that reuses `TemplateContext` objects and uses a request-dependent `ITemplateLoader` can leak previously authorized template content across requests, users, or tenants.\n\nThe issue impacts applications that:\n\n- Pool or reuse `TemplateContext`\n- Call `Reset()` between requests\n- Use `include`\n- Resolve include content based on request-specific state","aliases":["CVE-2026-74791"],"modified":"2026-08-17T03:55:59.456581711Z","published":"2026-03-24T22:09:49Z","database_specific":{"severity":"HIGH","github_reviewed":true,"github_reviewed_at":"2026-03-24T22:09:49Z","nvd_published_at":null,"cwe_ids":["CWE-226"]},"references":[{"type":"WEB","url":"https://github.com/scriban/scriban/security/advisories/GHSA-x6m9-38vm-2xhf"},{"type":"PACKAGE","url":"https://github.com/scriban/scriban"}],"affected":[{"package":{"name":"scriban","ecosystem":"NuGet","purl":"pkg:nuget/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"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-x6m9-38vm-2xhf/GHSA-x6m9-38vm-2xhf.json"}},{"package":{"name":"Scriban.Signed","ecosystem":"NuGet","purl":"pkg:nuget/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"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-x6m9-38vm-2xhf/GHSA-x6m9-38vm-2xhf.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N"}]}