Skip to content
Open
Show file tree
Hide file tree
Changes from all 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.

32 changes: 31 additions & 1 deletion javascript/frameworks/ui5/ext/ui5.model.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ 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]"]
- ["Controller", "Controller", "Instance"]
- ["Controller", "sap/ui/core/mvc/Controller", ""]
- ["Component", "sap/ui/core/mvc/Component", ""]
- ["Component", "sap/ui/core/UIComponent", ""]
- ["Renderer", "Control", "Member[extend].Argument[1].Member[renderer]"]
- ["Renderer", "sap/ui/core/RenderManager", "Member[extend].Argument[1].Member[renderer]"]
- ["Renderer", "sap/ui/core/Renderer", "Member[extend].Argument[1]"]
- ["Renderer", "sap/ui/core/Renderer", "Member[extend].Argument[1]"] # ?
- ["RenderManager", "RenderManager", "Instance"]
- ["RenderManager", "sap/ui/core/RenderManager", ""]
- ["RenderManager", "Renderer", "Parameter[0]"]
Expand Down Expand Up @@ -71,6 +76,31 @@ 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,subscribeOnce]"]
- ["UI5EventSubscriptionHandlerDataParameter", "UI5EventBusSubscribe", "Argument[2].Parameter[2]"]
- ["SapUICoreEventBusInstance", "SapUICoreInstance", "Member[getEventBus].ReturnValue"]
- ["SapUICoreEventBusPublish", "SapUICoreEventBusInstance", "Member[publish]"]
- ["SapUICoreEventBusPublishedEventData", "SapUICoreEventBusPublish", "Argument[2]"]
- ["SapUICoreEventBusSubscribe", "SapUICoreEventBusInstance", "Member[subscribe,subscribeOnce]"]
- ["SapUICoreEventSubscriptionHandlerDataParameter", "SapUICoreEventBusSubscribe", "Argument[2].Parameter[2]"]
# Extend Calls
- ["CustomControl", "Control", "Member[extend]"]
- ["CustomController", "Controller", "Member[extend]"]
- ["CustomControllerContent", "Controller", "Member[extend].Argument[1]"]
- ["CustomControllerGetOwnerComponent", "CustomControllerContent", "Fuzzy.Member[getOwnerComponent].ReturnValue"]
- ["CustomControllerGetOwnerComponentEventBus", "CustomControllerGetOwnerComponent", "Member[getEventBus].ReturnValue"]
- ["CustomControllerGetOwnerComponentEventBusPublish", "CustomControllerGetOwnerComponentEventBus", "Member[publish]"]
- ["CustomControllerGetOwnerComponentEventBusPublishedData", "CustomControllerGetOwnerComponentEventBusPublish", "Argument[2]"]
- ["CustomControllerGetOwnerComponentEventBusSubscribe", "CustomControllerGetOwnerComponentEventBus", "Member[subscribe,subscribeOnce]"]
- ["CustomControllerGetOwnerComponentEventBusSubscriptionHandlerDataParameter", "CustomControllerGetOwnerComponentEventBusSubscribe", "Argument[2].Parameter[2]"]
- ["CustomComponent", "Component", "Member[extend]"]
- ["CustomRenderer", "Renderer", "Member[extend]"]
- ["ViewReference", "CustomController", "Member[getView].ReturnValue"]
- ["ControlReference", "ViewReference", "Member[byId].ReturnValue"]

- addsTo:
pack: codeql/javascript-all
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,47 @@ import advanced_security.javascript.frameworks.ui5.UI5View
import semmle.javascript.security.dataflow.XssThroughDomCustomizations
private import semmle.javascript.frameworks.data.internal.ApiGraphModelsExtensions

