Jul 6, 2026·6 min read·13 visits
Scriban dynamically resolves and modifies restricted C# object properties (private, internal, init) within templates, bypassing language-level access controls and enabling mass assignment attacks.
An improper access control and mass assignment vulnerability in Scriban allows templates to write to arbitrary, restricted CLR properties (including private, internal, and init-only properties) of context-bound objects, causing unexpected state mutations on the host application heap.
Scriban is a high-performance text template engine for .NET. To facilitate dynamic content rendering, developers routinely expose host-level Common Language Runtime (CLR) objects to Scriban scripts using the standard context registration flow. This mapping is designed to allow templates to query object properties and insert runtime data directly into text layouts.\n\nHowever, the mechanism implemented to execute these integrations exposes an unexpected attack surface. During template evaluation, the library translates property-resolution and state-modification directives into reflection calls on the underlying host object. Because of this integration design, any code executing a template can interact with the memory space of registered .NET classes.\n\nThe vulnerability, tracked under GHSA-7jvp-hj45-2f2m, exists due to a critical asymmetry between read-indexing and write-dispatch paths. This design failure allows an attacker with template execution privileges to modify object properties that should remain immutable or inaccessible. The flaw encompasses both Improper Access Control (CWE-284) and Mass Assignment (CWE-915).
The root cause of the flaw resides in the member caching architecture of the TypedObjectAccessor class. During host-object initialization, Scriban maps the object's available public interfaces using the PrepareMembers method. This method identifies valid properties by searching exclusively for the presence of a public getter accessor. Properties containing public getters are stored in an internal cache array named _members.\n\nAn engineering oversight occurs during the write execution path. When a Scriban template processes an assignment operation (such as updating a field value), the execution engine invokes the TrySetValue method. Instead of consulting a separate write-validated registry, TrySetValue queries the getter-populated _members cache. If the target property is present in the cache, Scriban attempts to write the new value to the host object.\n\nTo perform the update, Scriban invokes standard .NET reflection via the PropertyInfo.SetValue interface. Because standard reflection bypasses compile-time safety and access-control checks, the underlying runtime executes writes to properties even if their setter accessors are marked with restrictive modifiers. The framework fails to verify if a valid, accessible setter exists before executing the reflection update.
The structural asymmetry is visible when evaluating the vulnerable implementation of the member accessor. The original code path retrieves properties based solely on getter availability, then utilizes reflection to execute modifications without evaluating setter visibility.\n\nBelow is a comparison highlighting the vulnerable structure against the remediation logic:\n\ncsharp\n// VULNERABLE: TypedObjectAccessor.cs (<= 7.2.1)\npublic bool TrySetValue(TemplateContext context, SourceSpan span, object target, string member, object value) {\n // Resolves based on '_members' cache, which was populated using only getter checks\n if (_members.TryGetValue(member, out var propertyAccessor)) {\n // BUG: Directly invokes reflection, ignoring private/internal/init constraints\n propertyAccessor.SetValue(target, context.ToObject(span, value, propertyAccessor.PropertyType));\n return true;\n }\n return false;\n}\n\n\nThe patched framework addresses this deficiency by validating both the existence and the accessibility level of the target setter. The correction includes explicit evaluation of the C# 9 init modifier, which is represented in intermediate language as a public setter decorated with a mandatory custom compiler modifier.\n\ncsharp\n// PATCHED: TypedObjectAccessor.cs (>= 7.2.2)\npublic bool TrySetValue(TemplateContext context, SourceSpan span, object target, string member, object value) {\n if (_members.TryGetValue(member, out var propertyAccessor)) {\n // Retrieve only the public set method\n var setM = propertyAccessor.GetSetMethod(nonPublic: false);\n if (setM is null) {\n return false; // Block if setter is private, protected, or internal\n }\n\n // Check for C# 9 \"init-only\" modifier (IsExternalInit)\n if (setM.ReturnParameter.GetRequiredCustomModifiers()\n .Any(m => m.FullName == \"System.Runtime.CompilerServices.IsExternalInit\")) {\n return false; // Block modification of init-only properties\n }\n\n propertyAccessor.SetValue(target, context.ToObject(span, value, propertyAccessor.PropertyType));\n return true;\n }\n return false;\n}\n
To exploit the vulnerability, an attacker must first target a host application that executes user-supplied or user-controlled Scriban templates. The host must also expose a CLR object containing sensitive internal state variables. The attack does not require prior authentication if the template execution flow is exposed to unauthenticated web inputs.\n\nThe attacker formats a liquid or scriban assignment statement referencing the protected field of the exposed model. Because the template engine parses the syntax and matches the member name to the getter cache, the security barrier is bypassed. The template processor hands execution to the reflection sink, which rewrites the host object's memory space directly on the C# heap.\n\nThe following diagram details the transaction flow and execution path from template input to state corruption on the heap:\n\nmermaid\ngraph LR\n A[\"Scriban Template Input\"] --> B[\"TemplateContext evaluation\"]\n B --> C[\"TypedObjectAccessor.TrySetValue()\"]\n C --> D[\"_members cache lookup\"]\n D --> E[\"PropertyInfo.SetValue() call\"]\n E --> F[\"Direct Heap Object Modification\"]\n\n\nA representative scenario involves an order processing model containing an immutable price field. When the script executes, the value is updated directly within the application's runtime memory, bypassing standard business logic constraints.
The impact of this vulnerability is significant, exposing host applications to unauthorized state modification and data tampering. By overwriting private and internal state fields, templates can alter execution parameters, skip security checkpoints, or modify system thresholds. This capability invalidates the threat boundaries assumed when hosting untrusted scripts.\n\nA typical vulnerability impact involves the bypass of state invariants. Under standard .NET execution, private or init-only setters guarantee that an object's state cannot be altered post-instantiation. Scriban's behavior neutralizes these platform guarantees, converting secure, read-only data structures into mutable buffers.\n\nThe CVSS v4 score is calculated at 7.7, reflecting a high impact on integrity. Because modifications occur directly in-memory, downstream components processing the compromised object will act on tampered data. This can lead to logical privilege escalation or remote control over application behavior.
The primary and recommended action is upgrading the Scriban NuGet dependency to version 7.2.2 or higher. The updated version implements strict verification of setter access modifiers and actively rejects attempts to write to properties lacking public, mutable setters.\n\nIn environments where updating the package is not immediately viable, developers must implement structural mitigations. This includes mapping data transfer objects (DTOs) specifically configured for template consumption, rather than binding raw business or domain models directly. Ensuring that context-bound objects contain no write-sensitive state will limit the exposure surface.\n\nAdditionally, custom MemberFilters can be registered to explicitly intercept write operations and block unauthorized properties. This provides a containment layer while the system is prepared for library patch deployment.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:P| Product | Affected Versions | Fixed Version |
|---|---|---|
Scriban Scriban | <= 7.2.1 | 7.2.2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-284, CWE-915 |
| Attack Vector | Network / Context Template Injection |
| CVSS v4 Score | 7.7 |
| Exploit Status | poc |
| CISA KEV Status | Not Listed |
| Impact | Improper Access Control / State Mutation |
The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor.
CVE-2026-12243 is a path traversal vulnerability in the Natural Language Toolkit (NLTK) version 3.9.4. The flaw exists because the input validation routine fails to account for percent-encoded directory traversal sequences like '..%2f' before passing them to urllib.request.url2pathname(), which decodes them into active traversal sequences.
CVE-2026-73654 is a high-severity prototype pollution vulnerability in Trigger.dev. The flaw occurs during the handling of run-metadata updates through the PUT /api/v1/runs/:runId/metadata endpoint. Because user-supplied keys are parsed directly by the @jsonhero/path library without sanitization, an authenticated attacker with low privileges can pollute the global Object.prototype. This causes database queries via Prisma ORM to fail validation and induces unhandled exceptions in the Prometheus metrics client, resulting in a process-wide denial of service.
CVE-2026-73559 is an uncontrolled resource consumption vulnerability in the vLLM engine, specifically within the /v1/completions API endpoint, allowing authenticated attackers to cause application-level denial of service via unbounded prompt arrays.
A critical security bypass vulnerability in Argo Workflows allows authenticated attackers with workflow submission privileges to bypass 'Strict' or 'Secure' template referencing restrictions. By injecting unvalidated fields into the nested ArtifactGC configuration, attackers can execute arbitrary pod patches, leading to host namespace escape and cluster-wide privilege escalation.
A Server-Side Request Forgery (SSRF) / Confused Deputy vulnerability has been identified in Pydantic AI UI Adapters (such as VercelAIAdapter). Under certain conditions, a malicious client can supply manipulated message history with provider metadata that forces the server to resolve files within privileged cloud environments (AWS S3, Google Cloud Storage) or model providers. This occurs because the adapters deserialize client-provided metadata structures directly into UploadedFile instances without validation, which are subsequently fetched using high-privilege server credentials.
A path traversal vulnerability in the optional dashboard server of atomic-agents-stack before version 1.1.0 allows unauthenticated remote attackers to read arbitrary files from the host filesystem.