{"id":"CVE-2026-74790","aliases":["GHSA-5wr9-m6jw-xx44"],"url":"https://o3.security/vulnerability/CVE-2026-74790","summary":"Scriban before 7.0.0 MemberFilter Bypass via TemplateContext Cache","details":"## Summary\n\n`TemplateContext` caches type accessors by `Type` only, but those accessors are built using the current `MemberFilter` and `MemberRenamer`. When a `TemplateContext` is reused and the filter is tightened for a later render, Scriban still reuses the old accessor and continues exposing members that should now be hidden.\n\n## Details\n\nThe relevant code path is:\n\n- `TemplateContext.GetMemberAccessor()` caches accessors in `_memberAccessors` by `Type` in `src/Scriban/TemplateContext.cs` lines 850–863.\n- For plain .NET objects, `GetMemberAccessorImpl()` creates a new `TypedObjectAccessor(type, _keyComparer, MemberFilter, MemberRenamer)` in `src/Scriban/TemplateContext.cs` lines 909–939.\n- `TypedObjectAccessor` stores the current filter and precomputes the exposed member set in its constructor and `PrepareMembers()` in `src/Scriban/Runtime/Accessors/TypedObjectAccessor.cs` lines 33–40 and 119–179.\n- Member access later goes through `ScriptMemberExpression.GetValue()` in `src/Scriban/Syntax/Expressions/ScriptMemberExpression.cs` lines 67–95, which uses the cached accessor.\n- `TemplateContext.Reset()` does **not** clear `_memberAccessors` in `src/Scriban/TemplateContext.cs` lines 877–902.\n\nAs a result, once a permissive accessor has been created for a given type, changing `TemplateContext.MemberFilter` later does not take effect for that type on the same reused context.\n\nThis is especially relevant because the Scriban docs explicitly recommend `TemplateContext.MemberFilter` for indirect .NET object exposure.\n\n---\n\n## Proof of Concept\n\n### Setup\n\n```bash\nmkdir scriban-poc2\ncd scriban-poc2\ndotnet new console --framework net8.0\ndotnet add package Scriban --version 6.6.0\n```\n\n### `Program.cs`\n\n```csharp\nusing System.Reflection;\nusing Scriban;\nusing Scriban.Runtime;\n\nvar template = Template.Parse(\"{{ model.secret }}\");\n\nvar context = new TemplateContext\n{\n    EnableRelaxedMemberAccess = false\n};\n\nvar globals = new ScriptObject();\nglobals[\"model\"] = new SensitiveModel();\ncontext.PushGlobal(globals);\n\ncontext.MemberFilter = _ => true;\nConsole.WriteLine(\"first=\" + template.Render(context));\n\ncontext.Reset();\n\nvar globals2 = new ScriptObject();\nglobals2[\"model\"] = new SensitiveModel();\ncontext.PushGlobal(globals2);\n\ncontext.MemberFilter = member => member.Name == nameof(SensitiveModel.Public);\n\nConsole.WriteLine(\"second=\" + template.Render(context));\n\nsealed class SensitiveModel\n{\n    public string Public => \"ok\";\n    public string Secret => \"leaked\";\n}\n```\n\n### Run\n\n```bash\ndotnet run\n```\n\n### Actual Output\n\n```\nfirst=leaked\nsecond=leaked\n```\n\n### Expected Behavior\n\nThe second render should fail or stop exposing `Secret`, because the filter only allows `Public` and `EnableRelaxedMemberAccess` is disabled.\n\nThis reproduces a direct filter bypass caused by the stale cached accessor.\n\n---\n\n## Impact\n\nThis is a protection-mechanism bypass. Applications that use `TemplateContext.MemberFilter` as part of their sandbox or object-exposure policy can unintentionally expose hidden members across requests when they reuse a `TemplateContext`.\n\nThe impact includes:\n\n- Unauthorized read access to filtered properties or fields\n- Unauthorized writes if the filtered member also has a setter\n- Policy bypass across requests, users, or tenants when contexts are pooled","published":"2026-08-16T13:14:14.417Z","modified":"2026-10-28T02:30:39.907391652Z","cvss":null,"epss":{"score":0.00286,"percentile":0.2119,"asOf":"2026-09-17"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"NuGet","name":"scriban","fixedVersion":"7.0.0"},{"ecosystem":"NuGet","name":"Scriban.Signed","fixedVersion":"7.0.0"}],"fix":null,"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/74xxx/CVE-2026-74790.json"},{"type":"ADVISORY","url":"https://github.com/scriban/scriban/security/advisories/GHSA-5wr9-m6jw-xx44"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-74790"},{"type":"ADVISORY","url":"https://www.vulncheck.com/advisories/scriban-before-memberfilter-bypass-via-templatecontext-cache"},{"type":"PACKAGE","url":"https://github.com/scriban/scriban"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-28T02:30:39.907391652Z"}}