Sep 18, 2026·6 min read·9 visits
A name-based context safety grant in djust is inherited by shadowed variables, causing raw rendering of untrusted values and allowing unauthenticated remote execution of arbitrary JavaScript.
An escaping defect in the djust templating engine allows Cross-Site Scripting (XSS) when a template binding construct shadows a variable that was previously marked safe. The Rust-based context safety tracking incorrectly preserves name-based safety grants even after the variable name has been bound to a new, untrusted value.
The djust templating engine is a high-performance, reactive server-side rendering library designed for Django applications. It uses a Rust-powered backend implementation (comprising the djust_core and djust_templates crates) to compile and render templates quickly. To maintain compatibility with Django's safety markings, djust passes information about which variables have been marked as safe across the PyO3 foreign function interface boundary.
However, the mechanism used to track context safety properties was designed to key safety settings by variable name rather than using individual object instances. When standard Django templates wrap strings in SafeData subclasses, the djust engine registers the corresponding variable paths inside a flat security lookup collection. If an outer variable is marked safe, any inner shadow scope that rebinds that same variable name will inherit the safe designation.
This architectural design flaw exposes a cross-site scripting attack surface. When templates employ common shadowing structures such as nested loop scopes or with blocks, the safety metadata remains pinned to the key. This allows untrusted user inputs bound to the shadowed variable name to bypass escaping and render raw HTML directly to the client browser.
This vulnerability affects all releases of djust from version 1.0.0 up to and including version 1.1.1. The issue has been addressed in version 1.1.2 by introducing active safety-key revocation during scope binding operations.
To understand the root cause of the vulnerability, we must examine the context management architecture in crates/djust_core/src/context.rs. The Context struct tracks safe variables using a HashSet containing String keys named safe_keys. When a Python string is marked safe via Django's mark_safe, its dictionary path is added to this set.
When rendering template nodes, the engine determines whether to escape a value by calling Context::is_safe with the variable's key path. If the path exists in safe_keys, the engine skips HTML sanitization. The vulnerability occurs because the engine handles variable rebinding by overwriting the context value map while leaving the safe_keys set unchanged.
This security bypass occurs across eight template binding structures. These include with assignments, for loops, tuple unpacking in loop iterations, template inclusions using with blocks, and custom assign tags. In each case, the safety grant associated with the key survives the shadowing operation, causing the new, untrusted value to render without escaping.
Before the patch, the template renderer in crates/djust_templates/src/renderer.rs updated variable values in shadow scopes using Context::set, which did not affect the safe_keys HashSet:
// Vulnerable update path in crates/djust_templates/src/renderer.rs
new_context.set(var_name.clone(), value);To resolve this issue, the maintainers implemented Context::bind in crates/djust_core/src/context.rs. This method revokes the safety grant of the shadowed variable and any of its dotted-path descendants before binding the new value:
impl Context {
// Safe binding method introduced in v1.1.2
pub fn bind(&mut self, name: String, value: Value) {
self.revoke_safe_subtree(&name);
self.set(name, value);
}
// Revokes safety designations for the key and all nested paths
pub fn revoke_safe_subtree(&mut self, key: &str) {
if self.safe_keys.is_empty() {
return;
}
self.safe_keys.remove(key);
let prefix = format!("{}.", key);
self.safe_keys.retain(|k| !k.starts_with(&prefix));
}
}For loop constructs ({% for %}), calling revoke_safe_subtree inside the loop body would degrade performance to O(N * M) complexity, where N is the loop count and M is the size of safe_keys. To avoid this, the fix hoists the revocation step out of the loop body:
// Optimized loop initialization in crates/djust_templates/src/renderer.rs
let mut ctx = context.clone();
for var_name in var_names {
ctx.revoke_safe_subtree(var_name);
}This optimization strips safe keys from the context once before starting the loop iterations, maintaining template rendering speed while preventing safe-key inheritance.
An attacker can exploit this vulnerability by targeting template components that shadow variables that are marked safe in the parent view context. This requires the application to expose an endpoint where input can be passed into a shadowed template variable.
Consider an application where a Django view marks a variable named 'p' as safe to output trusted HTML. If the associated template subsequently binds user input to 'p' within an inner block, the input inherits the safe marking of the outer variable:
{% with p=request.GET.q %}
<div class="user-container">
{{ p }}
</div>
{% endwith %}If an attacker targets this endpoint with a payload containing malicious HTML or JavaScript, the template engine skips escaping and renders the payload directly into the HTTP response. When the response is loaded, the victim's browser processes and executes the injected script:
<div class="user-container">
<img src=x onerror=alert(document.domain)>
</div>This allows the attacker to execute arbitrary scripts in the victim's browser, enabling session hijacking, CSRF bypass, and unauthorized actions within the application.
This vulnerability poses a significant risk to applications using the djust template library. Based on CVSS v3.1 metrics, the security impact is assessed at a High severity level with a score of 8.2 (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N).
The attack vector is network-based (AV:N) and requires low complexity (AC:L) to execute. It requires no authentication privileges (PR:N) but does require interaction from a victim (UI:R), such as clicking a link containing the exploit payload. The impact scope is changed (S:C) because the vulnerability allows code execution to migrate from the server's templating context to the victim's browser session.
Successful exploitation can result in a high loss of confidentiality (C:H) as attackers can steal session tokens and sensitive data. It also causes a low loss of integrity (I:L) because attackers can modify the structure of the rendered web page. The vulnerability is classified under CWE-79 (Improper Neutralization of Input During Web Page Generation).
The recommended remediation is upgrading the djust library to version 1.1.2 or higher. This release integrates safe context bindings across all template rendering pathways, ensuring safety grants do not persist past shadow scopes.
If you cannot update the library immediately, you can mitigate the vulnerability by reviewing your templates. Ensure that no variable names marked safe in your Django views are shadowed inside with, for, or include blocks. Using unique, non-overlapping names for safe-marked variables and user-controlled variables will prevent safe-key inheritance.
Additionally, you can deploy Web Application Firewall rules to block common XSS payloads at the network boundary. Rules should target script tags, execution hooks like onerror=, and JavaScript protocols in URL parameters. However, these signatures should be used as a defense-in-depth measure and not as a replacement for the library update.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
djust djust-org | >= 1.0.0, <= 1.1.1 | 1.1.2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 |
| Attack Vector | Network (AV:N) |
| CVSS v3.1 | 8.2 (High) |
| Exploit Status | PoC |
| KEV Status | Not Listed |
| Impact | Cross-Site Scripting (XSS) |
The software does not neutralize or incorrectly neutralizes user-controlled input before it is placed in output that is used as a web page.
CVE-2026-118777 is a prototype pollution vulnerability (CWE-1321) in the Forge JavaScript cryptography and networking library (distributed on npm as node-forge) prior to version 1.4.1. The flaw resides in the forge.http client subsystem, where incoming HTTP cookie names and paths parsed from Set-Cookie response headers are stored directly into standard JavaScript objects without key validation or property isolation. By returning crafted Set-Cookie headers containing keys such as __proto__ or constructor, a malicious HTTP server can inject arbitrary properties into Object.prototype, attach attributes to the global Object constructor, or delete built-in prototype methods to cause an application-wide denial of service.
CVE-2026-118775 (GHSA-4642-5mcj-2rqw) is an integer overflow and validation flaw in the ASN.1 serialization module of Forge (node-forge) prior to version 1.4.1. The asn1.oidToDer() function fails to validate the range of the first two arcs of an Object Identifier (OID) and truncates the packed initial subidentifier to a single 8-bit integer. Applications that perform policy checks on untrusted OID strings before encoding them into Distinguished Encoding Rules (DER) format can be tricked into emitting DER bytes that correspond to a completely different, restricted OID.
CVE-2026-119194 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting versions 0.3.3 through 0.6.4. The flaw resides in the schemaless decoding routine ConstructedPayloadDecoderBase._decodeComponentsSchemaless used across BER, CER, and DER codecs. By sending an ASN.1 container populated with child components whose tag identifiers are separated by multiples of CPython's integer hash modulus (sys.hash_info.modulus), an unauthenticated attacker can force worst-case hash collisions in an unbounded Python set, degrading decoding performance from O(N) to O(N^2) and causing a Denial of Service (DoS).
CVE-2026-119195 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting all versions prior to 0.6.5. When decoding constructed strings or indefinite-length ANY types across BER, CER, and certain DER code paths, the payload decoders repeatedly concatenate incoming fragments onto an immutable bytes object using the += operator. Because Python bytes objects cannot be resized in-place, each fragment iteration allocates a new buffer and copies all previously accumulated bytes, resulting in quadratic O(N^2) time and memory copy complexity. An unauthenticated remote attacker can supply a highly fragmented ASN.1 stream to exhaust CPU cycles and stall application worker threads.
An algorithmic complexity vulnerability (CWE-407) in pyasn1 versions prior to 0.6.5 allows unauthenticated attackers to cause a Denial of Service (DoS) via crafted ASN.1 payloads. The flaw resides in the binary-encoded ASN.1 REAL mantissa deserialization routine within RealPayloadDecoder.valueDecoder. By performing iterative byte-string slicing and repeated bitwise shifts on a growing arbitrary-precision integer, the decoder exhibits quadratic time and memory allocation complexity relative to the mantissa length. Supplying a BER-, CER-, or DER-encoded payload with a large REAL mantissa causes excessive CPU consumption and blocks application worker processes.
A vulnerability classified under CWE-407 (Inefficient Algorithmic Complexity) exists in the pyasn1 library prior to version 0.6.5. When parsing constructed ASN.1 BIT STRING payloads under BER, CER, or schema-driven DER encoding rules, the BitStringPayloadDecoder repeatedly shifts and copies the entire accumulated bit value for each incoming fragment. Processing inputs with a large number of small constructed fragments induces quadratic CPU consumption, allowing unauthenticated remote attackers to cause a denial of service via resource exhaustion during the initial decoding phase.