LeafKit's HTML escaping may be skipped for Collection values, enabling XSS
Date: 2026-03
Affected: swift/github.com/vapor/leaf-kit
Attack vector: xss
Category: vulnerability-disclosure
Severity: Medium
CVE: CVE-2026-28499
CWE: CWE-116
CWE-79
CWE-80

### Summary LeafKit HTML-escaping is not working correctly when a template prints a collection (Array / Dictionary) via `#(value)`. This can result in XSS, allowing potentially untrusted input to be rendered unescaped. ### Details LeafKit attempts to escape expressions during serialization, but due to [`LeafData.htmlEscaped()`](https://github.com/vapor/leaf-kit/blob/8ff06839d8b3ddf74032d2ade01e3453eb556d30/Sources/LeafKit/LeafData/LeafData.swift#L322)'s implementation, when the escaped type's conversion to `String` is marked as `.ambiguous` (as it is the case for Arrays and Dictionaries), an unescaped `self` is returned. > **Note: I recommend first looking at the POC, before taking a look at the details below, as it is simple.** In the detailed, verbose analysis below, I explored the functions involved in more detail, in hopes that it will help you understand and locate this issue. #### The issue's detailed analysis: 1. Leaf expression serialization eventually reaches `LeafSerializer`'s `serialize` private function below. This is where the `leafData` is `.htmlEscaped()`, and then serialized. https://github.com/vapor/leaf-kit/blob/8ff06839d8b3ddf74032d2ade01e3453eb556d30/Sources/LeafKit/LeafSerialize/LeafSerializer.swift#L60-L66 2. The `LeafData.htmlEscaped()` method uses the `LeafData.string` computed property to convert itself to a string. Then, it calls the `htmlEscaped()` method on it. However, if the string conversion fails, notice that an unescaped, unsafe `self` i


Mitigations:

Architectural layers (MAESTRO):