To prevent cross-site scripting in a server-side Swift app, render pages through an auto-escaping template engine such as Leaf instead of assembling HTML in Swift strings. Treat raw output tags like unsafeHTML as exceptions that need sanitized input, validate URLs, and add a Content Security Policy as a second layer.
Where does XSS come from in Swift?
Swift is memory safe, and that does not matter here. XSS is a rendering problem: untrusted text reaches a browser as markup. On the server side the usual cause is string interpolation, because it is so pleasant in Swift.
A constructed example, a handler for a guestbook page:
let page = "<h2>Hello \(name)</h2><p>\(message)</p>"
return Response(status: .ok, headers: ["Content-Type": "text/html"],
body: .init(string: page))
If message holds a script element, the browser runs it for every visitor who loads the page. The type checker sees two perfectly good Strings. Nothing in the language knows one of them came from a stranger.
What does a template engine change?
A template engine that escapes by default does the encoding for you at the point of output. Vapor's Leaf is the common choice. Its documentation describes a variable tag written as #(variable), and a separate #unsafeHTML tag that behaves like a variable tag but does not escape any HTML the variable contains. The docs add a note to be careful that the variable you give it does not expose your users to an XSS attack.
So the safe handler passes data and lets the template place it:
return try await req.view.render("guestbook", ["name": name, "message": message])
and the template uses the normal variable tag for both values. A message containing markup is then shown as text.
The risk moves to the escape hatch. Every use of #unsafeHTML should be in a list you can name and justify. OWASP makes the same point about frameworks in general: modern ones steer you toward auto-escaping, but problems appear when you use their raw output features or skip validation.
What about places escaping does not cover?
HTML escaping fits element content. Other contexts need different handling, and the OWASP XSS Prevention Cheat Sheet is organized around those contexts.
- URLs. A value placed in an href or src can start with a script scheme. Escaping the characters does not help. Parse the URL on the server and accept only schemes you expect, usually http and https.
- Inline scripts. Do not write server values into a script block. Put data in an attribute or a JSON response and read it with code that does not execute it.
- Rich text. If users may submit formatting, run the HTML through a sanitizer built for that purpose and only then mark it safe. Do not write your own regular expression.
Do I need a Content Security Policy?
Yes, as a safety net and not as the main defense. OWASP describes CSP as an allowlist that is easy to get wrong and says it should not be your primary defense, but a good additional layer. A policy that blocks inline scripts turns many missed encoding bugs into console errors rather than incidents. Send it as a response header from one middleware so no route can forget it.
What to do
- Search for HTML built from Swift strings, and replace each with a Leaf render call.
- List every
#unsafeHTMLuse. Remove it, or pass only sanitized content. - Validate URL fields on the server and restrict the allowed schemes.
- Keep user data out of inline script blocks.
- Add a CSP header through middleware and watch the reports before enforcing it.
- Add a test that posts a script element as a message and checks that the response shows it as text.
- Keep Vapor, Leaf and their dependencies current, and run a dependency scan on Package.resolved.
Safeguard can scan Swift package dependencies and code in a repository, which covers step 7 and part of step 1. The template habits above are still yours to enforce.
Quick check
Is Swift immune to XSS because it is memory safe?
No. Memory safety is about the process. XSS happens in the browser when untrusted text is treated as markup, and any language can produce that output.
When is unsafeHTML acceptable?
Only when the content is yours or has been through a sanitizer designed for HTML, and the use is recorded so it can be reviewed.
Sources
Last reviewed: 18 October 2026.