Vulnerability Database

328,925

Total vulnerabilities in the database

CVE-2026-28499

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()'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

  1. 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 is returned (line 324 below):

https://github.com/vapor/leaf-kit/blob/8ff06839d8b3ddf74032d2ade01e3453eb556d30/Sources/LeafKit/LeafData/LeafData.swift#L321-L328

  1. Regarding why .string may return nil, if the escaped value is not a string already, a convesion is attempted, which may fail.

https://github.com/vapor/leaf-kit/blob/8ff06839d8b3ddf74032d2ade01e3453eb556d30/Sources/LeafKit/LeafData/LeafData.swift#L211-L216

In this specific case, the conversion fails at line 303 below, when conversion.is >= level is checked. The check fails because .array and .dictionary conversions to .string are deemed .ambiguous. If we forcefully allow ambiguous conversions, the vulnerability disappears, as the conversion is successful.

https://github.com/vapor/leaf-kit/blob/8ff06839d8b3ddf74032d2ade01e3453eb556d30/Sources/LeafKit/LeafData/LeafData.swift#L295-L319

  1. Coming back to LeafSerializer's serialize private method, we are now interested in finding out what happens after LeafData.htmlEscaped() returns self. Recall from 1. that the output was then .serialized(). Thus, the unescaped LeafData follows the normal serialization path, as if it were HTML-escaped. More specifically, serialization is done here, where .map / .mapValues is called, unsafely serializing each element of the dictionary.

PoC

<!-- Complete instructions, including specific configuration details, to reproduce the vulnerability. -->

In a new Vapor project created with vapor new poc -n --leaf, use a simple leaf template like the following:

&lt;!doctype html&gt; &lt;html&gt; &lt;body&gt; &lt;h1&gt;#(username)&lt;/h1&gt; &lt;h2&gt;someDict:&lt;/h2&gt; &lt;p&gt;#(someDict)&lt;/p&gt; &lt;/body&gt; &lt;/html&gt;

And the following routes.swift:

import Vapor struct User: Encodable { var username: String var someDict: [String: String] } func routes(_ app: Application) throws { app.get { req async throws in try await req.view.render(&quot;index&quot;, User( username: &quot;Escaped XSS - &lt;img src=x onerror=alert(1)&gt;&quot;, someDict: [&quot;&lt;img src=x onerror=alert(1337)&gt;&quot;:&quot;&lt;img src=x onerror=alert(31337)&gt;&quot;] )) } }

By running and accessing the server in a browser, XSS should be triggered twice (with alert(1337) and alert(31337)). var someDict: [String: String] could also be replaced with an array / dictionary of a different type, such as another Encodable stuct.

Also note that, in a real concerning scenario, the array / dictionary would contain (i.e. reflect) data inputted by the user.

Impact

This is a cross-site scripting (XSS) vulnerability in rendered Leaf templates. Vapor/Leaf applications that render user-controlled data inside arrays or dictionaries using #(value) may be impacted.

No technical information available.

Frequently Asked Questions

A security vulnerability is a weakness in software, hardware, or configuration that can be exploited to compromise confidentiality, integrity, or availability. Many vulnerabilities are tracked as CVEs (Common Vulnerabilities and Exposures), which provide a standardized identifier so teams can coordinate patching, mitigation, and risk assessment across tools and vendors.

CVSS (Common Vulnerability Scoring System) estimates technical severity, but it doesn't automatically equal business risk. Prioritize using context like internet exposure, affected asset criticality, known exploitation (proof-of-concept or in-the-wild), and whether compensating controls exist. A "Medium" CVSS on an exposed, production system can be more urgent than a "Critical" on an isolated, non-production host.

A vulnerability is the underlying weakness. An exploit is the method or code used to take advantage of it. A zero-day is a vulnerability that is unknown to the vendor or has no publicly available fix when attackers begin using it. In practice, risk increases sharply when exploitation becomes reliable or widespread.

Recurring findings usually come from incomplete Asset Discovery, inconsistent patch management, inherited images, and configuration drift. In modern environments, you also need to watch the software supply chain: dependencies, containers, build pipelines, and third-party services can reintroduce the same weakness even after you patch a single host. Unknown or unmanaged assets (often called Shadow IT) are a common reason the same issues resurface.

Use a simple, repeatable triage model: focus first on externally exposed assets, high-value systems (identity, VPN, email, production), vulnerabilities with known exploits, and issues that enable remote code execution or privilege escalation. Then enforce patch SLAs and track progress using consistent metrics so remediation is steady, not reactive.

SynScan combines attack surface monitoring and continuous security auditing to keep your inventory current, flag high-impact vulnerabilities early, and help you turn raw findings into a practical remediation plan.