Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d52a0c7
Ensure sanitizeContent attribute is respected
mbaluda Nov 19, 2025
1ec8bfa
Ensure sanitizeContent attribute is respected
mbaluda Nov 19, 2025
0a7b668
Add more tests
mbaluda Nov 19, 2025
6a00000
Exclude sanitized HTML views from XSS sinks
mbaluda Nov 21, 2025
807ea34
Fix ql-4-ql alerts
mbaluda Nov 21, 2025
02c4537
Fix workflow
mbaluda Nov 21, 2025
235c441
Update expected file
mbaluda Nov 21, 2025
f108236
fix README
mbaluda Nov 21, 2025
ee49547
Address review comments
mbaluda Nov 25, 2025
9b50447
Fix conflict
mbaluda Nov 30, 2025
ddf7a6e
Merge branch 'main' into mbaluda/sanitize-content
mbaluda Nov 30, 2025
1a2c5cc
update expected sile
mbaluda Nov 30, 2025
0929f6e
fix expected file
mbaluda Nov 30, 2025
6baa2d7
Merge branch 'main' into mbaluda/sanitize-content
mbaluda Dec 1, 2025
744bf21
Update javascript/frameworks/ui5/lib/advanced_security/javascript/fra…
mbaluda Dec 2, 2025
9a36d3d
improved sink tests
mbaluda Dec 3, 2025
6d62945
update ui5.model.yml with RTE sinks
mbaluda Dec 3, 2025
b301afd
rename function isHTMLSanitized
mbaluda Dec 3, 2025
41ac329
Fix sink test for RichTextEditor
mbaluda Dec 3, 2025
213e17a
tests
mbaluda Dec 3, 2025
0b88cb8
Merge branch 'main' into mbaluda/sanitize-content
mbaluda Dec 8, 2025
bb2aab5
Address review
mbaluda Dec 8, 2025
8072f6b
missing tag
mbaluda Dec 8, 2025
e2bf1b3
Merge branch 'main' into mbaluda/sanitize-content
jeongsoolee09 Dec 11, 2025
863ea7b
Address review 2
mbaluda Dec 11, 2025
03ebe7f
Merge branch 'main' into mbaluda/sanitize-content
mbaluda Dec 11, 2025
4808163
update expected file
mbaluda Dec 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/code_scanning.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:

- name: Initialize CodeQL
id: initialize-codeql
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
env:
# Add our custom extractor to the CodeQL search path
CODEQL_ACTION_EXTRA_OPTIONS: '{"database":{"init":["--search-path","${{ github.workspace }}/extractors"]}}'
Expand All @@ -62,7 +62,7 @@ jobs:

- name: Perform CodeQL Analysis
id: analyze
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
env:
LGTM_INDEX_XML_MODE: all
LGTM_INDEX_FILETYPES: ".json:JSON"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/javascript.sarif.expected

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions javascript/frameworks/ui5/ext/ui5.model.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ extensions:
- ["UI5HTMLControl", "Argument[0].Member[content]", "ui5-html-injection"]
- ["UI5HTMLControl", "Member[content]", "ui5-html-injection"]
- ["UI5HTMLControl", "Member[setContent].Argument[0]", "ui5-html-injection"]
- ["sap/ui/richtexteditor/RichTextEditor", "Member[value]", "ui5-html-injection"]
- ["sap/ui/richtexteditor/RichTextEditor", "Member[setValue].Argument[0]", "ui5-html-injection"]
- ["Patcher", "Member[unsafeHtml].Argument[0..]", "ui5-html-injection"]
- ["RenderManager", "Member[write,unsafeHtml].Argument[0..]", "ui5-html-injection"]
- ["RenderManager", "Member[writeAttribute,addStyle].Argument[0..1]", "ui5-html-injection"]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import javascript
import DataFlow
import advanced_security.javascript.frameworks.ui5.UI5
import advanced_security.javascript.frameworks.ui5.dataflow.DataFlow
private import semmle.javascript.frameworks.data.internal.ApiGraphModelsExtensions as ApiGraphModelsExtensions
Expand Down Expand Up @@ -648,16 +646,7 @@ class XmlView extends UI5View instanceof XmlFile {
type = result.getControlTypeName() and
ApiGraphModelsExtensions::sinkModel(getASuperType(type), path, "ui5-html-injection", _) and
property = path.replaceAll(" ", "").regexpCapture("Member\\[([^\\]]+)\\]", 1) and
result.getBindingTarget() = control.getAttribute(property) and
/* If the control is an `sap.ui.core.HTML` then the control should be missing the `sanitizeContent` attribute */
(
getASuperType(type) = "HTMLControl"
implies
(
not exists(control.getAttribute("sanitizeContent")) or
control.getAttribute("sanitizeContent").getValue() = "false"
)
)
result.getBindingTarget() = control.getAttribute(property)
)
}

