Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 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]"]
- ["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
- ["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]"]
- ["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
@@ -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 @@ -334,16 +343,20 @@ class ControlReference extends Reference {
string controlId;

ControlReference() {
this.getArgument(0).getALocalSource().getStringValue() = controlId and
(
exists(CustomController controller |
this = controller.getAViewReference().getAMemberCall("byId") or
this = controller.getAThisNode().getAMemberCall("byId")
)
or
exists(SapUiCore sapUiCore | this = sapUiCore.getAMemberCall("byId"))
)
}
this.getArgument(0).getALocalSource().getStringValue() = controlId
/*
* and
* (
* exists(CustomController controller |
* this = controller.getAViewReference().getAMemberCall("byId") or
* this = controller.getAThisNode().getAMemberCall("byId")
* )
* or
* exists(SapUiCore sapUiCore | this = sapUiCore.getAMemberCall("byId"))
* )
*/

}

CustomControl getDefinition() {
exists(UI5Control controlDeclaration |
Expand Down Expand Up @@ -449,17 +462,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 +488,12 @@ class CustomController extends SapExtendCall {
}

MethodCallNode getOwnerComponentRef() {
Comment thread
knewbury01 marked this conversation as resolved.
result = this.getAThisNode().getAMemberCall("getOwnerComponent")
exists(API::Node getOwnerComponent |
getOwnerComponent = ModelOutput::getATypeNode("CustomControllerGetOwnerComponent")
|
customController.getASuccessor+() = getOwnerComponent and
result = getOwnerComponent.getACall()
)
}

/**
Expand Down Expand Up @@ -757,14 +776,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 +1443,176 @@ 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) }
}

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("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) }
}
}

private predicate test1(DataFlow::CallNode node) {
exists(API::Node customControllerGetOwnerComponentEventBusSubscribe, API::Node customController |
customControllerGetOwnerComponentEventBusSubscribe =
ModelOutput::getATypeNode("CustomControllerGetOwnerComponentEventBusSubscribe") and
customController = ModelOutput::getATypeNode("CustomController") and
customControllerGetOwnerComponentEventBusSubscribe = customController.getASuccessor+() and
node = customControllerGetOwnerComponentEventBusSubscribe.getACall()
)
}

private predicate test2(DataFlow::CallNode node) {
node = ModelOutput::getATypeNode("CustomControllerGetOwnerComponentEventBusSubscribe").getACall()
}
Original file line number Diff line number Diff line change
Expand Up @@ -920,15 +920,16 @@ class UI5Handler extends FunctionNode {
/**
* Models controller references in event handlers as types
*/
overlay[local?]
class ControlTypeInHandlerModel extends ModelInput::TypeModel {
Comment thread
jeongsoolee09 marked this conversation as resolved.
override DataFlow::CallNode getASource(string type) {
// oEvent.getSource() is of the type of the Control calling the handler
exists(UI5Handler h |
type = h.getControl().getImportPath() and
result.getCalleeName() = "getSource" and
result.getReceiver().getALocalSource() = h.getParameter(0)
)
or
// exists(UI5Handler h |
// type = h.getControl().getImportPath() and
// result.getCalleeName() = "getSource" and
// result.getReceiver().getALocalSource() = h.getParameter(0)
// )
// or
// this.getView().byId("id") is of the type of the Control with id="id"
exists(UI5Control c |
type = c.getImportPath() and
Expand Down
Loading
Loading