Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fbc4be8
Add an XSS example that uses `sap/ui/core/EventBus`
jeongsoolee09 Nov 19, 2025
d7a8b30
Add the first draft of working query
jeongsoolee09 Nov 24, 2025
77c96b1
Update expected results for both jobs
jeongsoolee09 Nov 24, 2025
969c218
Regress to previous expected file to unblock check
jeongsoolee09 Nov 24, 2025
3e5255a
Expected file from Action summary
mbaluda Nov 25, 2025
9331ad5
Merge branch 'main' into jeongsoolee09/add-eventbus-example
jeongsoolee09 Dec 4, 2025
7a020d2
Ensure that the channel name and the message types are matched
jeongsoolee09 Dec 11, 2025
4e32adc
Merge branch 'jeongsoolee09/add-eventbus-example' of github.com:advan…
jeongsoolee09 Dec 11, 2025
3fde2af
Add hierarchy of EventBus classes
jeongsoolee09 Dec 11, 2025
28b78dc
Renaming of abstract predicate
jeongsoolee09 Dec 11, 2025
94b0b8a
Add examples of sap.ui.getCore() and this.getOwnerComponent()
jeongsoolee09 Dec 12, 2025
29e0862
Fix minor bug in the regex
jeongsoolee09 Dec 12, 2025
e3ff3c0
Checkpoint
jeongsoolee09 Dec 12, 2025
4779663
Checkpoint
jeongsoolee09 Dec 12, 2025
b05963e
Final draft
jeongsoolee09 Dec 12, 2025
44dc40e
Update expected file of code scanning
jeongsoolee09 Dec 12, 2025
15ff2eb
Remove test predicates
jeongsoolee09 Dec 15, 2025
fcf43dc
Port ControlTypeInHandlerModel to QL
jeongsoolee09 Dec 15, 2025
904b1fe
Fix bug in UI5ControlHandlerParameter
jeongsoolee09 Dec 15, 2025
6876bcb
Add UI5HTMLControlReferenceContentAPI
jeongsoolee09 Dec 15, 2025
d665b93
Introduce hierarchy on control-related API calls
jeongsoolee09 Dec 15, 2025
f75f848
Remove ControlTypeInHandlerModel and minor formatting
jeongsoolee09 Dec 15, 2025
af948b0
Merge branch 'main' into jeongsoolee09/add-eventbus-example
jeongsoolee09 Dec 15, 2025
dd9277e
Revert hack in `ControlReference`
jeongsoolee09 Dec 15, 2025
66a4ddc
Update expected results of Code Scanning
jeongsoolee09 Dec 15, 2025
a82f5ad
Add `subscribeOnce` as recognized subscription API
jeongsoolee09 Dec 15, 2025
51d9cce
Add documentation comments on branches
jeongsoolee09 Dec 15, 2025
8a8fff2
Remove commented-out test predicate
jeongsoolee09 Dec 15, 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
2 changes: 1 addition & 1 deletion .github/workflows/javascript.sarif.expected

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions javascript/frameworks/ui5/ext/ui5.model.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ extensions:
pack: codeql/javascript-all
extensible: "typeModel"
data:
- ["SapUICoreInstance", "global", "Member[sap].Member[ui].Member[getCore].ReturnValue"]
- ["Control", "Control", "Instance"]
- ["Control", "sap/ui/core/Control", ""]
- ["Control", "global", "Member[sap].Member[ui].Member[core].Member[Control]"]
Expand Down Expand Up @@ -71,6 +72,19 @@ extensions:
- ["UI5ClientStorage", "global", "Member[jQuery].Member[sap].Member[storage]"]
- ["UI5ClientStorage", "sap/ui/core/util/File", ""]
- ["UI5ClientStorage", "global", "Member[sap].Member[ui].Member[core].Member[util].Member[File]"]
# Publishing and Subscribing to Events
- ["UI5EventBusInstance", "sap/ui/core/EventBus", "Member[getInstance].ReturnValue"]
- ["UI5EventBusPublish", "UI5EventBusInstance", "Member[publish]"]
- ["UI5EventBusPublishedEventData", "UI5EventBusPublish", "Argument[2]"]
- ["UI5EventBusSubscribe", "UI5EventBusInstance", "Member[subscribe]"]
- ["UI5EventSubscriptionHandlerDataParameter", "UI5EventBusSubscribe", "Argument[2].Parameter[2]"]
- ["SapUICoreEventBusInstance", "SapUICoreInstance", "Member[getEventBus].ReturnValue"]
- ["SapUICoreEventBusPublish", "SapUICoreEventBusInstance", "Member[publish]"]
- ["SapUICoreEventBusPublishedEventData", "SapUICoreEventBusPublish", "Argument[2]"]
- ["SapUICoreEventBusSubscribe", "SapUICoreEventBusInstance", "Member[subscribe]"]
- ["SapUICoreEventSubscriptionHandlerDataParameter", "SapUICoreEventBusSubscribe", "Argument[2].Parameter[2]"]
# Extend Calls
# -

