From 56bc60e834dfc3339df32debd14ff35b92bbbcab Mon Sep 17 00:00:00 2001 From: Kristen Newbury Date: Thu, 9 Oct 2025 11:50:04 -0400 Subject: [PATCH 01/23] Add test cases ui5/webcomponents-react explore --- .../ui5/src/Diagnostics/InvestigateReact.ql | 15 ++++++++ .../source-react/controlledcomponent.tsx | 38 +++++++++++++++++++ .../models/source-react/propscomponents.tsx | 20 ++++++++++ .../models/source-react/sourceTest.expected | 0 .../test/models/source-react/sourceTest.qlref | 1 + .../source-react/uncontrolledcomponent.tsx | 24 ++++++++++++ 6 files changed, 98 insertions(+) create mode 100644 javascript/frameworks/ui5/src/Diagnostics/InvestigateReact.ql create mode 100644 javascript/frameworks/ui5/test/models/source-react/controlledcomponent.tsx create mode 100644 javascript/frameworks/ui5/test/models/source-react/propscomponents.tsx create mode 100644 javascript/frameworks/ui5/test/models/source-react/sourceTest.expected create mode 100644 javascript/frameworks/ui5/test/models/source-react/sourceTest.qlref create mode 100644 javascript/frameworks/ui5/test/models/source-react/uncontrolledcomponent.tsx diff --git a/javascript/frameworks/ui5/src/Diagnostics/InvestigateReact.ql b/javascript/frameworks/ui5/src/Diagnostics/InvestigateReact.ql new file mode 100644 index 000000000..b586e0e22 --- /dev/null +++ b/javascript/frameworks/ui5/src/Diagnostics/InvestigateReact.ql @@ -0,0 +1,15 @@ +/** + * @name List properties of react modelling + * @description List properties of react modelling + * @ kind problem + * @problem.severity info + * @precision high + * @id js/ui5-investigate-react + * @tags diagnostics + */ + +import javascript +import semmle.javascript.frameworks.React + +from ViewComponentInput v +select v, v.getSourceType() diff --git a/javascript/frameworks/ui5/test/models/source-react/controlledcomponent.tsx b/javascript/frameworks/ui5/test/models/source-react/controlledcomponent.tsx new file mode 100644 index 000000000..1499e8357 --- /dev/null +++ b/javascript/frameworks/ui5/test/models/source-react/controlledcomponent.tsx @@ -0,0 +1,38 @@ +import { Input, Button } from '@ui5/webcomponents-react'; +import { useRef, useState } from 'react'; +import type { InputDomRef } from '@ui5/webcomponents-react'; + +function ControlledComponent( { props }) { + const inputRef1 = useRef < InputDomRef > (null); + const [inputRef2, setInputValue] = useState(''); + + const handleButtonPress1 = () => { + // Access the input value via the hook + console.log('Current input value:', inputRef1.current.value); // SOURCE + }; + + const handleButtonPress2 = event => { + setInputValue(event.target.value); // SOURCE + console.log('Current input value:', inputRef2); // SOURCE - only because of setInputValue + }; + + return ( +
+ + + + +
+ ); +} + diff --git a/javascript/frameworks/ui5/test/models/source-react/propscomponents.tsx b/javascript/frameworks/ui5/test/models/source-react/propscomponents.tsx new file mode 100644 index 000000000..e171047f3 --- /dev/null +++ b/javascript/frameworks/ui5/test/models/source-react/propscomponents.tsx @@ -0,0 +1,20 @@ +import { Input } from '@ui5/webcomponents-react'; + +// normal react component props +function ChildComponent({ value }) { // SOURCE + + console.log('Input finalized with value:', value); + + return ( +
+ +
+ ); +} + +function ParentComponent() { + const data = "Hello from Parent"; + return ; +} \ No newline at end of file diff --git a/javascript/frameworks/ui5/test/models/source-react/sourceTest.expected b/javascript/frameworks/ui5/test/models/source-react/sourceTest.expected new file mode 100644 index 000000000..e69de29bb diff --git a/javascript/frameworks/ui5/test/models/source-react/sourceTest.qlref b/javascript/frameworks/ui5/test/models/source-react/sourceTest.qlref new file mode 100644 index 000000000..cdf3e859a --- /dev/null +++ b/javascript/frameworks/ui5/test/models/source-react/sourceTest.qlref @@ -0,0 +1 @@ +Diagnostics/InvestigateReact.ql \ No newline at end of file diff --git a/javascript/frameworks/ui5/test/models/source-react/uncontrolledcomponent.tsx b/javascript/frameworks/ui5/test/models/source-react/uncontrolledcomponent.tsx new file mode 100644 index 000000000..199b659b0 --- /dev/null +++ b/javascript/frameworks/ui5/test/models/source-react/uncontrolledcomponent.tsx @@ -0,0 +1,24 @@ +import { Input, Button } from '@ui5/webcomponents-react'; +import type { InputDomRef } from '@ui5/webcomponents-react'; +import type { Ui5CustomEvent } from '@ui5/webcomponents-react-base'; + +function UncontrolledComponent({ props }) { + + //direct event value access, no hook/react specific function + const handleButtonPress = (event: Ui5CustomEvent) => { + const finalValue = event.target.value; // SOURCE + console.log('Input finalized with value:', finalValue); + }; + + return ( +
+ + +
+ ); +} + From 4c2351f746ec5f542df4a0275cef6fc15c44f291 Mon Sep 17 00:00:00 2001 From: Kristen Newbury Date: Mon, 20 Oct 2025 15:19:22 -0400 Subject: [PATCH 02/23] Add event target value source to test cases --- .../source-react/functionalcomponentsetstate.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 javascript/frameworks/ui5/test/models/source-react/functionalcomponentsetstate.tsx diff --git a/javascript/frameworks/ui5/test/models/source-react/functionalcomponentsetstate.tsx b/javascript/frameworks/ui5/test/models/source-react/functionalcomponentsetstate.tsx new file mode 100644 index 000000000..f7388a7ae --- /dev/null +++ b/javascript/frameworks/ui5/test/models/source-react/functionalcomponentsetstate.tsx @@ -0,0 +1,16 @@ +import React, { useState } from 'react'; + +function MyFunctionalComponent({ props }) { + const [count, setState] = useState({ count: 0 }); + + const handleClick = event => { + setState({ count: event.target.value + 1 }); // Directly update the state + console.log('Current input value:', count); + }; + + return ( +
+ +
+ ); +} \ No newline at end of file From 7a51865d6db9437934d0fd7bb6dc88a40a9f2296 Mon Sep 17 00:00:00 2001 From: Kristen Newbury Date: Mon, 20 Oct 2025 18:37:45 -0400 Subject: [PATCH 03/23] Fix test case to include previously missing handler --- .../ui5/test/models/source-react/uncontrolledcomponent.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/javascript/frameworks/ui5/test/models/source-react/uncontrolledcomponent.tsx b/javascript/frameworks/ui5/test/models/source-react/uncontrolledcomponent.tsx index 199b659b0..bfdd27917 100644 --- a/javascript/frameworks/ui5/test/models/source-react/uncontrolledcomponent.tsx +++ b/javascript/frameworks/ui5/test/models/source-react/uncontrolledcomponent.tsx @@ -5,7 +5,7 @@ import type { Ui5CustomEvent } from '@ui5/webcomponents-react-base'; function UncontrolledComponent({ props }) { //direct event value access, no hook/react specific function - const handleButtonPress = (event: Ui5CustomEvent) => { + const handleClick = (event: Ui5CustomEvent) => { const finalValue = event.target.value; // SOURCE console.log('Input finalized with value:', finalValue); }; @@ -14,6 +14,7 @@ function UncontrolledComponent({ props }) {
- - -
- ); -} - diff --git a/javascript/frameworks/ui5/test/models/source-react/functionalcomponentsetstate.tsx b/javascript/frameworks/ui5/test/models/source-react/functionalcomponentsetstate.tsx deleted file mode 100644 index 28abc3088..000000000 --- a/javascript/frameworks/ui5/test/models/source-react/functionalcomponentsetstate.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React, { useState } from 'react'; - -function MyFunctionalComponent({ props }) { - const [count, setState] = useState({ count: 0 }); - - const handleClick = event => { - setState({ count: event.target.value + 1 }); // SOURCE - detected as event.target.value - console.log('Current input value:', count); - }; - - return ( -
- -
- ); -} \ No newline at end of file diff --git a/javascript/frameworks/ui5/test/models/source-react/propscomponents.tsx b/javascript/frameworks/ui5/test/models/source-react/propscomponents.tsx deleted file mode 100644 index e171047f3..000000000 --- a/javascript/frameworks/ui5/test/models/source-react/propscomponents.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Input } from '@ui5/webcomponents-react'; - -// normal react component props -function ChildComponent({ value }) { // SOURCE - - console.log('Input finalized with value:', value); - - return ( -
- -
- ); -} - -function ParentComponent() { - const data = "Hello from Parent"; - return ; -} \ No newline at end of file diff --git a/javascript/frameworks/ui5/test/models/source-react/sourceTest.expected b/javascript/frameworks/ui5/test/models/source-react/sourceTest.expected deleted file mode 100644 index 4e6640604..000000000 --- a/javascript/frameworks/ui5/test/models/source-react/sourceTest.expected +++ /dev/null @@ -1,5 +0,0 @@ -| controlledcomponent.tsx:11:49:11:71 | inputRe ... t.value | | -| controlledcomponent.tsx:12:24:12:46 | inputRe ... t.value | | -| controlledcomponent.tsx:16:23:16:40 | event.target.value | | -| functionalcomponentsetstate.tsx:7:23:7:40 | event.target.value | | -| uncontrolledcomponent.tsx:9:24:9:41 | event.target.value | | diff --git a/javascript/frameworks/ui5/test/models/source-react/sourceTest.qlref b/javascript/frameworks/ui5/test/models/source-react/sourceTest.qlref deleted file mode 100644 index cdf3e859a..000000000 --- a/javascript/frameworks/ui5/test/models/source-react/sourceTest.qlref +++ /dev/null @@ -1 +0,0 @@ -Diagnostics/InvestigateReact.ql \ No newline at end of file diff --git a/javascript/frameworks/ui5/test/models/source-react/uncontrolledcomponent.tsx b/javascript/frameworks/ui5/test/models/source-react/uncontrolledcomponent.tsx deleted file mode 100644 index c518084c5..000000000 --- a/javascript/frameworks/ui5/test/models/source-react/uncontrolledcomponent.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Input, Button } from '@ui5/webcomponents-react'; -import type { InputDomRef } from '@ui5/webcomponents-react'; -import type { Ui5CustomEvent } from '@ui5/webcomponents-react-base'; - -function UncontrolledComponent({ props }) { - - //direct event value access, no hook/react specific function - const handleClick = (event: Ui5CustomEvent) => { - const finalValue = event.target.value; // SOURCE - detected - console.log('Input finalized with value:', finalValue); - }; - - return ( -
- - -
- ); -} - From 881e5f958c8e0658a3f9f80afda28c579cfdd43d Mon Sep 17 00:00:00 2001 From: Mauro Baluda Date: Thu, 27 Nov 2025 00:27:29 +0100 Subject: [PATCH 13/23] move qlpack.yml --- .../test/codeql-pack.lock.yml | 30 +++++++++++++++++++ .../qlpack.yml | 0 2 files changed, 30 insertions(+) create mode 100644 javascript/frameworks/ui5-webcomponents/test/codeql-pack.lock.yml rename javascript/frameworks/ui5-webcomponents/test/{queries/xss-input-dangerouslySetInnerHTML => }/qlpack.yml (100%) diff --git a/javascript/frameworks/ui5-webcomponents/test/codeql-pack.lock.yml b/javascript/frameworks/ui5-webcomponents/test/codeql-pack.lock.yml new file mode 100644 index 000000000..07ba5841a --- /dev/null +++ b/javascript/frameworks/ui5-webcomponents/test/codeql-pack.lock.yml @@ -0,0 +1,30 @@ +--- +lockVersion: 1.0.0 +dependencies: + codeql/concepts: + version: 0.0.9 + codeql/controlflow: + version: 2.0.19 + codeql/dataflow: + version: 2.0.19 + codeql/javascript-all: + version: 2.6.15 + codeql/mad: + version: 1.0.35 + codeql/regex: + version: 1.0.35 + codeql/ssa: + version: 2.0.11 + codeql/threat-models: + version: 1.0.35 + codeql/tutorial: + version: 1.0.35 + codeql/typetracking: + version: 2.0.19 + codeql/util: + version: 2.0.22 + codeql/xml: + version: 1.0.35 + codeql/yaml: + version: 1.0.35 +compiled: false diff --git a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/qlpack.yml b/javascript/frameworks/ui5-webcomponents/test/qlpack.yml similarity index 100% rename from javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/qlpack.yml rename to javascript/frameworks/ui5-webcomponents/test/qlpack.yml From ca1cb51224af3fabf235d72bcbcf09109d9d0a22 Mon Sep 17 00:00:00 2001 From: Mauro Baluda Date: Thu, 27 Nov 2025 01:16:14 +0100 Subject: [PATCH 14/23] Revert "move qlpack.yml" This reverts commit 881e5f958c8e0658a3f9f80afda28c579cfdd43d. --- .../test/codeql-pack.lock.yml | 30 ------------------- .../qlpack.yml | 0 2 files changed, 30 deletions(-) delete mode 100644 javascript/frameworks/ui5-webcomponents/test/codeql-pack.lock.yml rename javascript/frameworks/ui5-webcomponents/test/{ => queries/xss-input-dangerouslySetInnerHTML}/qlpack.yml (100%) diff --git a/javascript/frameworks/ui5-webcomponents/test/codeql-pack.lock.yml b/javascript/frameworks/ui5-webcomponents/test/codeql-pack.lock.yml deleted file mode 100644 index 07ba5841a..000000000 --- a/javascript/frameworks/ui5-webcomponents/test/codeql-pack.lock.yml +++ /dev/null @@ -1,30 +0,0 @@ ---- -lockVersion: 1.0.0 -dependencies: - codeql/concepts: - version: 0.0.9 - codeql/controlflow: - version: 2.0.19 - codeql/dataflow: - version: 2.0.19 - codeql/javascript-all: - version: 2.6.15 - codeql/mad: - version: 1.0.35 - codeql/regex: - version: 1.0.35 - codeql/ssa: - version: 2.0.11 - codeql/threat-models: - version: 1.0.35 - codeql/tutorial: - version: 1.0.35 - codeql/typetracking: - version: 2.0.19 - codeql/util: - version: 2.0.22 - codeql/xml: - version: 1.0.35 - codeql/yaml: - version: 1.0.35 -compiled: false diff --git a/javascript/frameworks/ui5-webcomponents/test/qlpack.yml b/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/qlpack.yml similarity index 100% rename from javascript/frameworks/ui5-webcomponents/test/qlpack.yml rename to javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/qlpack.yml From 72ef18e04642399b23b78a759dfb7d96443d3b8b Mon Sep 17 00:00:00 2001 From: Nathan Randall <70299490+data-douser@users.noreply.github.com> Date: Sun, 30 Nov 2025 08:22:16 -0700 Subject: [PATCH 15/23] Update javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.ql Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.ql b/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.ql index 5f2bb14c8..27ee5f875 100644 --- a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.ql +++ b/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.ql @@ -12,7 +12,7 @@ * external/cwe/cwe-116 */ -//a exact copy of - https://github.com/github/codeql/blob/main/javascript/ql/src/Security/CWE-079/XssThroughDom.ql +//an exact copy of - https://github.com/github/codeql/blob/main/javascript/ql/src/Security/CWE-079/XssThroughDom.ql //included for testing purposes only //tests the use of customizations to filter results via sanitizer import javascript From 618fb038e9ca27aa43d6b07009309fdde3de7bd4 Mon Sep 17 00:00:00 2001 From: Kristen Newbury Date: Mon, 1 Dec 2025 11:02:02 -0500 Subject: [PATCH 16/23] Fix test pack yml location --- .../xss-input-dangerouslySetInnerHTML => }/codeql-pack.lock.yml | 0 .../{queries/xss-input-dangerouslySetInnerHTML => }/qlpack.yml | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename javascript/frameworks/ui5-webcomponents/test/{queries/xss-input-dangerouslySetInnerHTML => }/codeql-pack.lock.yml (100%) rename javascript/frameworks/ui5-webcomponents/test/{queries/xss-input-dangerouslySetInnerHTML => }/qlpack.yml (100%) diff --git a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/codeql-pack.lock.yml b/javascript/frameworks/ui5-webcomponents/test/codeql-pack.lock.yml similarity index 100% rename from javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/codeql-pack.lock.yml rename to javascript/frameworks/ui5-webcomponents/test/codeql-pack.lock.yml diff --git a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/qlpack.yml b/javascript/frameworks/ui5-webcomponents/test/qlpack.yml similarity index 100% rename from javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/qlpack.yml rename to javascript/frameworks/ui5-webcomponents/test/qlpack.yml From 6781bb2e4578e60e17fc940bfa6725df9c8332fe Mon Sep 17 00:00:00 2001 From: Kristen Newbury Date: Mon, 1 Dec 2025 16:14:24 -0500 Subject: [PATCH 17/23] Improve documentation on app and query tests for webcomponents for react --- .../XssThroughDom.ql | 3 +- .../src/App.tsx | 100 +++++++++--------- 2 files changed, 52 insertions(+), 51 deletions(-) diff --git a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.ql b/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.ql index 27ee5f875..4796c966e 100644 --- a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.ql +++ b/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.ql @@ -2,7 +2,7 @@ * @name DOM text reinterpreted as HTML * @description Reinterpreting text from the DOM as HTML * can lead to a cross-site scripting vulnerability. - * @kind path-problem + * @ kind path-problem * @problem.severity warning * @security-severity 6.1 * @precision high @@ -13,6 +13,7 @@ */ //an exact copy of - https://github.com/github/codeql/blob/main/javascript/ql/src/Security/CWE-079/XssThroughDom.ql +//at commit sha: 7b6720c //included for testing purposes only //tests the use of customizations to filter results via sanitizer import javascript diff --git a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/src/App.tsx b/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/src/App.tsx index 991a53daf..c19e7a716 100644 --- a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/src/App.tsx +++ b/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/src/App.tsx @@ -8,7 +8,7 @@ function App() { const inputRef = useRef(null); const handleInputChange = useCallback(() => { - setInputValue((msg) => inputRef.current?.value || ""); + setInputValue((msg) => inputRef.current?.value || ""); {/* Potentially Unsafe */} }, [setInputValue]); useEffect(() => { @@ -24,7 +24,7 @@ function App() { const textAreaRef = useRef(null); const handleTextAreaChange = useCallback(() => { - setTextAreaValue((msg) => textAreaRef.current?.value || ""); + setTextAreaValue((msg) => textAreaRef.current?.value || ""); {/* Potentially Unsafe */} }, [setTextAreaValue]); useEffect(() => { @@ -40,7 +40,7 @@ function App() { const searchRef = useRef(null); const handleSearchChange = useCallback(() => { - setSearchValue((msg) => searchRef.current?.value || ""); + setSearchValue((msg) => searchRef.current?.value || ""); {/* Potentially Unsafe */} }, [setSearchValue]); useEffect(() => { @@ -56,7 +56,7 @@ function App() { const shellBarSearchRef = useRef(null); const handleShellBarSearchChange = useCallback(() => { - setShellBarSearchValue((msg) => shellBarSearchRef.current?.value || ""); + setShellBarSearchValue((msg) => shellBarSearchRef.current?.value || ""); {/* Potentially Unsafe */} }, [setShellBarSearchValue]); useEffect(() => { @@ -72,7 +72,7 @@ function App() { const comboBoxRef = useRef(null); const handleComboBoxChange = useCallback(() => { - setComboBoxValue((msg) => comboBoxRef.current?.value || ""); + setComboBoxValue((msg) => comboBoxRef.current?.value || ""); {/* Potentially Unsafe */} }, [setComboBoxValue]); useEffect(() => { @@ -88,7 +88,7 @@ function App() { const multiComboBoxRef = useRef(null); const handleMultiComboBoxChange = useCallback(() => { - setMultiComboBoxValue((msg) => multiComboBoxRef.current?.value || ""); + setMultiComboBoxValue((msg) => multiComboBoxRef.current?.value || ""); {/* Safe */} }, [setMultiComboBoxValue]); useEffect(() => { @@ -104,7 +104,7 @@ function App() { const selectRef = useRef(null); const handleSelectChange = useCallback(() => { - setSelectValue((msg) => selectRef.current?.value || ""); + setSelectValue((msg) => selectRef.current?.value || ""); {/* Safe */} }, [setSelectValue]); useEffect(() => { @@ -120,7 +120,7 @@ function App() { const datePickerRef = useRef(null); const handleDatePickerChange = useCallback(() => { - setDatePickerValue((msg) => datePickerRef.current?.value || ""); + setDatePickerValue((msg) => datePickerRef.current?.value || ""); {/* Potentially Unsafe */} }, [setDatePickerValue]); useEffect(() => { @@ -136,7 +136,7 @@ function App() { const dateRangePickerRef = useRef(null); const handleDateRangePickerChange = useCallback(() => { - setDateRangePickerValue((msg) => dateRangePickerRef.current?.value || ""); + setDateRangePickerValue((msg) => dateRangePickerRef.current?.value || ""); {/* Potentially Unsafe */} }, [setDateRangePickerValue]); useEffect(() => { @@ -152,7 +152,7 @@ function App() { const dateTimePickerRef = useRef(null); const handleDateTimePickerChange = useCallback(() => { - setDateTimePickerValue((msg) => dateTimePickerRef.current?.value || ""); + setDateTimePickerValue((msg) => dateTimePickerRef.current?.value || ""); {/* Potentially Unsafe */} }, [setDateTimePickerValue]); useEffect(() => { @@ -168,7 +168,7 @@ function App() { const timePickerRef = useRef(null); const handleTimePickerChange = useCallback(() => { - setTimePickerValue((msg) => timePickerRef.current?.value || ""); + setTimePickerValue((msg) => timePickerRef.current?.value || ""); {/* Potentially Unsafe */} }, [setTimePickerValue]); useEffect(() => { @@ -184,7 +184,7 @@ function App() { const colorPickerRef = useRef(null); const handleColorPickerChange = useCallback(() => { - setColorPickerValue((msg) => colorPickerRef.current?.value || ""); + setColorPickerValue((msg) => colorPickerRef.current?.value || ""); {/* Safe */} }, [setColorPickerValue]); useEffect(() => { @@ -200,7 +200,7 @@ function App() { const colorPaletteItemRef = useRef(null); const handleColorPaletteItemChange = useCallback(() => { - setColorPaletteItemValue((msg) => colorPaletteItemRef.current?.value || ""); + setColorPaletteItemValue((msg) => colorPaletteItemRef.current?.value || ""); {/* Safe */} }, [setColorPaletteItemValue]); useEffect(() => { @@ -216,7 +216,7 @@ function App() { const calendarDateRef = useRef(null); const handleCalendarDateChange = useCallback(() => { - setCalendarDateValue((msg) => calendarDateRef.current?.value || ""); + setCalendarDateValue((msg) => calendarDateRef.current?.value || ""); {/* Safe */} }, [setCalendarDateValue]); useEffect(() => { @@ -232,7 +232,7 @@ function App() { const fileUploaderRef = useRef(null); const handleFileUploaderChange = useCallback(() => { - setFileUploaderValue((msg) => fileUploaderRef.current?.value || ""); + setFileUploaderValue((msg) => fileUploaderRef.current?.value || ""); {/* Safe */} }, [setFileUploaderValue]); useEffect(() => { @@ -248,7 +248,7 @@ function App() { const checkBoxRef = useRef(null); const handleCheckBoxChange = useCallback(() => { - setCheckBoxValue((msg) => checkBoxRef.current?.value || ""); + setCheckBoxValue((msg) => checkBoxRef.current?.value || ""); {/* Safe */} }, [setCheckBoxValue]); useEffect(() => { @@ -264,7 +264,7 @@ function App() { const radioButtonRef = useRef(null); const handleRadioButtonChange = useCallback(() => { - setRadioButtonValue((msg) => radioButtonRef.current?.value || ""); + setRadioButtonValue((msg) => radioButtonRef.current?.value || ""); {/* Safe */} }, [setRadioButtonValue]); useEffect(() => { @@ -280,7 +280,7 @@ function App() { const switchRef = useRef(null); const handleSwitchChange = useCallback(() => { - setSwitchValue((msg) => switchRef.current?.value || ""); + setSwitchValue((msg) => switchRef.current?.value || ""); {/* Safe */} }, [setSwitchValue]); useEffect(() => { @@ -296,7 +296,7 @@ function App() { const optionRef = useRef(null); const handleOptionChange = useCallback(() => { - setOptionValue((msg) => optionRef.current?.value || ""); + setOptionValue((msg) => optionRef.current?.value || ""); {/* Potentially Unsafe */} }, [setOptionValue]); useEffect(() => { @@ -312,7 +312,7 @@ function App() { const optionCustomRef = useRef(null); const handleOptionCustomChange = useCallback(() => { - setOptionCustomValue((msg) => optionCustomRef.current?.value || ""); + setOptionCustomValue((msg) => optionCustomRef.current?.value || ""); {/* Potentially Unsafe */} }, [setOptionCustomValue]); useEffect(() => { @@ -328,7 +328,7 @@ function App() { const ratingIndicatorRef = useRef(null); const handleRatingIndicatorChange = useCallback(() => { - setRatingIndicatorValue((msg) => ratingIndicatorRef.current?.value || ""); + setRatingIndicatorValue((msg) => ratingIndicatorRef.current?.value || ""); {/* Safe - numeric */} }, [setRatingIndicatorValue]); useEffect(() => { @@ -344,7 +344,7 @@ function App() { const sliderRef = useRef(null); const handleSliderChange = useCallback(() => { - setSliderValue((msg) => sliderRef.current?.value || ""); + setSliderValue((msg) => sliderRef.current?.value || ""); {/* Safe - numeric */} }, [setSliderValue]); useEffect(() => { @@ -360,7 +360,7 @@ function App() { const progressIndicatorRef = useRef(null); const handleProgressIndicatorChange = useCallback(() => { - setProgressIndicatorValue((msg) => progressIndicatorRef.current?.value || ""); + setProgressIndicatorValue((msg) => progressIndicatorRef.current?.value || ""); {/* Safe - numeric */} }, [setProgressIndicatorValue]); useEffect(() => { @@ -376,7 +376,7 @@ function App() { const stepInputRef = useRef(null); const handleStepInputChange = useCallback(() => { - setStepInputValue((msg) => stepInputRef.current?.value || ""); + setStepInputValue((msg) => stepInputRef.current?.value || ""); {/* Safe - numeric */} }, [setStepInputValue]); useEffect(() => { @@ -392,7 +392,7 @@ function App() { const dynamicDateRangeRef = useRef(null); const handleDynamicDateRangeChange = useCallback(() => { - setDynamicDateRangeValue((msg) => dynamicDateRangeRef.current?.value || ""); + setDynamicDateRangeValue((msg) => dynamicDateRangeRef.current?.value || ""); {/* Safe - numeric */} }, [setDynamicDateRangeValue]); useEffect(() => { @@ -406,31 +406,31 @@ function App() { return (
- - - - - - {/* FP */} - {/* FP */} - - - - - {/* FP */} - {/* FP */} - {/* FP - not a standalone component */} - {/* FP */} - {/* FP */} - {/* FP */} - {/* FP */} - - - {/* FP - numeric */} - {/* FP - numeric */} - {/* FP - numeric */} - {/* FP - numeric */} - {/* FP - numeric */} + {/* Potentially Unsafe */} + {/* Potentially Unsafe */} + {/* Potentially Unsafe */} + {/* Potentially Unsafe */} + {/* Potentially Unsafe */} + {/* Safe */} + {/* Safe */} + {/* Potentially Unsafe */} + {/* Potentially Unsafe */} + {/* Potentially Unsafe */} + {/* Potentially Unsafe */} + {/* Safe */} + {/* Safe */} + {/* Safe - not a standalone component */} + {/* Safe */} + {/* Safe */} + {/* Safe */} + {/* Safe */} + {/* Potentially Unsafe */} + {/* Potentially Unsafe */} + {/* Safe - numeric */} + {/* Safe - numeric */} + {/* Safe - numeric */} + {/* Safe - numeric */} + {/* Safe - numeric */}
From a9b572a371d9520041dd2ed7f7088a21024fb0b7 Mon Sep 17 00:00:00 2001 From: Kristen Newbury Date: Mon, 1 Dec 2025 16:45:29 -0500 Subject: [PATCH 18/23] Add module level documentation to ui5/Sanitizers.qll --- .../javascript/frameworks/ui5/Sanitizers.qll | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/Sanitizers.qll b/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/Sanitizers.qll index 3963926d1..3e058e44e 100644 --- a/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/Sanitizers.qll +++ b/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/Sanitizers.qll @@ -1,3 +1,9 @@ +/** + * A module to describe santizers that should be applied to out of the box queries. + * To include various frameworks and concepts as need be. + * Extension points will depend very much on which query is the intended affected one. + */ + import advanced_security.javascript.frameworks.ui5.UI5WebcomponentsReact /** @@ -11,7 +17,7 @@ class ExcludedSource extends DomBasedXss::Sanitizer { source.getElement().getName() in [ "MultiComboBox", "Select", "ColorPicker", "ColorPaletteItem", "CalendarDate", "FileUploader", "CheckBox", "RadioButton", "Switch", "RatingIndicator", "Slider", - "ProgressIndicator", "StepInput", "DynamicDateRange" + "ProgressIndicator", "StepInput", "DynamicDateRange", "RangeSlider" ] and this.(DataFlow::PropRead).getBase() = source ) From e2d66e4f728b9c0f3e19e957c072b6813e349ac6 Mon Sep 17 00:00:00 2001 From: Kristen Newbury Date: Mon, 1 Dec 2025 17:01:23 -0500 Subject: [PATCH 19/23] Add missing components to sanitize for ui5-webcomponents-react --- .../XssThroughDom.expected | 66 +++++------ .../src/App.tsx | 110 +++++++++++++++++- .../javascript/frameworks/ui5/Sanitizers.qll | 3 +- 3 files changed, 144 insertions(+), 35 deletions(-) diff --git a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.expected b/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.expected index de87e9ef4..b77351291 100644 --- a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.expected +++ b/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.expected @@ -1,46 +1,46 @@ edges | src/App.tsx:7:10:7:19 | inputValue | src/App.tsx:7:10:7:19 | inputValue | provenance | | -| src/App.tsx:7:10:7:19 | inputValue | src/App.tsx:435:46:435:55 | inputValue | provenance | | +| src/App.tsx:7:10:7:19 | inputValue | src/App.tsx:537:46:537:55 | inputValue | provenance | | | src/App.tsx:11:28:11:50 | inputRe ... ?.value | src/App.tsx:11:28:11:56 | inputRe ... e \|\| "" | provenance | | | src/App.tsx:11:28:11:56 | inputRe ... e \|\| "" | src/App.tsx:7:10:7:19 | inputValue | provenance | | | src/App.tsx:23:10:23:22 | textAreaValue | src/App.tsx:23:10:23:22 | textAreaValue | provenance | | -| src/App.tsx:23:10:23:22 | textAreaValue | src/App.tsx:436:46:436:58 | textAreaValue | provenance | | +| src/App.tsx:23:10:23:22 | textAreaValue | src/App.tsx:538:46:538:58 | textAreaValue | provenance | | | src/App.tsx:27:31:27:56 | textAre ... ?.value | src/App.tsx:27:31:27:62 | textAre ... e \|\| "" | provenance | | | src/App.tsx:27:31:27:62 | textAre ... e \|\| "" | src/App.tsx:23:10:23:22 | textAreaValue | provenance | | | src/App.tsx:39:10:39:20 | searchValue | src/App.tsx:39:10:39:20 | searchValue | provenance | | -| src/App.tsx:39:10:39:20 | searchValue | src/App.tsx:437:46:437:56 | searchValue | provenance | | +| src/App.tsx:39:10:39:20 | searchValue | src/App.tsx:539:46:539:56 | searchValue | provenance | | | src/App.tsx:43:29:43:52 | searchR ... ?.value | src/App.tsx:43:29:43:58 | searchR ... e \|\| "" | provenance | | | src/App.tsx:43:29:43:58 | searchR ... e \|\| "" | src/App.tsx:39:10:39:20 | searchValue | provenance | | | src/App.tsx:55:10:55:28 | shellBarSearchValue | src/App.tsx:55:10:55:28 | shellBarSearchValue | provenance | | -| src/App.tsx:55:10:55:28 | shellBarSearchValue | src/App.tsx:438:46:438:64 | shellBarSearchValue | provenance | | +| src/App.tsx:55:10:55:28 | shellBarSearchValue | src/App.tsx:540:46:540:64 | shellBarSearchValue | provenance | | | src/App.tsx:59:37:59:68 | shellBa ... ?.value | src/App.tsx:59:37:59:74 | shellBa ... e \|\| "" | provenance | | | src/App.tsx:59:37:59:74 | shellBa ... e \|\| "" | src/App.tsx:55:10:55:28 | shellBarSearchValue | provenance | | | src/App.tsx:71:10:71:22 | comboBoxValue | src/App.tsx:71:10:71:22 | comboBoxValue | provenance | | -| src/App.tsx:71:10:71:22 | comboBoxValue | src/App.tsx:439:46:439:58 | comboBoxValue | provenance | | +| src/App.tsx:71:10:71:22 | comboBoxValue | src/App.tsx:541:46:541:58 | comboBoxValue | provenance | | | src/App.tsx:75:31:75:56 | comboBo ... ?.value | src/App.tsx:75:31:75:62 | comboBo ... e \|\| "" | provenance | | | src/App.tsx:75:31:75:62 | comboBo ... e \|\| "" | src/App.tsx:71:10:71:22 | comboBoxValue | provenance | | | src/App.tsx:119:10:119:24 | datePickerValue | src/App.tsx:119:10:119:24 | datePickerValue | provenance | | -| src/App.tsx:119:10:119:24 | datePickerValue | src/App.tsx:442:46:442:60 | datePickerValue | provenance | | +| src/App.tsx:119:10:119:24 | datePickerValue | src/App.tsx:544:46:544:60 | datePickerValue | provenance | | | src/App.tsx:123:33:123:60 | datePic ... ?.value | src/App.tsx:123:33:123:66 | datePic ... e \|\| "" | provenance | | | src/App.tsx:123:33:123:66 | datePic ... e \|\| "" | src/App.tsx:119:10:119:24 | datePickerValue | provenance | | | src/App.tsx:135:10:135:29 | dateRangePickerValue | src/App.tsx:135:10:135:29 | dateRangePickerValue | provenance | | -| src/App.tsx:135:10:135:29 | dateRangePickerValue | src/App.tsx:443:46:443:65 | dateRangePickerValue | provenance | | +| src/App.tsx:135:10:135:29 | dateRangePickerValue | src/App.tsx:545:46:545:65 | dateRangePickerValue | provenance | | | src/App.tsx:139:38:139:70 | dateRan ... ?.value | src/App.tsx:139:38:139:76 | dateRan ... e \|\| "" | provenance | | | src/App.tsx:139:38:139:76 | dateRan ... e \|\| "" | src/App.tsx:135:10:135:29 | dateRangePickerValue | provenance | | | src/App.tsx:151:10:151:28 | dateTimePickerValue | src/App.tsx:151:10:151:28 | dateTimePickerValue | provenance | | -| src/App.tsx:151:10:151:28 | dateTimePickerValue | src/App.tsx:444:46:444:64 | dateTimePickerValue | provenance | | +| src/App.tsx:151:10:151:28 | dateTimePickerValue | src/App.tsx:546:46:546:64 | dateTimePickerValue | provenance | | | src/App.tsx:155:37:155:68 | dateTim ... ?.value | src/App.tsx:155:37:155:74 | dateTim ... e \|\| "" | provenance | | | src/App.tsx:155:37:155:74 | dateTim ... e \|\| "" | src/App.tsx:151:10:151:28 | dateTimePickerValue | provenance | | | src/App.tsx:167:10:167:24 | timePickerValue | src/App.tsx:167:10:167:24 | timePickerValue | provenance | | -| src/App.tsx:167:10:167:24 | timePickerValue | src/App.tsx:445:46:445:60 | timePickerValue | provenance | | +| src/App.tsx:167:10:167:24 | timePickerValue | src/App.tsx:547:46:547:60 | timePickerValue | provenance | | | src/App.tsx:171:33:171:60 | timePic ... ?.value | src/App.tsx:171:33:171:66 | timePic ... e \|\| "" | provenance | | | src/App.tsx:171:33:171:66 | timePic ... e \|\| "" | src/App.tsx:167:10:167:24 | timePickerValue | provenance | | | src/App.tsx:295:10:295:20 | optionValue | src/App.tsx:295:10:295:20 | optionValue | provenance | | -| src/App.tsx:295:10:295:20 | optionValue | src/App.tsx:453:46:453:56 | optionValue | provenance | | +| src/App.tsx:295:10:295:20 | optionValue | src/App.tsx:555:46:555:56 | optionValue | provenance | | | src/App.tsx:299:29:299:52 | optionR ... ?.value | src/App.tsx:299:29:299:58 | optionR ... e \|\| "" | provenance | | | src/App.tsx:299:29:299:58 | optionR ... e \|\| "" | src/App.tsx:295:10:295:20 | optionValue | provenance | | | src/App.tsx:311:10:311:26 | optionCustomValue | src/App.tsx:311:10:311:26 | optionCustomValue | provenance | | -| src/App.tsx:311:10:311:26 | optionCustomValue | src/App.tsx:454:46:454:62 | optionCustomValue | provenance | | +| src/App.tsx:311:10:311:26 | optionCustomValue | src/App.tsx:556:46:556:62 | optionCustomValue | provenance | | | src/App.tsx:315:35:315:64 | optionC ... ?.value | src/App.tsx:315:35:315:70 | optionC ... e \|\| "" | provenance | | | src/App.tsx:315:35:315:70 | optionC ... e \|\| "" | src/App.tsx:311:10:311:26 | optionCustomValue | provenance | | nodes @@ -88,27 +88,27 @@ nodes | src/App.tsx:311:10:311:26 | optionCustomValue | semmle.label | optionCustomValue | | src/App.tsx:315:35:315:64 | optionC ... ?.value | semmle.label | optionC ... ?.value | | src/App.tsx:315:35:315:70 | optionC ... e \|\| "" | semmle.label | optionC ... e \|\| "" | -| src/App.tsx:435:46:435:55 | inputValue | semmle.label | inputValue | -| src/App.tsx:436:46:436:58 | textAreaValue | semmle.label | textAreaValue | -| src/App.tsx:437:46:437:56 | searchValue | semmle.label | searchValue | -| src/App.tsx:438:46:438:64 | shellBarSearchValue | semmle.label | shellBarSearchValue | -| src/App.tsx:439:46:439:58 | comboBoxValue | semmle.label | comboBoxValue | -| src/App.tsx:442:46:442:60 | datePickerValue | semmle.label | datePickerValue | -| src/App.tsx:443:46:443:65 | dateRangePickerValue | semmle.label | dateRangePickerValue | -| src/App.tsx:444:46:444:64 | dateTimePickerValue | semmle.label | dateTimePickerValue | -| src/App.tsx:445:46:445:60 | timePickerValue | semmle.label | timePickerValue | -| src/App.tsx:453:46:453:56 | optionValue | semmle.label | optionValue | -| src/App.tsx:454:46:454:62 | optionCustomValue | semmle.label | optionCustomValue | +| src/App.tsx:537:46:537:55 | inputValue | semmle.label | inputValue | +| src/App.tsx:538:46:538:58 | textAreaValue | semmle.label | textAreaValue | +| src/App.tsx:539:46:539:56 | searchValue | semmle.label | searchValue | +| src/App.tsx:540:46:540:64 | shellBarSearchValue | semmle.label | shellBarSearchValue | +| src/App.tsx:541:46:541:58 | comboBoxValue | semmle.label | comboBoxValue | +| src/App.tsx:544:46:544:60 | datePickerValue | semmle.label | datePickerValue | +| src/App.tsx:545:46:545:65 | dateRangePickerValue | semmle.label | dateRangePickerValue | +| src/App.tsx:546:46:546:64 | dateTimePickerValue | semmle.label | dateTimePickerValue | +| src/App.tsx:547:46:547:60 | timePickerValue | semmle.label | timePickerValue | +| src/App.tsx:555:46:555:56 | optionValue | semmle.label | optionValue | +| src/App.tsx:556:46:556:62 | optionCustomValue | semmle.label | optionCustomValue | subpaths #select -| src/App.tsx:435:46:435:55 | inputValue | src/App.tsx:11:28:11:50 | inputRe ... ?.value | src/App.tsx:435:46:435:55 | inputValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:11:28:11:50 | inputRe ... ?.value | DOM text | -| src/App.tsx:436:46:436:58 | textAreaValue | src/App.tsx:27:31:27:56 | textAre ... ?.value | src/App.tsx:436:46:436:58 | textAreaValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:27:31:27:56 | textAre ... ?.value | DOM text | -| src/App.tsx:437:46:437:56 | searchValue | src/App.tsx:43:29:43:52 | searchR ... ?.value | src/App.tsx:437:46:437:56 | searchValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:43:29:43:52 | searchR ... ?.value | DOM text | -| src/App.tsx:438:46:438:64 | shellBarSearchValue | src/App.tsx:59:37:59:68 | shellBa ... ?.value | src/App.tsx:438:46:438:64 | shellBarSearchValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:59:37:59:68 | shellBa ... ?.value | DOM text | -| src/App.tsx:439:46:439:58 | comboBoxValue | src/App.tsx:75:31:75:56 | comboBo ... ?.value | src/App.tsx:439:46:439:58 | comboBoxValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:75:31:75:56 | comboBo ... ?.value | DOM text | -| src/App.tsx:442:46:442:60 | datePickerValue | src/App.tsx:123:33:123:60 | datePic ... ?.value | src/App.tsx:442:46:442:60 | datePickerValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:123:33:123:60 | datePic ... ?.value | DOM text | -| src/App.tsx:443:46:443:65 | dateRangePickerValue | src/App.tsx:139:38:139:70 | dateRan ... ?.value | src/App.tsx:443:46:443:65 | dateRangePickerValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:139:38:139:70 | dateRan ... ?.value | DOM text | -| src/App.tsx:444:46:444:64 | dateTimePickerValue | src/App.tsx:155:37:155:68 | dateTim ... ?.value | src/App.tsx:444:46:444:64 | dateTimePickerValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:155:37:155:68 | dateTim ... ?.value | DOM text | -| src/App.tsx:445:46:445:60 | timePickerValue | src/App.tsx:171:33:171:60 | timePic ... ?.value | src/App.tsx:445:46:445:60 | timePickerValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:171:33:171:60 | timePic ... ?.value | DOM text | -| src/App.tsx:453:46:453:56 | optionValue | src/App.tsx:299:29:299:52 | optionR ... ?.value | src/App.tsx:453:46:453:56 | optionValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:299:29:299:52 | optionR ... ?.value | DOM text | -| src/App.tsx:454:46:454:62 | optionCustomValue | src/App.tsx:315:35:315:64 | optionC ... ?.value | src/App.tsx:454:46:454:62 | optionCustomValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:315:35:315:64 | optionC ... ?.value | DOM text | +| src/App.tsx:537:46:537:55 | inputValue | src/App.tsx:11:28:11:50 | inputRe ... ?.value | src/App.tsx:537:46:537:55 | inputValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:11:28:11:50 | inputRe ... ?.value | DOM text | +| src/App.tsx:538:46:538:58 | textAreaValue | src/App.tsx:27:31:27:56 | textAre ... ?.value | src/App.tsx:538:46:538:58 | textAreaValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:27:31:27:56 | textAre ... ?.value | DOM text | +| src/App.tsx:539:46:539:56 | searchValue | src/App.tsx:43:29:43:52 | searchR ... ?.value | src/App.tsx:539:46:539:56 | searchValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:43:29:43:52 | searchR ... ?.value | DOM text | +| src/App.tsx:540:46:540:64 | shellBarSearchValue | src/App.tsx:59:37:59:68 | shellBa ... ?.value | src/App.tsx:540:46:540:64 | shellBarSearchValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:59:37:59:68 | shellBa ... ?.value | DOM text | +| src/App.tsx:541:46:541:58 | comboBoxValue | src/App.tsx:75:31:75:56 | comboBo ... ?.value | src/App.tsx:541:46:541:58 | comboBoxValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:75:31:75:56 | comboBo ... ?.value | DOM text | +| src/App.tsx:544:46:544:60 | datePickerValue | src/App.tsx:123:33:123:60 | datePic ... ?.value | src/App.tsx:544:46:544:60 | datePickerValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:123:33:123:60 | datePic ... ?.value | DOM text | +| src/App.tsx:545:46:545:65 | dateRangePickerValue | src/App.tsx:139:38:139:70 | dateRan ... ?.value | src/App.tsx:545:46:545:65 | dateRangePickerValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:139:38:139:70 | dateRan ... ?.value | DOM text | +| src/App.tsx:546:46:546:64 | dateTimePickerValue | src/App.tsx:155:37:155:68 | dateTim ... ?.value | src/App.tsx:546:46:546:64 | dateTimePickerValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:155:37:155:68 | dateTim ... ?.value | DOM text | +| src/App.tsx:547:46:547:60 | timePickerValue | src/App.tsx:171:33:171:60 | timePic ... ?.value | src/App.tsx:547:46:547:60 | timePickerValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:171:33:171:60 | timePic ... ?.value | DOM text | +| src/App.tsx:555:46:555:56 | optionValue | src/App.tsx:299:29:299:52 | optionR ... ?.value | src/App.tsx:555:46:555:56 | optionValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:299:29:299:52 | optionR ... ?.value | DOM text | +| src/App.tsx:556:46:556:62 | optionCustomValue | src/App.tsx:315:35:315:64 | optionC ... ?.value | src/App.tsx:556:46:556:62 | optionCustomValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:315:35:315:64 | optionC ... ?.value | DOM text | diff --git a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/src/App.tsx b/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/src/App.tsx index c19e7a716..79f943d4d 100644 --- a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/src/App.tsx +++ b/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/src/App.tsx @@ -1,5 +1,5 @@ import { useState, useRef, useEffect, useCallback } from 'react'; -import {Input, TextArea, Search, ShellBarSearch, ComboBox, MultiComboBox, Select, DatePicker, DateRangePicker, DateTimePicker, TimePicker, ColorPicker, ColorPaletteItem, CalendarDate, FileUploader, CheckBox, RadioButton, Switch, Option, OptionCustom, RatingIndicator, Slider, ProgressIndicator, StepInput, DynamicDateRange } from "@ui5/webcomponents-react"; +import {Input, TextArea, Search, ShellBarSearch, ComboBox, MultiComboBox, Select, DatePicker, DateRangePicker, DateTimePicker, TimePicker, ColorPicker, ColorPaletteItem, CalendarDate, FileUploader, CheckBox, RadioButton, Switch, Option, OptionCustom, RatingIndicator, Slider, ProgressIndicator, StepInput, DynamicDateRange, RangeSlider, Button, MessageViewButton, SegmentedButton, SplitButton, ToggleButton } from "@ui5/webcomponents-react"; import '@ui5/webcomponents/dist/Assets.js'; function App() { @@ -403,6 +403,102 @@ function App() { }; }, [handleDynamicDateRangeChange]); + //RangeSlider component usage + const [rangeSliderValue, setRangeSliderValue] = useState(""); + const rangeSliderRef = useRef(null); + + const handleRangeSliderChange = useCallback(() => { + setRangeSliderValue((msg) => rangeSliderRef.current?.value || ""); {/* Safe - numeric */} + }, [setRangeSliderValue]); + + useEffect(() => { + const currentRangeSlider = rangeSliderRef.current; + currentRangeSlider?.addEventListener("change", handleRangeSliderChange); + return () => { + currentRangeSlider?.removeEventListener("change", handleRangeSliderChange); + }; + }, [handleRangeSliderChange]); + + // Button component usage + const [buttonValue, setButtonValue] = useState(""); + const buttonRef = useRef(null); + + const handleButtonChange = useCallback(() => { + setButtonValue((msg) => buttonRef.current?.value || ""); {/* Safe */} + }, [setButtonValue]); + + useEffect(() => { + const currentButton = buttonRef.current; + currentButton?.addEventListener("change", handleButtonChange); + return () => { + currentButton?.removeEventListener("change", handleButtonChange); + }; + }, [handleButtonChange]); + + // MessageViewButton component usage + const [messageViewButtonValue, setMessageViewButtonValue] = useState(""); + const messageViewButtonRef = useRef(null); + + const handleMessageViewButtonChange = useCallback(() => { + setMessageViewButtonValue((msg) => messageViewButtonRef.current?.value || ""); {/* Safe */} + }, [setMessageViewButtonValue]); + + useEffect(() => { + const currentMessageViewButton = messageViewButtonRef.current; + currentMessageViewButton?.addEventListener("change", handleMessageViewButtonChange); + return () => { + currentMessageViewButton?.removeEventListener("change", handleMessageViewButtonChange); + }; + }, [handleMessageViewButtonChange]); + + // SegmentedButton component usage + const [segmentedButtonValue, setSegmentedButtonValue] = useState(""); + const segmentedButtonRef = useRef(null); + + const handleSegmentedButtonChange = useCallback(() => { + setSegmentedButtonValue((msg) => segmentedButtonRef.current?.value || ""); {/* Safe */} + }, [setSegmentedButtonValue]); + + useEffect(() => { + const currentSegmentedButton = segmentedButtonRef.current; + currentSegmentedButton?.addEventListener("change", handleSegmentedButtonChange); + return () => { + currentSegmentedButton?.removeEventListener("change", handleSegmentedButtonChange); + }; + }, [handleSegmentedButtonChange]); + + // SplitButton component usage + const [splitButtonValue, setSplitButtonValue] = useState(""); + const splitButtonRef = useRef(null); + + const handleSplitButtonChange = useCallback(() => { + setSplitButtonValue((msg) => splitButtonRef.current?.value || ""); {/* Safe */} + }, [setSplitButtonValue]); + + useEffect(() => { + const currentSplitButton = splitButtonRef.current; + currentSplitButton?.addEventListener("change", handleSplitButtonChange); + return () => { + currentSplitButton?.removeEventListener("change", handleSplitButtonChange); + }; + }, [handleSplitButtonChange]); + + // ToggleButton component usage + const [toggleButtonValue, setToggleButtonValue] = useState(""); + const toggleButtonRef = useRef(null); + + const handleToggleButtonChange = useCallback(() => { + setToggleButtonValue((msg) => toggleButtonRef.current?.value || ""); {/* Safe */} + }, [setToggleButtonValue]); + + useEffect(() => { + const currentToggleButton = toggleButtonRef.current; + currentToggleButton?.addEventListener("change", handleToggleButtonChange); + return () => { + currentToggleButton?.removeEventListener("change", handleToggleButtonChange); + }; + }, [handleToggleButtonChange]); + return (
@@ -431,6 +527,12 @@ function App() { {/* Safe - numeric */} {/* Safe - numeric */} {/* Safe - numeric */} + {/* Safe - numeric */} + {/* Safe */} + {/* Safe */} + {/* Safe */} + Split {/* Safe */} + Toggle {/* Safe */}
@@ -457,6 +559,12 @@ function App() {
+
+
+
+
+
+
); } diff --git a/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/Sanitizers.qll b/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/Sanitizers.qll index 3e058e44e..9c1e070e0 100644 --- a/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/Sanitizers.qll +++ b/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/Sanitizers.qll @@ -17,7 +17,8 @@ class ExcludedSource extends DomBasedXss::Sanitizer { source.getElement().getName() in [ "MultiComboBox", "Select", "ColorPicker", "ColorPaletteItem", "CalendarDate", "FileUploader", "CheckBox", "RadioButton", "Switch", "RatingIndicator", "Slider", - "ProgressIndicator", "StepInput", "DynamicDateRange", "RangeSlider" + "ProgressIndicator", "StepInput", "DynamicDateRange", "RangeSlider", "Button", + "MessageViewButton", "SegmentedButton", "SplitButton", "ToggleButton" ] and this.(DataFlow::PropRead).getBase() = source ) From a24d844df5c8763ae701ef7719890445ef842261 Mon Sep 17 00:00:00 2001 From: Kristen Newbury Date: Wed, 3 Dec 2025 12:26:29 -0500 Subject: [PATCH 20/23] Address review comments --- .../XssThroughDom.ql | 12 +- .../src/App.tsx | 152 +++++++++--------- .../frameworks/ui5/UI5WebcomponentsReact.qll | 18 +-- 3 files changed, 90 insertions(+), 92 deletions(-) diff --git a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.ql b/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.ql index 4796c966e..0c14b7018 100644 --- a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.ql +++ b/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.ql @@ -2,7 +2,7 @@ * @name DOM text reinterpreted as HTML * @description Reinterpreting text from the DOM as HTML * can lead to a cross-site scripting vulnerability. - * @ kind path-problem + * @kind path-problem * @problem.severity warning * @security-severity 6.1 * @precision high @@ -12,10 +12,12 @@ * external/cwe/cwe-116 */ -//an exact copy of - https://github.com/github/codeql/blob/main/javascript/ql/src/Security/CWE-079/XssThroughDom.ql -//at commit sha: 7b6720c -//included for testing purposes only -//tests the use of customizations to filter results via sanitizer +/* + * This file is an exact copy of - https://github.com/github/codeql/blob/main/javascript/ql/src/Security/CWE-079/XssThroughDom.ql + * replicated at commit sha: 7b6720c , included for testing purposes only. + * Its purpose is to test the use of customizations to filter results via the sanitizers. + */ + import javascript import semmle.javascript.security.dataflow.XssThroughDomQuery import XssThroughDomFlow::PathGraph diff --git a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/src/App.tsx b/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/src/App.tsx index 79f943d4d..5126d24c1 100644 --- a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/src/App.tsx +++ b/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/src/App.tsx @@ -3,12 +3,12 @@ import {Input, TextArea, Search, ShellBarSearch, ComboBox, MultiComboBox, Select import '@ui5/webcomponents/dist/Assets.js'; function App() { - // Input component usage + /* `Input`: Accepts unrestricted string */ const [inputValue, setInputValue] = useState(""); const inputRef = useRef(null); const handleInputChange = useCallback(() => { - setInputValue((msg) => inputRef.current?.value || ""); {/* Potentially Unsafe */} + setInputValue((msg) => inputRef.current?.value || ""); // UNSAFE: Unrestricted string set as content }, [setInputValue]); useEffect(() => { @@ -19,12 +19,12 @@ function App() { }; }, [handleInputChange]); - // TextArea component usage + /* `TextArea`: Accepts unrestricted string */ const [textAreaValue, setTextAreaValue] = useState(""); const textAreaRef = useRef(null); const handleTextAreaChange = useCallback(() => { - setTextAreaValue((msg) => textAreaRef.current?.value || ""); {/* Potentially Unsafe */} + setTextAreaValue((msg) => textAreaRef.current?.value || ""); // UNSAFE: Unrestricted string set as content }, [setTextAreaValue]); useEffect(() => { @@ -35,12 +35,12 @@ function App() { }; }, [handleTextAreaChange]); - // Search component usage + /* `Search`: Accepts unrestricted string */ const [searchValue, setSearchValue] = useState(""); const searchRef = useRef(null); const handleSearchChange = useCallback(() => { - setSearchValue((msg) => searchRef.current?.value || ""); {/* Potentially Unsafe */} + setSearchValue((msg) => searchRef.current?.value || ""); // UNSAFE: Unrestricted string set as content }, [setSearchValue]); useEffect(() => { @@ -51,12 +51,12 @@ function App() { }; }, [handleSearchChange]); - // ShellBarSearch component usage + /* `ShellBarSearch`: Accepts unrestricted string */ const [shellBarSearchValue, setShellBarSearchValue] = useState(""); const shellBarSearchRef = useRef(null); const handleShellBarSearchChange = useCallback(() => { - setShellBarSearchValue((msg) => shellBarSearchRef.current?.value || ""); {/* Potentially Unsafe */} + setShellBarSearchValue((msg) => shellBarSearchRef.current?.value || ""); // UNSAFE: Unrestricted string set as content }, [setShellBarSearchValue]); useEffect(() => { @@ -67,12 +67,12 @@ function App() { }; }, [handleShellBarSearchChange]); - // ComboBox component usage + /* `ComboBox`: Accepts unrestricted string */ const [comboBoxValue, setComboBoxValue] = useState(""); const comboBoxRef = useRef(null); const handleComboBoxChange = useCallback(() => { - setComboBoxValue((msg) => comboBoxRef.current?.value || ""); {/* Potentially Unsafe */} + setComboBoxValue((msg) => comboBoxRef.current?.value || ""); // UNSAFE: Unrestricted string set as content }, [setComboBoxValue]); useEffect(() => { @@ -83,12 +83,12 @@ function App() { }; }, [handleComboBoxChange]); - // MultiComboBox component usage + /* `MultiComboBox`: component usage */ const [multiComboBoxValue, setMultiComboBoxValue] = useState(""); const multiComboBoxRef = useRef(null); const handleMultiComboBoxChange = useCallback(() => { - setMultiComboBoxValue((msg) => multiComboBoxRef.current?.value || ""); {/* Safe */} + setMultiComboBoxValue((msg) => multiComboBoxRef.current?.value || ""); // SAFE: Does not take unrestricted string }, [setMultiComboBoxValue]); useEffect(() => { @@ -99,12 +99,12 @@ function App() { }; }, [handleMultiComboBoxChange]); - // Select component usage + /* `Select` component usage */ const [selectValue, setSelectValue] = useState(""); const selectRef = useRef(null); const handleSelectChange = useCallback(() => { - setSelectValue((msg) => selectRef.current?.value || ""); {/* Safe */} + setSelectValue((msg) => selectRef.current?.value || ""); // SAFE: Does not take unrestricted string }, [setSelectValue]); useEffect(() => { @@ -115,12 +115,12 @@ function App() { }; }, [handleSelectChange]); - // DatePicker component usage + /* `DatePicker`: Accepts unrestricted string */ const [datePickerValue, setDatePickerValue] = useState(""); const datePickerRef = useRef(null); const handleDatePickerChange = useCallback(() => { - setDatePickerValue((msg) => datePickerRef.current?.value || ""); {/* Potentially Unsafe */} + setDatePickerValue((msg) => datePickerRef.current?.value || ""); // UNSAFE: Unrestricted string set as content }, [setDatePickerValue]); useEffect(() => { @@ -131,12 +131,12 @@ function App() { }; }, [handleDatePickerChange]); - // DateRangePicker component usage + /* `DateRangePicker`: Accepts unrestricted string */ const [dateRangePickerValue, setDateRangePickerValue] = useState(""); const dateRangePickerRef = useRef(null); const handleDateRangePickerChange = useCallback(() => { - setDateRangePickerValue((msg) => dateRangePickerRef.current?.value || ""); {/* Potentially Unsafe */} + setDateRangePickerValue((msg) => dateRangePickerRef.current?.value || ""); // UNSAFE: Unrestricted string set as content }, [setDateRangePickerValue]); useEffect(() => { @@ -147,12 +147,12 @@ function App() { }; }, [handleDateRangePickerChange]); - // DateTimePicker component usage + /* `DateTimePicker`: Accepts unrestricted string */ const [dateTimePickerValue, setDateTimePickerValue] = useState(""); const dateTimePickerRef = useRef(null); const handleDateTimePickerChange = useCallback(() => { - setDateTimePickerValue((msg) => dateTimePickerRef.current?.value || ""); {/* Potentially Unsafe */} + setDateTimePickerValue((msg) => dateTimePickerRef.current?.value || ""); // UNSAFE: Unrestricted string set as content }, [setDateTimePickerValue]); useEffect(() => { @@ -163,12 +163,12 @@ function App() { }; }, [handleDateTimePickerChange]); - // TimePicker component usage + /* `TimePicker`: Accepts unrestricted string */ const [timePickerValue, setTimePickerValue] = useState(""); const timePickerRef = useRef(null); const handleTimePickerChange = useCallback(() => { - setTimePickerValue((msg) => timePickerRef.current?.value || ""); {/* Potentially Unsafe */} + setTimePickerValue((msg) => timePickerRef.current?.value || ""); // UNSAFE: Unrestricted string set as content }, [setTimePickerValue]); useEffect(() => { @@ -179,12 +179,12 @@ function App() { }; }, [handleTimePickerChange]); - // ColorPicker component usage + /* `ColorPicker` component usage */ const [colorPickerValue, setColorPickerValue] = useState(""); const colorPickerRef = useRef(null); const handleColorPickerChange = useCallback(() => { - setColorPickerValue((msg) => colorPickerRef.current?.value || ""); {/* Safe */} + setColorPickerValue((msg) => colorPickerRef.current?.value || ""); // SAFE: Does not take unrestricted string }, [setColorPickerValue]); useEffect(() => { @@ -195,12 +195,12 @@ function App() { }; }, [handleColorPickerChange]); - // ColorPaletteItem component usage + /* `ColorPaletteItem` component usage */ const [colorPaletteItemValue, setColorPaletteItemValue] = useState(""); const colorPaletteItemRef = useRef(null); const handleColorPaletteItemChange = useCallback(() => { - setColorPaletteItemValue((msg) => colorPaletteItemRef.current?.value || ""); {/* Safe */} + setColorPaletteItemValue((msg) => colorPaletteItemRef.current?.value || ""); // SAFE: Does not take unrestricted string }, [setColorPaletteItemValue]); useEffect(() => { @@ -211,12 +211,12 @@ function App() { }; }, [handleColorPaletteItemChange]); - // CalendarDate component usage + /* `CalendarDate` component usage */ const [calendarDateValue, setCalendarDateValue] = useState(""); const calendarDateRef = useRef(null); const handleCalendarDateChange = useCallback(() => { - setCalendarDateValue((msg) => calendarDateRef.current?.value || ""); {/* Safe */} + setCalendarDateValue((msg) => calendarDateRef.current?.value || ""); // SAFE: Does not take unrestricted string }, [setCalendarDateValue]); useEffect(() => { @@ -227,12 +227,12 @@ function App() { }; }, [handleCalendarDateChange]); - // FileUploader component usage + /* `FileUploader` component usage */ const [fileUploaderValue, setFileUploaderValue] = useState(""); const fileUploaderRef = useRef(null); const handleFileUploaderChange = useCallback(() => { - setFileUploaderValue((msg) => fileUploaderRef.current?.value || ""); {/* Safe */} + setFileUploaderValue((msg) => fileUploaderRef.current?.value || ""); // SAFE: Does not take unrestricted string }, [setFileUploaderValue]); useEffect(() => { @@ -243,12 +243,12 @@ function App() { }; }, [handleFileUploaderChange]); - // CheckBox component usage + /* `CheckBox` component usage */ const [checkBoxValue, setCheckBoxValue] = useState(""); const checkBoxRef = useRef(null); const handleCheckBoxChange = useCallback(() => { - setCheckBoxValue((msg) => checkBoxRef.current?.value || ""); {/* Safe */} + setCheckBoxValue((msg) => checkBoxRef.current?.value || ""); // SAFE: Does not take unrestricted string }, [setCheckBoxValue]); useEffect(() => { @@ -259,12 +259,12 @@ function App() { }; }, [handleCheckBoxChange]); - // RadioButton component usage + /* `RadioButton` component usage */ const [radioButtonValue, setRadioButtonValue] = useState(""); const radioButtonRef = useRef(null); const handleRadioButtonChange = useCallback(() => { - setRadioButtonValue((msg) => radioButtonRef.current?.value || ""); {/* Safe */} + setRadioButtonValue((msg) => radioButtonRef.current?.value || ""); // SAFE: Does not take unrestricted string }, [setRadioButtonValue]); useEffect(() => { @@ -275,12 +275,12 @@ function App() { }; }, [handleRadioButtonChange]); - // Switch component usage + /* `Switch` component usage */ const [switchValue, setSwitchValue] = useState(""); const switchRef = useRef(null); const handleSwitchChange = useCallback(() => { - setSwitchValue((msg) => switchRef.current?.value || ""); {/* Safe */} + setSwitchValue((msg) => switchRef.current?.value || ""); // SAFE: Does not take unrestricted string }, [setSwitchValue]); useEffect(() => { @@ -291,12 +291,12 @@ function App() { }; }, [handleSwitchChange]); - // Option component usage + /* `Option`: Accepts unrestricted string */ const [optionValue, setOptionValue] = useState(""); const optionRef = useRef(null); const handleOptionChange = useCallback(() => { - setOptionValue((msg) => optionRef.current?.value || ""); {/* Potentially Unsafe */} + setOptionValue((msg) => optionRef.current?.value || ""); // UNSAFE: Unrestricted string set as content }, [setOptionValue]); useEffect(() => { @@ -307,12 +307,12 @@ function App() { }; }, [handleOptionChange]); - // OptionCustom component usage + /* `OptionCustom`: Accepts unrestricted string */ const [optionCustomValue, setOptionCustomValue] = useState(""); const optionCustomRef = useRef(null); const handleOptionCustomChange = useCallback(() => { - setOptionCustomValue((msg) => optionCustomRef.current?.value || ""); {/* Potentially Unsafe */} + setOptionCustomValue((msg) => optionCustomRef.current?.value || ""); // UNSAFE: Unrestricted string set as content }, [setOptionCustomValue]); useEffect(() => { @@ -323,12 +323,12 @@ function App() { }; }, [handleOptionCustomChange]); - // RatingIndicator component usage + /* `RatingIndicator` component usage */ const [ratingIndicatorValue, setRatingIndicatorValue] = useState(""); const ratingIndicatorRef = useRef(null); const handleRatingIndicatorChange = useCallback(() => { - setRatingIndicatorValue((msg) => ratingIndicatorRef.current?.value || ""); {/* Safe - numeric */} + setRatingIndicatorValue((msg) => ratingIndicatorRef.current?.value || ""); // SAFE: Does not take unrestricted string }, [setRatingIndicatorValue]); useEffect(() => { @@ -339,12 +339,12 @@ function App() { }; }, [handleRatingIndicatorChange]); - // Slider component usage + /* `Slider` component usage */ const [sliderValue, setSliderValue] = useState(""); const sliderRef = useRef(null); const handleSliderChange = useCallback(() => { - setSliderValue((msg) => sliderRef.current?.value || ""); {/* Safe - numeric */} + setSliderValue((msg) => sliderRef.current?.value || ""); // SAFE: Does not take unrestricted string (numeric input) }, [setSliderValue]); useEffect(() => { @@ -355,12 +355,12 @@ function App() { }; }, [handleSliderChange]); - // ProgressIndicator component usage + /* `ProgressIndicator` component usage */ const [progressIndicatorValue, setProgressIndicatorValue] = useState(""); const progressIndicatorRef = useRef(null); const handleProgressIndicatorChange = useCallback(() => { - setProgressIndicatorValue((msg) => progressIndicatorRef.current?.value || ""); {/* Safe - numeric */} + setProgressIndicatorValue((msg) => progressIndicatorRef.current?.value || ""); // SAFE: Does not take unrestricted string (numeric input) }, [setProgressIndicatorValue]); useEffect(() => { @@ -371,12 +371,12 @@ function App() { }; }, [handleProgressIndicatorChange]); - // StepInput component usage + /* `StepInput` component usage */ const [stepInputValue, setStepInputValue] = useState(""); const stepInputRef = useRef(null); const handleStepInputChange = useCallback(() => { - setStepInputValue((msg) => stepInputRef.current?.value || ""); {/* Safe - numeric */} + setStepInputValue((msg) => stepInputRef.current?.value || ""); // SAFE: Does not take unrestricted string (numeric input) }, [setStepInputValue]); useEffect(() => { @@ -387,12 +387,12 @@ function App() { }; }, [handleStepInputChange]); - // DynamicDateRange component usage + /* `DynamicDateRange` component usage */ const [dynamicDateRangeValue, setDynamicDateRangeValue] = useState(""); const dynamicDateRangeRef = useRef(null); const handleDynamicDateRangeChange = useCallback(() => { - setDynamicDateRangeValue((msg) => dynamicDateRangeRef.current?.value || ""); {/* Safe - numeric */} + setDynamicDateRangeValue((msg) => dynamicDateRangeRef.current?.value || ""); // SAFE: Does not take unrestricted string (numeric input) }, [setDynamicDateRangeValue]); useEffect(() => { @@ -403,12 +403,12 @@ function App() { }; }, [handleDynamicDateRangeChange]); - //RangeSlider component usage + /* `RangeSlider` component usage */ const [rangeSliderValue, setRangeSliderValue] = useState(""); const rangeSliderRef = useRef(null); const handleRangeSliderChange = useCallback(() => { - setRangeSliderValue((msg) => rangeSliderRef.current?.value || ""); {/* Safe - numeric */} + setRangeSliderValue((msg) => rangeSliderRef.current?.value || ""); // SAFE: Does not take unrestricted string (numeric input) }, [setRangeSliderValue]); useEffect(() => { @@ -419,12 +419,12 @@ function App() { }; }, [handleRangeSliderChange]); - // Button component usage + /* `Button` component usage */ const [buttonValue, setButtonValue] = useState(""); const buttonRef = useRef(null); const handleButtonChange = useCallback(() => { - setButtonValue((msg) => buttonRef.current?.value || ""); {/* Safe */} + setButtonValue((msg) => buttonRef.current?.value || ""); // SAFE: Does not take unrestricted string }, [setButtonValue]); useEffect(() => { @@ -435,12 +435,12 @@ function App() { }; }, [handleButtonChange]); - // MessageViewButton component usage + /* `MessageViewButton` component usage */ const [messageViewButtonValue, setMessageViewButtonValue] = useState(""); const messageViewButtonRef = useRef(null); const handleMessageViewButtonChange = useCallback(() => { - setMessageViewButtonValue((msg) => messageViewButtonRef.current?.value || ""); {/* Safe */} + setMessageViewButtonValue((msg) => messageViewButtonRef.current?.value || ""); // SAFE: Does not take unrestricted string }, [setMessageViewButtonValue]); useEffect(() => { @@ -451,12 +451,12 @@ function App() { }; }, [handleMessageViewButtonChange]); - // SegmentedButton component usage + /* `SegmentedButton` component usage */ const [segmentedButtonValue, setSegmentedButtonValue] = useState(""); const segmentedButtonRef = useRef(null); const handleSegmentedButtonChange = useCallback(() => { - setSegmentedButtonValue((msg) => segmentedButtonRef.current?.value || ""); {/* Safe */} + setSegmentedButtonValue((msg) => segmentedButtonRef.current?.value || ""); // SAFE: Does not take unrestricted string }, [setSegmentedButtonValue]); useEffect(() => { @@ -467,12 +467,12 @@ function App() { }; }, [handleSegmentedButtonChange]); - // SplitButton component usage + /* `SplitButton` component usage */ const [splitButtonValue, setSplitButtonValue] = useState(""); const splitButtonRef = useRef(null); const handleSplitButtonChange = useCallback(() => { - setSplitButtonValue((msg) => splitButtonRef.current?.value || ""); {/* Safe */} + setSplitButtonValue((msg) => splitButtonRef.current?.value || ""); // SAFE: Does not take unrestricted string }, [setSplitButtonValue]); useEffect(() => { @@ -483,12 +483,12 @@ function App() { }; }, [handleSplitButtonChange]); - // ToggleButton component usage + /* `ToggleButton` component usage */ const [toggleButtonValue, setToggleButtonValue] = useState(""); const toggleButtonRef = useRef(null); const handleToggleButtonChange = useCallback(() => { - setToggleButtonValue((msg) => toggleButtonRef.current?.value || ""); {/* Safe */} + setToggleButtonValue((msg) => toggleButtonRef.current?.value || ""); // SAFE: Does not take unrestricted string }, [setToggleButtonValue]); useEffect(() => { @@ -502,24 +502,24 @@ function App() { return (
- {/* Potentially Unsafe */} + {/* Potentially Unsafe */} {/* Potentially Unsafe */} {/* Potentially Unsafe */} {/* Potentially Unsafe */} {/* Potentially Unsafe */} - {/* Safe */} - {/* Safe */} + {/* Safe - accepts a fixed set of strings */} + {/* Safe - accepts a fixed set of strings */} {/* Potentially Unsafe */} {/* Potentially Unsafe */} {/* Potentially Unsafe */} {/* Potentially Unsafe */} - {/* Safe */} - {/* Safe */} + {/* Safe - does not accept any string input */} + {/* Safe - does not accept any string input */} {/* Safe - not a standalone component */} - {/* Safe */} - {/* Safe */} - {/* Safe */} - {/* Safe */} + {/* Safe - accepts a fixed set of strings */} + {/* Safe - does not accept any string input */} + {/* Safe - does not accept any input */} + {/* Safe - does not accept any input */} {/* Potentially Unsafe */} {/* Potentially Unsafe */} {/* Safe - numeric */} @@ -528,11 +528,11 @@ function App() { {/* Safe - numeric */} {/* Safe - numeric */} {/* Safe - numeric */} - {/* Safe */} - {/* Safe */} - {/* Safe */} - Split {/* Safe */} - Toggle {/* Safe */} + {/* Safe - does not accept any input */} + {/* Safe - does not accept any input */} + {/* Safe - does not accept any input */} + Split {/* Safe - does not accept any input */} + Toggle {/* Safe - does not accept any input */}
diff --git a/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/UI5WebcomponentsReact.qll b/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/UI5WebcomponentsReact.qll index 354d5dcff..82b474a46 100644 --- a/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/UI5WebcomponentsReact.qll +++ b/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/UI5WebcomponentsReact.qll @@ -5,17 +5,14 @@ import semmle.javascript.security.dataflow.DomBasedXssCustomizations * import { Input, Button } from '@ui5/webcomponents-react'; * ``` */ -class WebComponentImport extends API::Node { - WebComponentImport() { this = API::moduleImport("@ui5/webcomponents-react") } -} +class WebComponentImport extends DataFlow::SourceNode { + string typeName; -private DataFlow::SourceNode isUI5WebComponentReact(DataFlow::TypeTracker t) { - exists(WebComponentImport i | result = i.getMember(_).asSource()) or - exists(DataFlow::TypeTracker t2 | result = isUI5WebComponentReact(t).track(t2, t)) -} + WebComponentImport() { + this = API::moduleImport("@ui5/webcomponents-react").getMember(typeName).asSource() + } -DataFlow::SourceNode isUI5WebComponentReact() { - result = isUI5WebComponentReact(DataFlow::TypeTracker::end()) + string getTypeName() { result = typeName } } /** @@ -31,13 +28,12 @@ class RefAttribute extends JsxAttribute { */ predicate isRefAssignedToUI5Component(UseRefDomValueSource source) { exists( - Variable refVar, JsxElement jsx, RefAttribute attr, VarRef componentVar, DataFlow::Node decl + Variable refVar, JsxElement jsx, RefAttribute attr, VarRef componentVar, WebComponentImport decl | source.getElement() = jsx and // The JSX element uses a UI5 webcomponent for react jsx.getNameExpr() = componentVar and decl.asExpr() = componentVar.getVariable().getADefinition() and - isUI5WebComponentReact() = decl and // The JSX element has a ref attribute pointing to our ref variable jsx.getAnAttribute() = attr and attr.getValue().(VarRef).getVariable() = refVar From 15325d669d66213b10003f98f47953494cd68da6 Mon Sep 17 00:00:00 2001 From: Kristen Newbury Date: Wed, 3 Dec 2025 15:28:58 -0500 Subject: [PATCH 21/23] Adjust qualified name in reference in lib UI5WebcomponentsReact --- .../javascript/frameworks/ui5/UI5WebcomponentsReact.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/UI5WebcomponentsReact.qll b/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/UI5WebcomponentsReact.qll index 82b474a46..3d347e564 100644 --- a/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/UI5WebcomponentsReact.qll +++ b/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/UI5WebcomponentsReact.qll @@ -41,7 +41,7 @@ predicate isRefAssignedToUI5Component(UseRefDomValueSource source) { } /** - * A custom version of the `UseRefDomValueSource` in the out of the box libraries + * A custom version of the `React::UseRefDomValueSource` in the out of the box libraries * this version exposes its JSX element and also is not private */ class UseRefDomValueSource extends DOM::DomValueSource::Range { From e61d80c8de4b731cb2188fddd51aacf8e9277705 Mon Sep 17 00:00:00 2001 From: Kristen Newbury Date: Wed, 10 Dec 2025 11:34:59 -0500 Subject: [PATCH 22/23] Add multicombo box to ui5 webcomponents react model, falsely rm'd --- .../XssThroughDom.expected | 10 ++++++++++ .../xss-input-dangerouslySetInnerHTML/src/App.tsx | 4 ++-- .../javascript/frameworks/ui5/Sanitizers.qll | 8 ++++---- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.expected b/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.expected index b77351291..7cd5021f6 100644 --- a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.expected +++ b/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/XssThroughDom.expected @@ -19,6 +19,10 @@ edges | src/App.tsx:71:10:71:22 | comboBoxValue | src/App.tsx:541:46:541:58 | comboBoxValue | provenance | | | src/App.tsx:75:31:75:56 | comboBo ... ?.value | src/App.tsx:75:31:75:62 | comboBo ... e \|\| "" | provenance | | | src/App.tsx:75:31:75:62 | comboBo ... e \|\| "" | src/App.tsx:71:10:71:22 | comboBoxValue | provenance | | +| src/App.tsx:87:10:87:27 | multiComboBoxValue | src/App.tsx:87:10:87:27 | multiComboBoxValue | provenance | | +| src/App.tsx:87:10:87:27 | multiComboBoxValue | src/App.tsx:542:46:542:63 | multiComboBoxValue | provenance | | +| src/App.tsx:91:36:91:66 | multiCo ... ?.value | src/App.tsx:91:36:91:72 | multiCo ... e \|\| "" | provenance | | +| src/App.tsx:91:36:91:72 | multiCo ... e \|\| "" | src/App.tsx:87:10:87:27 | multiComboBoxValue | provenance | | | src/App.tsx:119:10:119:24 | datePickerValue | src/App.tsx:119:10:119:24 | datePickerValue | provenance | | | src/App.tsx:119:10:119:24 | datePickerValue | src/App.tsx:544:46:544:60 | datePickerValue | provenance | | | src/App.tsx:123:33:123:60 | datePic ... ?.value | src/App.tsx:123:33:123:66 | datePic ... e \|\| "" | provenance | | @@ -64,6 +68,10 @@ nodes | src/App.tsx:71:10:71:22 | comboBoxValue | semmle.label | comboBoxValue | | src/App.tsx:75:31:75:56 | comboBo ... ?.value | semmle.label | comboBo ... ?.value | | src/App.tsx:75:31:75:62 | comboBo ... e \|\| "" | semmle.label | comboBo ... e \|\| "" | +| src/App.tsx:87:10:87:27 | multiComboBoxValue | semmle.label | multiComboBoxValue | +| src/App.tsx:87:10:87:27 | multiComboBoxValue | semmle.label | multiComboBoxValue | +| src/App.tsx:91:36:91:66 | multiCo ... ?.value | semmle.label | multiCo ... ?.value | +| src/App.tsx:91:36:91:72 | multiCo ... e \|\| "" | semmle.label | multiCo ... e \|\| "" | | src/App.tsx:119:10:119:24 | datePickerValue | semmle.label | datePickerValue | | src/App.tsx:119:10:119:24 | datePickerValue | semmle.label | datePickerValue | | src/App.tsx:123:33:123:60 | datePic ... ?.value | semmle.label | datePic ... ?.value | @@ -93,6 +101,7 @@ nodes | src/App.tsx:539:46:539:56 | searchValue | semmle.label | searchValue | | src/App.tsx:540:46:540:64 | shellBarSearchValue | semmle.label | shellBarSearchValue | | src/App.tsx:541:46:541:58 | comboBoxValue | semmle.label | comboBoxValue | +| src/App.tsx:542:46:542:63 | multiComboBoxValue | semmle.label | multiComboBoxValue | | src/App.tsx:544:46:544:60 | datePickerValue | semmle.label | datePickerValue | | src/App.tsx:545:46:545:65 | dateRangePickerValue | semmle.label | dateRangePickerValue | | src/App.tsx:546:46:546:64 | dateTimePickerValue | semmle.label | dateTimePickerValue | @@ -106,6 +115,7 @@ subpaths | src/App.tsx:539:46:539:56 | searchValue | src/App.tsx:43:29:43:52 | searchR ... ?.value | src/App.tsx:539:46:539:56 | searchValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:43:29:43:52 | searchR ... ?.value | DOM text | | src/App.tsx:540:46:540:64 | shellBarSearchValue | src/App.tsx:59:37:59:68 | shellBa ... ?.value | src/App.tsx:540:46:540:64 | shellBarSearchValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:59:37:59:68 | shellBa ... ?.value | DOM text | | src/App.tsx:541:46:541:58 | comboBoxValue | src/App.tsx:75:31:75:56 | comboBo ... ?.value | src/App.tsx:541:46:541:58 | comboBoxValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:75:31:75:56 | comboBo ... ?.value | DOM text | +| src/App.tsx:542:46:542:63 | multiComboBoxValue | src/App.tsx:91:36:91:66 | multiCo ... ?.value | src/App.tsx:542:46:542:63 | multiComboBoxValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:91:36:91:66 | multiCo ... ?.value | DOM text | | src/App.tsx:544:46:544:60 | datePickerValue | src/App.tsx:123:33:123:60 | datePic ... ?.value | src/App.tsx:544:46:544:60 | datePickerValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:123:33:123:60 | datePic ... ?.value | DOM text | | src/App.tsx:545:46:545:65 | dateRangePickerValue | src/App.tsx:139:38:139:70 | dateRan ... ?.value | src/App.tsx:545:46:545:65 | dateRangePickerValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:139:38:139:70 | dateRan ... ?.value | DOM text | | src/App.tsx:546:46:546:64 | dateTimePickerValue | src/App.tsx:155:37:155:68 | dateTim ... ?.value | src/App.tsx:546:46:546:64 | dateTimePickerValue | $@ is reinterpreted as HTML without escaping meta-characters. | src/App.tsx:155:37:155:68 | dateTim ... ?.value | DOM text | diff --git a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/src/App.tsx b/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/src/App.tsx index 5126d24c1..b33f7a04b 100644 --- a/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/src/App.tsx +++ b/javascript/frameworks/ui5-webcomponents/test/queries/xss-input-dangerouslySetInnerHTML/src/App.tsx @@ -88,7 +88,7 @@ function App() { const multiComboBoxRef = useRef(null); const handleMultiComboBoxChange = useCallback(() => { - setMultiComboBoxValue((msg) => multiComboBoxRef.current?.value || ""); // SAFE: Does not take unrestricted string + setMultiComboBoxValue((msg) => multiComboBoxRef.current?.value || ""); // UNSAFE: Unrestricted string set as content }, [setMultiComboBoxValue]); useEffect(() => { @@ -507,7 +507,7 @@ function App() { {/* Potentially Unsafe */} {/* Potentially Unsafe */} {/* Potentially Unsafe */} - {/* Safe - accepts a fixed set of strings */} + {/* Potentially Unsafe */} {/* Safe - accepts a fixed set of strings */} {/* Potentially Unsafe */} {/* Potentially Unsafe */} diff --git a/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/Sanitizers.qll b/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/Sanitizers.qll index 9c1e070e0..f0ede5049 100644 --- a/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/Sanitizers.qll +++ b/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/Sanitizers.qll @@ -15,10 +15,10 @@ class ExcludedSource extends DomBasedXss::Sanitizer { // exclude components with this name from @ui5/webcomponents-react only isRefAssignedToUI5Component(source) and source.getElement().getName() in [ - "MultiComboBox", "Select", "ColorPicker", "ColorPaletteItem", "CalendarDate", - "FileUploader", "CheckBox", "RadioButton", "Switch", "RatingIndicator", "Slider", - "ProgressIndicator", "StepInput", "DynamicDateRange", "RangeSlider", "Button", - "MessageViewButton", "SegmentedButton", "SplitButton", "ToggleButton" + "Select", "ColorPicker", "ColorPaletteItem", "CalendarDate", "FileUploader", "CheckBox", + "RadioButton", "Switch", "RatingIndicator", "Slider", "ProgressIndicator", "StepInput", + "DynamicDateRange", "RangeSlider", "Button", "MessageViewButton", "SegmentedButton", + "SplitButton", "ToggleButton" ] and this.(DataFlow::PropRead).getBase() = source ) From 1a8a0628f14f3ddabed36bc1a937a19a576f6451 Mon Sep 17 00:00:00 2001 From: Kristen Newbury Date: Wed, 10 Dec 2025 12:25:05 -0500 Subject: [PATCH 23/23] Apply suggestions from code review Co-authored-by: Jeongsoo Lee --- .../javascript/frameworks/ui5/UI5WebcomponentsReact.qll | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/UI5WebcomponentsReact.qll b/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/UI5WebcomponentsReact.qll index 3d347e564..5337dbe3c 100644 --- a/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/UI5WebcomponentsReact.qll +++ b/javascript/frameworks/ui5/lib/advanced_security/javascript/frameworks/ui5/UI5WebcomponentsReact.qll @@ -1,6 +1,8 @@ import semmle.javascript.security.dataflow.DomBasedXssCustomizations /** + * Classes imported from `@ui5/webcomponents-react`. e.g. + * * ``` javascript * import { Input, Button } from '@ui5/webcomponents-react'; * ``` @@ -16,8 +18,11 @@ class WebComponentImport extends DataFlow::SourceNode { } /** - * Refers to the ref attribute + * The `ref` attribute of a JSX attribute. e.g. + * + * ``` javascript * + * ``` */ class RefAttribute extends JsxAttribute { RefAttribute() { this.getName() = "ref" }