Expand Down Expand Up @@ -823,7 +812,7 @@ class UI5Control extends TUI5Control {
}

/** Holds if this control reads from or writes to a model. */
predicate accessesModel(UI5Model model) { accessesModel(model, _) }
predicate accessesModel(UI5Model model) { this.accessesModel(model, _) }

/** Holds if this control reads from or writes to a model with regards to a binding path. */
predicate accessesModel(UI5Model model, XmlBindingPath bindingPath) {
Expand All @@ -842,6 +831,43 @@ class UI5Control extends TUI5Control {

/** Get the controller that manages this control. */
CustomController getController() { result = this.getView().getController() }

/**
* Gets the full import path of the associated control.
*/
string getControlTypeName() { result = this.getQualifiedType().replaceAll(".", "/") }

/**
* Holds if the control content is sanitized for HTML
* 'sap/ui/core/HTML' sanitized using the property 'sanitizeContent'
* 'sap/ui/richttexteditor/RichTextEditor' sanitized using the property 'sanitizeValue'
*/
predicate isHTMLSanitized() {
this.getControlTypeName() = "sap/ui/richtexteditor/RichTextEditor" and
not this.isSanitizePropertySetTo("sanitizeValue", false)
or
this.getControlTypeName() = "sap/ui/core/HTML" and
this.isSanitizePropertySetTo("sanitizeContent", true) and
not this.isSanitizePropertySetTo("sanitizeContent", false)
}

bindingset[propName, val]
private predicate isSanitizePropertySetTo(string propName, boolean val) {
/* 1. `sanitizeContent` attribute is set declaratively. */
this.getProperty(propName).toString() = val.toString()
or
/* 2. `sanitizeContent` attribute is set programmatically using setProperty(). */
exists(CallNode node | node = this.getAReference().getAMemberCall("setProperty") |
node.getArgument(0).getStringValue() = propName and
not node.getArgument(1).mayHaveBooleanValue(val.booleanNot())
)
or
/* 3. `sanitizeContent` attribute is set programmatically using a setter. */
exists(CallNode node |
node = this.getAReference().getAMemberCall("setS" + propName.suffix(1)) and
not node.getArgument(0).mayHaveBooleanValue(val.booleanNot())
)
}
}

private newtype TUI5ControlProperty =
Expand All @@ -857,7 +883,7 @@ class UI5ControlProperty extends TUI5ControlProperty {
ValueNode asJsControlProperty() { this = TJsControlProperty(result) }

string toString() {
result = this.asXmlControlProperty().toString() or
result = this.asXmlControlProperty().getValue().toString() or
result = this.asJsonControlProperty().toString() or
result = this.asJsControlProperty().toString()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import javascript
import semmle.javascript.dataflow.DataFlow as StdLibDataFlow
import advanced_security.javascript.frameworks.ui5.UI5
import advanced_security.javascript.frameworks.ui5.UI5View
import advanced_security.javascript.frameworks.ui5.RemoteFlowSources
import advanced_security.javascript.frameworks.ui5.dataflow.FlowSteps
private import PatchDataFlow
Expand Down Expand Up @@ -107,6 +106,7 @@ module UI5PathGraph<PathNodeSig ConfigPathNode, PathGraphSig<ConfigPathNode> Con
}

UI5PathNode getAPrimaryHtmlISink() {
not result.asUI5BindingPathNode().getControlDeclaration().isHTMLSanitized() and
if
this.asDataFlowNode() instanceof LocalModelContentBoundBidirectionallyToHtmlISinkControl or
this.asDataFlowNode() instanceof UI5ExternalModel // TODO: Narrow it down to ExternalModelBoundToHtmlISinkControl
Expand Down
6 changes: 5 additions & 1 deletion javascript/frameworks/ui5/test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ User input flows to XSS sinks via event handlers in 4 different ways:

### [xss-html-control](queries/UI5Xss/xss-html-control)
- `sap.ui.core.HTML` Control
- sanitization using the `sanitizeContent` property
- sanitization disabled by programmatically setting the `sanitizeContent` property to false

### [xss-html-control-df](queries/UI5Xss/xss-html-control-df)
- `sap.ui.core.HTML` Control
Expand All @@ -57,15 +59,17 @@ User input flows to XSS sinks via event handlers in 4 different ways:

### [xss-html-view](queries/UI5Xss/xss-html-view)
- `sap.ui.core.mvc.HTMLView` View
-

### [xss-indirect-control](queries/UI5Xss/xss-indirect-control)
- control accessed indirectly

### [xss-js-view](queries/UI5Xss/xss-js-view)
- `sap.ui.core.mvc.JSView` View
- sanitization using the `sanitizeContent` property

### [xss-json-view](queries/UI5Xss/xss-json-view)
- `sap.ui.core.mvc.JSONView` View
- sanitization using the `sanitizeContent` property

### [xss-separate-renderer](queries/UI5Xss/xss-separate-renderer)
- `renderer` property is set to a class name (a string)
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
| sink1.xml:5:5:5:44 | content={path: '/input'} | The binding path `content={path: '/input'}` is an HTML injection sink. |
| sink1.xml:7:5:7:68 | content={path: '/input'} | The binding path `content={path: '/input'}` is an HTML injection sink. |
| sink1.xml:8:5:8:44 | content={path: '/input'} | The binding path `content={path: '/input'}` is an HTML injection sink. |
| sink1.xml:10:5:10:73 | value={path: '/input'} | The binding path `value={path: '/input'}` is an HTML injection sink. |
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ import javascript
import advanced_security.javascript.frameworks.ui5.UI5View

from UI5BindingPath bp
where bp = any(UI5View ui5v).getAnHtmlISink()
where
bp = any(UI5View ui5v).getAnHtmlISink() and
not bp.getControlDeclaration().isHTMLSanitized()
select bp, "The binding path `" + bp.toString() + "` is an HTML injection sink."
18 changes: 9 additions & 9 deletions javascript/frameworks/ui5/test/models/sink/logSinkTest.expected
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
| sink.js:24:38:24:42 | code0 | SAP UI5 log injection sink with kind: ui5-log-injection |
| sink.js:24:45:24:49 | code1 | SAP UI5 log injection sink with kind: ui5-log-injection |
| sink.js:24:52:24:56 | code2 | SAP UI5 log injection sink with kind: ui5-log-injection |
| sink.js:25:38:25:42 | code0 | SAP UI5 log injection sink with kind: ui5-log-injection |
| sink.js:25:45:25:49 | code1 | SAP UI5 log injection sink with kind: ui5-log-injection |
| sink.js:25:52:25:56 | code2 | SAP UI5 log injection sink with kind: ui5-log-injection |
| sink.js:27:40:27:44 | code0 | SAP UI5 log injection sink with kind: ui5-log-injection |
| sink.js:27:47:27:51 | code1 | SAP UI5 log injection sink with kind: ui5-log-injection |
| sink.js:27:54:27:58 | code2 | SAP UI5 log injection sink with kind: ui5-log-injection |
| sink.js:26:38:26:42 | code0 | SAP UI5 log injection sink with kind: ui5-log-injection |
| sink.js:26:45:26:49 | code1 | SAP UI5 log injection sink with kind: ui5-log-injection |
| sink.js:26:52:26:56 | code2 | SAP UI5 log injection sink with kind: ui5-log-injection |
| sink.js:27:38:27:42 | code0 | SAP UI5 log injection sink with kind: ui5-log-injection |
| sink.js:27:45:27:49 | code1 | SAP UI5 log injection sink with kind: ui5-log-injection |
| sink.js:27:52:27:56 | code2 | SAP UI5 log injection sink with kind: ui5-log-injection |
| sink.js:28:40:28:44 | code0 | SAP UI5 log injection sink with kind: ui5-log-injection |
| sink.js:28:47:28:51 | code1 | SAP UI5 log injection sink with kind: ui5-log-injection |
| sink.js:28:54:28:58 | code2 | SAP UI5 log injection sink with kind: ui5-log-injection |
| sink.js:29:37:29:41 | code0 | SAP UI5 log injection sink with kind: ui5-log-injection |
| sink.js:29:44:29:48 | code1 | SAP UI5 log injection sink with kind: ui5-log-injection |
| sink.js:29:51:29:55 | code2 | SAP UI5 log injection sink with kind: ui5-log-injection |
Expand Down
17 changes: 12 additions & 5 deletions javascript/frameworks/ui5/test/models/sink/sink.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ sap.ui.define(
"sap/base/util/Properties",
"sap/ui/core/RenderManager",
"sap/ui/util/Storage",
"sap/ui/core/util/File"
"sap/ui/core/util/File",
"sap/ui/richtexteditor/RichTextEditor"
],
function (
LoaderExtensions,
Expand All @@ -19,13 +20,12 @@ sap.ui.define(
Properties,
RenderManager,
Storage,
File
File,
RichTextEditor
) {
var value = jQuery.sap.log.fatal(code0, code1, code2);
var value = jQuery.sap.log.error(code0, code1, code2);

var value = jQuery.sap.log.warning(code0, code1, code2);

var value = jQuery.sap.log.info(code0, code1, code2);

var value = jQuery.sap.log.debug(code0, code1, code2);
Expand Down Expand Up @@ -94,7 +94,7 @@ sap.ui.define(
var value = obj.registerResourcePath(code0, code1);
var obj = new Properties();
var value = obj.create(code0);
var obj = new HTML({content: code0});
var obj = new HTML({ content: code0 }); // UNSAFE: Content is not sanitized
obj.content = code0;
obj.setContent(code0);
var obj = new Patcher();
Expand Down Expand Up @@ -125,5 +125,12 @@ sap.ui.define(
var value = sap.ui.core.util.File.save(code0, code1, "csv", "text/csv", code4, code5);
var value = sap.ui.core.util.File.save(code0, code1, "csv", "text/plain", code4, code5);
var value = sap.ui.core.util.File.save(code0, code1, code2, code3, code4, code5);

var obj = new HTML({ content: code0, sanitizeContent: true }); // SAFE: Content is sanitized
var obj = new HTML({ content: code0, sanitizeContent: false }); // UNSAFE: Content is explicitly not sanitized

var obj = new RichTextEditor({ value: code0 }); // SAFE: Content is sanitized by default
var obj = new RichTextEditor({ value: code0, sanitizeValue: true }); // SAFE: Content is sanitized
var obj = new RichTextEditor({ value: code0, sanitizeValue: false }); // UNSAFE: Content is explicitly not sanitized
},
);
8 changes: 7 additions & 1 deletion javascript/frameworks/ui5/test/models/sink/sink1.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
<mvc:View controllerName="codeql-sap-js.controller.app"
xmlns="sap.m"
xmlns:core="sap.ui.core"
xmlns:mvc="sap.ui.core.mvc">
xmlns:mvc="sap.ui.core.mvc"
xmlns:rte="sap.ui.richtexteditor">
<core:HTML content="{path: '/input'}" sanitizeContent="true"/> <!--sanitized XSS sink sap.ui.core.HTML.content -->
<core:HTML content="{path: '/input'}" sanitizeContent="false"/> <!--XSS sink sap.ui.core.HTML.content -->
<core:HTML content="{path: '/input'}"/> <!--XSS sink sap.ui.core.HTML.content -->
<rte:RichTextEditor value="{path: '/input'}" sanitizeValue="true"/> <!--sanitized XSS sink sap.ui.core.HTML.content -->
<rte:RichTextEditor value="{path: '/input'}" sanitizeValue="false"/> <!--XSS sink sap.ui.core.HTML.content -->
<rte:RichTextEditor value="{path: '/input'}"/> <!--default sanitized XSS sink sap.ui.core.HTML.content -->
</mvc:View>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
| sink.js:61:39:61:43 | code0 | code0 |
| sink.js:97:34:97:38 | code0 | code0 |
| sink.js:97:35:97:39 | code0 | code0 |
| sink.js:98:19:98:23 | code0 | code0 |
| sink.js:99:20:99:24 | code0 | code0 |
| sink.js:101:32:101:36 | code0 | code0 |
Expand All @@ -10,3 +10,5 @@
| sink.js:109:37:109:41 | code1 | code1 |
| sink.js:111:30:111:34 | code0 | code0 |
| sink.js:113:32:113:36 | code0 | code0 |
| sink.js:129:35:129:39 | code0 | code0 |
| sink.js:130:35:130:39 | code0 | code0 |
Loading