- addsTo:
pack: codeql/javascript-all
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import javascript
import DataFlow
import advanced_security.javascript.frameworks.ui5.JsonParser
import advanced_security.javascript.frameworks.ui5.dataflow.TypeTrackers
import semmle.javascript.security.dataflow.DomBasedXssCustomizations
import advanced_security.javascript.frameworks.ui5.UI5View
import advanced_security.javascript.frameworks.ui5.UI5HTML
Expand Down Expand Up @@ -138,6 +139,15 @@ class WebApp extends HTML::HtmlFile {
* by a call to `sap.ui.getCore()`.
*/
class SapUiCore extends MethodCallNode {
/*
* NOTE: Ideally, we'd like to use `ModelOutput::getATypeNode("SapUICore").asSource()` to
* take advantage of the inter-procedural flexibility of MaD, but doing so causes
* non-monotomic recursion.
*
* So, we opt to use `SourceNode.getAPropertyRead/1` and `SourceNode.getAMethodCall/1`
* instead and get away with local flow tracking they provide.
*/

SapUiCore() { this = globalVarRef("sap").getAPropertyRead("ui").getAMethodCall("getCore") }
}

Expand Down Expand Up @@ -459,7 +469,8 @@ class CustomController extends SapExtendCall {
}

Component getOwnerComponent() {
exists(ManifestJson manifestJson, JsonObject rootObj | manifestJson = result.getManifestJson() |
exists(ManifestJson manifestJson, JsonObject rootObj |
manifestJson = result.getManifestJson() and
rootObj
.getPropValue("targets")
.(JsonObject)
Expand Down Expand Up @@ -764,7 +775,7 @@ class Component extends SapExtendCall {
]).getAMemberCall("extend")
}

string getId() { result = this.getName().regexpCapture("([a-zA-Z0-9.]+).Component", 1) }
string getId() { result = this.getName().regexpCapture("(.+).Component", 1) }

ManifestJson getManifestJson() {
this.getMetadata().getAPropertySource("manifest").asExpr().(StringLiteral).getValue() = "json" and
Expand Down Expand Up @@ -1428,18 +1439,158 @@ class PropertyMetadata extends ObjectLiteralNode {
}
}

module TypeTrackers {
private SourceNode hasDependency(TypeTracker t, string dependencyPath) {
t.start() and
exists(UserModule d |
d.getADependency() = dependencyPath and
result = d.getRequiredObject(dependencyPath).asSourceNode()
)
or
exists(TypeTracker t2 | result = hasDependency(t2, dependencyPath).track(t2, t))
module EventBus {
abstract class EventBusPublishCall extends CallNode {
abstract EventBusSubscribeCall getAMatchingSubscribeCall();

abstract DataFlow::Node getPublishedData();

string getChannelName() { result = this.getArgument(0).getALocalSource().getStringValue() }

string getMessageType() { result = this.getArgument(1).getALocalSource().getStringValue() }
}

abstract class EventBusSubscribeCall extends CallNode {
abstract EventBusPublishCall getMatchingPublishCall();

abstract DataFlow::Node getSubscriptionData();

string getChannelName() { result = this.getArgument(0).getALocalSource().getStringValue() }

string getMessageType() { result = this.getArgument(1).getALocalSource().getStringValue() }
}

class GlobalEventBusPublishCall extends EventBusPublishCall {
API::Node publishMethod;

GlobalEventBusPublishCall() {
publishMethod = ModelOutput::getATypeNode("UI5EventBusPublish") and
this = publishMethod.getACall()
}

override GlobalEventBusSubscribeCall getAMatchingSubscribeCall() {
result.getChannelName() = this.getChannelName() and
result.getMessageType() = this.getMessageType()
}

override DataFlow::Node getPublishedData() {
exists(API::Node publishedData |
publishedData = ModelOutput::getATypeNode("UI5EventBusPublishedEventData")
|
publishMethod.getASuccessor*() = publishedData and
result = publishedData.getInducingNode()
)
}
}

class SapUICoreEventBusPublishCall extends EventBusPublishCall {
API::Node publishMethod;

SapUICoreEventBusPublishCall() {
publishMethod = ModelOutput::getATypeNode("SapUICoreEventBusPublish") and
this = publishMethod.getACall()
}

override SapUICoreEventBusSubscribeCall getAMatchingSubscribeCall() {
result.getChannelName() = this.getChannelName() and
result.getMessageType() = this.getMessageType()
}

override DataFlow::Node getPublishedData() {
exists(API::Node publishedData |
publishedData = ModelOutput::getATypeNode("SapUICoreEventBusPublishedEventData")
|
publishMethod.getASuccessor*() = publishedData and
result = publishedData.getInducingNode()
)
}
}

class ComponentEventBusPublishCall extends EventBusPublishCall {
CustomController controller;
Component component;

ComponentEventBusPublishCall() {
component = controller.getOwnerComponent() and
this = controller.getOwnerComponentRef().getAMemberCall("publish")
}

override ComponentEventBusSubscribeCall getAMatchingSubscribeCall() {
result.getChannelName() = this.getChannelName() and
result.getMessageType() = this.getMessageType() and
result.getComponent() = this.getComponent()
}

override DataFlow::Node getPublishedData() { result = this.getArgument(2) }

Component getComponent() { result = component }
}

class GlobalEventBusSubscribeCall extends EventBusSubscribeCall {
API::Node subscribeMethod;

GlobalEventBusSubscribeCall() {
subscribeMethod = ModelOutput::getATypeNode("UI5EventBusSubscribe") and
this = subscribeMethod.getACall()
}

override GlobalEventBusPublishCall getMatchingPublishCall() {
result.getChannelName() = this.getChannelName() and
result.getMessageType() = this.getMessageType()
}

override DataFlow::Node getSubscriptionData() {
exists(API::Node subscribeMethodCallbackDataParameter |
subscribeMethodCallbackDataParameter =
ModelOutput::getATypeNode("UI5EventSubscriptionHandlerDataParameter")
|
subscribeMethod.getASuccessor*() = subscribeMethodCallbackDataParameter and
result = subscribeMethodCallbackDataParameter.getInducingNode()
)
}
}

SourceNode hasDependency(string dependencyPath) {
result = hasDependency(TypeTracker::end(), dependencyPath)
class SapUICoreEventBusSubscribeCall extends EventBusSubscribeCall {
API::Node subscribeMethod;

SapUICoreEventBusSubscribeCall() {
subscribeMethod = ModelOutput::getATypeNode("SapUICoreInstance") and
this = subscribeMethod.getACall()
}

override SapUICoreEventBusPublishCall getMatchingPublishCall() {
result.getChannelName() = this.getChannelName() and
result.getMessageType() = this.getMessageType()
}

override DataFlow::Node getSubscriptionData() {
exists(API::Node subscribeMethodCallbackDataParameter |
subscribeMethodCallbackDataParameter =
ModelOutput::getATypeNode("SapUICoreEventSubscriptionHandlerDataParameter")
|
subscribeMethod.getASuccessor*() = subscribeMethodCallbackDataParameter and
result = subscribeMethodCallbackDataParameter.getInducingNode()
)
}
}

class ComponentEventBusSubscribeCall extends EventBusSubscribeCall {
CustomController controller;
Component component;

ComponentEventBusSubscribeCall() {
component = controller.getOwnerComponent() and
this = controller.getOwnerComponentRef().getAMemberCall("subscribe")
}

override ComponentEventBusPublishCall getMatchingPublishCall() {
result.getChannelName() = this.getChannelName() and
result.getMessageType() = this.getMessageType() and
result.getComponent() = this.getComponent()
}

override DataFlow::Node getSubscriptionData() { result = this.getABoundCallbackParameter(2, 2) }

Component getComponent() { result = component }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,16 @@ class LogArgumentToListener extends DataFlow::SharedFlowStep {
logArgumentToListener(start, end)
}
}

class PublishedEventToEventSubscribedEventData extends DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node start, DataFlow::Node end) {
exists(
EventBus::EventBusPublishCall publishCall, EventBus::EventBusSubscribeCall subscribeCall
|
publishCall.getAMatchingSubscribeCall() = subscribeCall
|
start = publishCall.getPublishedData() and
end = subscribeCall.getSubscriptionData()
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import javascript
import DataFlow

Comment thread
mbaluda marked this conversation as resolved.
module TypeTrackers {
private SourceNode hasDependency(TypeTracker t, string dependencyPath) {
t.start() and
exists(UserModule d |
d.getADependency() = dependencyPath and
result = d.getRequiredObject(dependencyPath).asSourceNode()
)
or
exists(TypeTracker t2 | result = hasDependency(t2, dependencyPath).track(t2, t))
}

SourceNode hasDependency(string dependencyPath) {
result = hasDependency(TypeTracker::end(), dependencyPath)
}

private MethodCallNode getOwnerComponentRef(TypeTracker t, CustomController customController) {
customController.getAThisNode() = result.getReceiver() and
result.getMethodName() = "getOwnerComponent"
or
exists(TypeTracker t2 | result = getOwnerComponentRef(t2, customController).track(t2, t))
Comment thread
jeongsoolee09 marked this conversation as resolved.
}

/* owner component ref */
Comment thread
jeongsoolee09 marked this conversation as resolved.
MethodCallNode getOwnerComponentRef(CustomController customController) {
result = getOwnerComponentRef(TypeTracker::end(), customController)
}

private class ObjFieldStep extends SharedTypeTrackingStep {

Check warning

Code scanning / CodeQL-Community

Dead code

This code is never used, and it's not publicly exported.
override predicate step(DataFlow::Node start, DataFlow::Node end) {
exists(SapExtendCall sapExtendCall, ObjectLiteralNode wrappedObject, string name |
wrappedObject = sapExtendCall.getContent() and
start = getAnAlias(wrappedObject).getAPropertyWrite(name).getRhs() and
end = getAnAlias(wrappedObject).getAPropertyRead(name)
)
}
}

private DataFlow::SourceNode getAnAlias(DataFlow::SourceNode object) {
Comment thread
jeongsoolee09 marked this conversation as resolved.
result = object
or
result = getAnAlias(object).getAPropertySource().(DataFlow::FunctionNode).getReceiver()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
nodes
| webapp/controller/app.controller.js:26:11:26:15 | value |
| webapp/controller/app.controller.js:26:19:26:35 | oInput.getValue() |
| webapp/controller/app.controller.js:27:45:27:62 | { message: value } |
| webapp/controller/app.controller.js:27:56:27:60 | value |
| webapp/controller/app.controller.js:30:34:30:38 | model |
| webapp/controller/app.controller.js:32:30:32:34 | model |
| webapp/controller/app.controller.js:32:30:32:42 | model.message |
| webapp/view/app.view.xml:5:3:8:29 | value={/input} |
| webapp/view/app.view.xml:11:3:12:37 | content={/output1} |
edges
| webapp/controller/app.controller.js:15:9:15:19 | input: null | webapp/view/app.view.xml:5:3:8:29 | value={/input} |
| webapp/controller/app.controller.js:16:9:16:21 | output1: null | webapp/view/app.view.xml:11:3:12:37 | content={/output1} |
| webapp/controller/app.controller.js:18:20:18:39 | new JSONModel(oData) | webapp/view/app.view.xml:11:3:12:37 | content={/output1} |
| webapp/controller/app.controller.js:26:11:26:15 | value | webapp/controller/app.controller.js:27:56:27:60 | value |
| webapp/controller/app.controller.js:26:19:26:35 | oInput.getValue() | webapp/controller/app.controller.js:26:11:26:15 | value |
| webapp/controller/app.controller.js:27:45:27:62 | { message: value } | webapp/controller/app.controller.js:30:34:30:38 | model |
| webapp/controller/app.controller.js:27:56:27:60 | value | webapp/controller/app.controller.js:27:45:27:62 | { message: value } |
| webapp/controller/app.controller.js:30:34:30:38 | model | webapp/controller/app.controller.js:32:30:32:34 | model |
| webapp/controller/app.controller.js:32:30:32:34 | model | webapp/controller/app.controller.js:32:30:32:42 | model.message |
| webapp/view/app.view.xml:5:3:8:29 | value={/input} | webapp/controller/app.controller.js:15:9:15:19 | input: null |
| webapp/view/app.view.xml:5:3:8:29 | value={/input} | webapp/controller/app.controller.js:18:20:18:39 | new JSONModel(oData) |
| webapp/view/app.view.xml:11:3:12:37 | content={/output1} | webapp/controller/app.controller.js:16:9:16:21 | output1: null |
#select
| webapp/controller/app.controller.js:32:30:32:42 | model.message | webapp/controller/app.controller.js:26:19:26:35 | oInput.getValue() | webapp/controller/app.controller.js:32:30:32:42 | model.message | XSS vulnerability due to $@. | webapp/controller/app.controller.js:26:19:26:35 | oInput.getValue() | user-provided value |
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
UI5Xss/UI5Xss.ql
Comment thread
jeongsoolee09 marked this conversation as resolved.
Loading
Loading