private class DataFromRemoteControlReference extends RemoteFlowSource {
DataFromRemoteControlReference() {
exists(UI5Control sourceControl, string typeAlias, ControlReference controlReference |
abstract private class RemoteControlAPISource extends SourceNode { }

private class RemoteControlReference extends RemoteControlAPISource, ControlReference {
RemoteControlReference() {
exists(UI5Control sourceControl, string typeAlias |
typeModel(typeAlias, sourceControl.getImportPath(), _) and
sourceModel(typeAlias, _, "remote", _) and
sourceControl.getAReference() = controlReference and
(
this = controlReference.getAMemberCall("getValue") or
this = controlReference.getAPropertyRead("value")
)
sourceControl.getAReference() = this
)
}
}

private class RemoteControlHandlerParameter extends RemoteControlAPISource, CallNode {
RemoteControlHandlerParameter() {
exists(UI5Control sourceControl, string typeAlias, UI5Handler handler |
typeModel(typeAlias, sourceControl.getImportPath(), _) and
sourceModel(typeAlias, _, "remote", _) and
handler.getControl() = sourceControl and
this = handler.getParameter(0).getAMemberCall("getSource")
)
}
}

private class UserDataFromRemoteControlAPISource extends RemoteFlowSource {
UserDataFromRemoteControlAPISource() {
exists(RemoteControlAPISource remoteControlAPISource |
/*
* 1. The `value` or its getter of `HTML` control reference, `CodeEditor` control reference,
* or handler parameters.
*/

this = remoteControlAPISource.getAPropertyRead("value") or
this = remoteControlAPISource.getAMemberCall("getValue") or
/* 2. The `getCurrentValue` method call on `CodeEditor` control reference. */
this = remoteControlAPISource.getAMemberCall("getCurrentValue")
)
}

override string getSourceType() { result = "Data from a remote control" }
override string getSourceType() {
result = "User-provided data fetched from an input control via JavaScript API"
}
}

private class InputControlInstantiation extends ElementInstantiation {
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 @@ -247,9 +257,8 @@ class Renderer extends SapExtendCall {

class CustomControl extends SapExtendCall {
CustomControl() {
this =
TypeTrackers::hasDependency(["sap/ui/core/Control", "sap.ui.core.Control"])
.getAMemberCall("extend") or
this = ModelOutput::getATypeNode("CustomControl").getACall()
or
exists(SapDefineModule sapModule | this.getDefine() = sapModule.getExtendingModule())
}

Expand Down Expand Up @@ -449,17 +458,18 @@ class ControllerReference extends Reference {
}

class CustomController extends SapExtendCall {
API::Node customController;
string name;

CustomController() {
this =
TypeTrackers::hasDependency(["sap/ui/core/mvc/Controller", "sap.ui.core.mvc.Controller"])
.getAMemberCall("extend") and
name = this.getFile().getBaseName().regexpCapture("([a-zA-Z0-9]+).[cC]ontroller.js", 1)
customController = ModelOutput::getATypeNode("CustomController") and
this = customController.getACall() and
name = this.getFile().getBaseName().regexpCapture("(.+).[cC]ontroller.js", 1)
}

Component getOwnerComponent() {
exists(ManifestJson manifestJson, JsonObject rootObj | manifestJson = result.getManifestJson() |
exists(ManifestJson manifestJson, JsonObject rootObj |
manifestJson = result.getManifestJson() and
rootObj
.getPropValue("targets")
.(JsonObject)
Expand All @@ -474,7 +484,12 @@ class CustomController extends SapExtendCall {
}

MethodCallNode getOwnerComponentRef() {
result = this.getAThisNode().getAMemberCall("getOwnerComponent")
exists(API::Node getOwnerComponent |
getOwnerComponent = ModelOutput::getATypeNode("CustomControllerGetOwnerComponent")
|
customController.getASuccessor+() = getOwnerComponent and
result = getOwnerComponent.asSource()
)
}

/**
Expand Down Expand Up @@ -757,14 +772,10 @@ class Component extends SapExtendCall {
* It is the value flowing to a `setModel` call in a handler of a `CustomController` (which is represented by `ControllerHandler`), since it is the closest we can get to the actual model itself.
*/

this =
TypeTrackers::hasDependency([
"sap/ui/core/mvc/Component", "sap.ui.core.mvc.Component", "sap/ui/core/UIComponent",
"sap.ui.core.UIComponent"
]).getAMemberCall("extend")
this = ModelOutput::getATypeNode("CustomComponent").getACall()
}

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,162 @@ 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 {
API::Node customController;

ComponentEventBusPublishCall() {
exists(API::Node customControllerGetOwnerComponentEventBusPublish |
customControllerGetOwnerComponentEventBusPublish =
ModelOutput::getATypeNode("CustomControllerGetOwnerComponentEventBusPublish")
|
customController = ModelOutput::getATypeNode("CustomController") and
customControllerGetOwnerComponentEventBusPublish = customController.getASuccessor+() and
this = customControllerGetOwnerComponentEventBusPublish.getACall()
)
}

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

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

SourceNode hasDependency(string dependencyPath) {
result = hasDependency(TypeTracker::end(), dependencyPath)
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()
)
}
}

class SapUICoreEventBusSubscribeCall extends EventBusSubscribeCall {
API::Node subscribeMethod;

SapUICoreEventBusSubscribeCall() {
subscribeMethod = ModelOutput::getATypeNode("SapUICoreEventBusSubscribe") 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 {
API::Node customController;

ComponentEventBusSubscribeCall() {
exists(API::Node customControllerGetOwnerComponentEventBusSubscribe |
customControllerGetOwnerComponentEventBusSubscribe =
ModelOutput::getATypeNode("CustomControllerGetOwnerComponentEventBusSubscribe")
|
customController = ModelOutput::getATypeNode("CustomController") and
customControllerGetOwnerComponentEventBusSubscribe = customController.getASuccessor+() and
this = customControllerGetOwnerComponentEventBusSubscribe.getACall()
)
}

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

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