SAPUI5 does a lot of the heavy lifting for output encoding, but I’ve seen teams get a false sense of safety from that. The framework helps, yes. It does not save you when you bypass its protections, render raw HTML, or trust backend data too much.
If you build SAPUI5 apps, most XSS bugs come from a handful of repeat mistakes. They’re boring, predictable, and still very exploitable.
Mistake #1: Assuming model data is always safe
A common SAPUI5 habit is to bind model values directly into controls and assume the framework will handle everything. Often it does. But “often” is not a security strategy.
Take a basic binding:
<Text text="{/customerName}" />
This is generally fine because sap.m.Text renders text, not HTML. The problem starts when developers later swap controls or use the same data in a less safe context.
For example:
<core:HTML content="{/welcomeMessage}" />
If /welcomeMessage contains attacker-controlled HTML or scriptable markup, you’ve just changed a safe text flow into a dangerous HTML sink.
Fix
Treat all model data as untrusted unless you can prove otherwise. Use controls that render text safely by default:
<Text text="{/welcomeMessage}" />
<FormattedText htmlText="{/safeRichText}" />
Even with sap.m.FormattedText, you should only allow limited markup and ensure the content is sanitized before it gets there. If you truly need rich HTML, sanitize it server-side and validate what tags and attributes are allowed.
The safe default is simple: if plain text works, use plain text.
Mistake #2: Using sap.ui.core.HTML for convenience
I get why people use sap.ui.core.HTML. It’s quick. It solves layout edge cases. It also creates one of the easiest XSS paths in a UI5 app.
Bad example:
const oHtml = new sap.ui.core.HTML({
content: "<div>" + oModel.getProperty("/message") + "</div>"
});
If /message contains something like:
<img src=x onerror=alert(1)>
you’ve got XSS.
Even worse is mixing user input into bigger HTML snippets:
oHtml.setContent(`
<div class="profile">
<h3>${oModel.getProperty("/displayName")}</h3>
<p>${oModel.getProperty("/bio")}</p>
</div>
`);
This is basically DOM XSS with nicer indentation.
Fix
Don’t build HTML strings from untrusted data. Prefer real UI5 controls:
const oVBox = new sap.m.VBox({
items: [
new sap.m.Title({ text: "{/displayName}" }),
new sap.m.Text({ text: "{/bio}" })
]
});
If you absolutely must use sap.ui.core.HTML, sanitize the content before assigning it and keep the allowed HTML subset tiny. Also review the official SAPUI5 security guidance and control documentation before using it in data-driven views:
https://ui5.sap.com/
My rule: if I can replace sap.ui.core.HTML with standard controls, I do it every time.
Mistake #3: Writing directly to the DOM with jQuery or browser APIs
UI5 apps still end up with old jQuery habits hanging around. Something like this is a classic bug:
this.byId("status").$().html(oModel.getProperty("/statusMessage"));
Or plain DOM API usage:
document.getElementById("banner").innerHTML = oModel.getProperty("/banner");
If attacker input reaches those sinks, game over.
Developers often justify this with “it’s only internal data” or “the backend already validated it.” I’ve heard both right before a bug bounty report landed.
Fix
Use text APIs, not HTML APIs.
this.byId("status").$().text(oModel.getProperty("/statusMessage"));
Or better, stop mutating rendered DOM and update the control property instead:
this.byId("statusText").setText(oModel.getProperty("/statusMessage"));
That keeps rendering inside the framework, where escaping rules are more predictable.
If you see innerHTML, outerHTML, document.write, jQuery.html(), or string-based DOM construction in a UI5 codebase, those lines deserve immediate review.
Mistake #4: Trusting formatter functions too much
Formatters are useful, but they often become tiny XSS gadgets because they combine data transformation with output generation.
Bad formatter:
formatWelcome: function (sName) {
return "<strong>Welcome, " + sName + "</strong>";
}
Used like this:
<core:HTML content="{ path: '/name', formatter: '.formatWelcome' }" />
That’s unsafe for the same reason raw HTML is unsafe: the formatter injects untrusted data into markup.
Another subtle issue is returning URL-like values from a formatter without validating scheme or destination.
formatLink: function (sUrl) {
return sUrl;
}
Used in a link control, an attacker might supply javascript:alert(1) if the control or custom rendering path doesn’t defend against it.
Fix
Keep formatters focused on plain text or strict value mapping.
Safe formatter:
formatWelcome: function (sName) {
return "Welcome, " + sName;
}
Then bind it to a text property:
<Text text="{ path: '/name', formatter: '.formatWelcome' }" />
For URLs, validate allowed schemes explicitly:
formatLink: function (sUrl) {
try {
const u = new URL(sUrl, window.location.origin);
if (u.protocol === "http:" || u.protocol === "https:") {
return u.href;
}
} catch (e) {}
return "";
}
If a formatter returns HTML, I assume it’s dangerous until proven otherwise.
Mistake #5: Rendering backend error messages as HTML
This one shows up all the time with OData errors, validation responses, and “friendly” backend messages.
Bad pattern:
const sError = oError.responseText;
this.byId("errorBox").setContent(`<pre>${sError}</pre>`);
Or:
MessageBox.error(oError.message);
The second example is not always unsafe by itself, depending on the control and how the message is rendered. The mistake is assuming every backend-supplied error string is harmless. If backend systems reflect request content, a malicious payload can bounce back into the UI.
Fix
Parse structured error responses and display only the fields you need as text.
let sMessage = "Unexpected error";
try {
const oResponse = JSON.parse(oError.responseText);
sMessage = oResponse.error?.message?.value || sMessage;
} catch (e) {}
this.byId("errorText").setText(sMessage);
Don’t dump whole backend payloads into HTML containers. If you need raw diagnostics for admins, put them in a text area or escaped <pre> equivalent rendered as text, not markup.
Mistake #6: Forgetting that custom controls need safe rendering too
Built-in UI5 controls usually do the right thing. Custom controls are where teams accidentally reintroduce old-school XSS.
Bad renderer:
renderer: function (oRm, oControl) {
oRm.write("<div>");
oRm.write(oControl.getValue());
oRm.write("</div>");
}
If getValue() contains attacker data, oRm.write() dumps it directly into the response.
Fix
Use escaping-aware renderer APIs.
renderer: function (oRm, oControl) {
oRm.openStart("div", oControl);
oRm.openEnd();
oRm.text(oControl.getValue());
oRm.close("div");
}
If you’re writing custom renderers, prefer methods that understand text versus raw markup. The official UI5 documentation covers renderer behavior and control development: https://ui5.sap.com/
This is one of those places where a tiny API choice decides whether your control is safe by default.
Mistake #7: Ignoring CSP because “UI5 already protects us”
Framework defenses reduce XSS risk. CSP limits impact when something slips through. You want both.
A decent Content Security Policy can block inline scripts, restrict script sources, and make common payloads fail even if bad HTML lands in the page.
A minimal starting point might look like this:
Content-Security-Policy:
default-src 'self';
script-src 'self';
object-src 'none';
base-uri 'self';
frame-ancestors 'self';
The exact policy depends on your UI5 bootstrap, hosting model, and any third-party assets. If you need implementation guidance, https://csp-guide.com is useful for policy design details. For framework-specific behavior, check SAPUI5 documentation: https://ui5.sap.com/
Fix
Deploy CSP in report-only mode first, then enforce it. Remove inline scripts where possible, lock down script sources, and don’t allow unsafe-inline unless you’ve exhausted every alternative.
CSP won’t fix a vulnerable sink. It does make exploitation harder, and sometimes that’s the difference between a bug and an incident.
Mistake #8: Skipping XSS tests in UI flows
A lot of SAPUI5 teams test business logic thoroughly and barely test rendering abuse cases. That’s how obvious XSS survives.
I like to test every place where untrusted data appears:
- user profile fields
- search terms
- comments
- imported CSV content
- backend error messages
- admin-configurable banners
- deep-link parameters
Basic payloads still catch real bugs:
"><img src=x onerror=alert(1)>
<script>alert(1)</script>
javascript:alert(1)
Fix
Build XSS checks into your normal QA and code review flow.
Ask simple questions:
- Does this control render text or HTML?
- Are we building strings that become DOM?
- Can backend data contain markup?
- Are custom renderers escaping output?
- Are links and URLs validated?
Those five questions catch most SAPUI5 XSS issues before production.
What I’d actually enforce on a SAPUI5 team
If I were setting guardrails for a team, they’d be blunt:
- No
sap.ui.core.HTMLwith untrusted data. - No
innerHTMLorjQuery.html()in application code. - Custom renderers must use text-escaping APIs.
- Formatters return text, not markup.
- Backend messages are displayed as text only.
- CSP is enabled and tightened over time.
That’s not fancy. It works.
SAPUI5 gives you a safer baseline than many frontend stacks, but only if you stay inside the rails. Most XSS bugs happen the moment developers decide they need “just a little HTML.” That little shortcut is usually where the vulnerability starts.