Merge pull request #19006 from Napalys/js/vue_tanstack_model

Js: Added support for `@tanstack/vue-query`
This commit is contained in:
Napalys Klicius
2025-03-14 14:36:35 +01:00
committed by GitHub
9 changed files with 209 additions and 30 deletions

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added support for the `@tanstack/vue-query` package.

View File

@@ -3,5 +3,9 @@ extensions:
pack: codeql/javascript-all
extensible: summaryModel
data:
- ["@tanstack/angular-query-experimental", "Member[injectQuery]", "Argument[0].ReturnValue.Member[queryFn].ReturnValue", "ReturnValue.Member[data].Awaited", "taint"]
- ["@tanstack/angular-query", "Member[injectQuery]", "Argument[0].ReturnValue.Member[queryFn].ReturnValue", "ReturnValue.Member[data].Awaited", "taint"]
- ["@tanstack/angular-query-experimental", "Member[injectQuery]", "Argument[0].ReturnValue.Member[queryFn].ReturnValue", "ReturnValue.Member[data].Awaited", "value"]
- ["@tanstack/angular-query", "Member[injectQuery]", "Argument[0].ReturnValue.Member[queryFn].ReturnValue", "ReturnValue.Member[data].Awaited", "value"]
- ["@tanstack/vue-query", "Member[useQuery]", "Argument[0].Member[queryFn].ReturnValue.Awaited", "ReturnValue.Member[data]", "value"]
- ["@tanstack/vue-query", "Member[useQueries]", "Argument[0].Member[queries].ArrayElement.Member[queryFn].ReturnValue.Awaited", "ReturnValue.AnyMember.Member[data]", "value"]
- ["@tanstack/react-query", "Member[useQueries]", "Argument[0].Member[queries].ArrayElement.Member[queryFn].ReturnValue.Awaited", "ReturnValue.AnyMember.Member[data]", "value"]
- ["@tanstack/react-query", "Member[useQuery]", "Argument[0].Member[queryFn].ReturnValue.Awaited", "ReturnValue.Member[data]", "value"]

View File

@@ -139,7 +139,6 @@ import semmle.javascript.frameworks.Webix
import semmle.javascript.frameworks.WebSocket
import semmle.javascript.frameworks.XmlParsers
import semmle.javascript.frameworks.xUnit
import semmle.javascript.frameworks.Tanstack
import semmle.javascript.linters.ESLint
import semmle.javascript.linters.JSLint
import semmle.javascript.linters.Linting

View File

@@ -1,26 +0,0 @@
/**
* Provides classes and predicates modeling the Tanstack/react-query library.
*/
private import javascript
/**
* An additional flow step that propagates data from the return value of the query function,
* defined in a useQuery call from the '@tanstack/react-query' module, to the 'data' property.
*/
private class TanstackStep extends DataFlow::AdditionalFlowStep {
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
exists(API::CallNode useQuery |
useQuery = useQueryCall() and
node1 = useQuery.getParameter(0).getMember("queryFn").getReturn().getPromised().asSink() and
node2 = useQuery.getReturn().getMember("data").asSource()
)
}
}
/**
* Retrieves a call node representing a useQuery invocation from the '@tanstack/react-query' module.
*/
private API::CallNode useQueryCall() {
result = API::moduleImport("@tanstack/react-query").getMember("useQuery").getACall()
}

View File

@@ -2,6 +2,7 @@
| test.jsx:27:29:27:32 | data | test.jsx:5:28:5:63 | fetch(" ... ntent") | test.jsx:27:29:27:32 | data | Cross-site scripting vulnerability due to $@. | test.jsx:5:28:5:63 | fetch(" ... ntent") | user-provided value |
| test.ts:21:57:21:76 | response.description | test.ts:8:9:8:79 | this.#h ... query') | test.ts:21:57:21:76 | response.description | Cross-site scripting vulnerability due to $@. | test.ts:8:9:8:79 | this.#h ... query') | user-provided value |
| test.ts:24:36:24:90 | `<h2>${ ... o}</p>` | test.ts:8:9:8:79 | this.#h ... query') | test.ts:24:36:24:90 | `<h2>${ ... o}</p>` | Cross-site scripting vulnerability due to $@. | test.ts:8:9:8:79 | this.#h ... query') | user-provided value |
| test.vue:22:10:22:22 | v-html=data | test.vue:10:32:10:84 | fetch(" ... sts/1") | test.vue:22:10:22:22 | v-html=data | Cross-site scripting vulnerability due to $@. | test.vue:10:32:10:84 | fetch(" ... sts/1") | user-provided value |
| testReactRelay.tsx:7:43:7:58 | commentData.text | testReactRelay.tsx:5:23:5:52 | useFrag ... entRef) | testReactRelay.tsx:7:43:7:58 | commentData.text | Cross-site scripting vulnerability due to $@. | testReactRelay.tsx:5:23:5:52 | useFrag ... entRef) | user-provided value |
| testReactRelay.tsx:18:48:18:68 | data.co ... 0].text | testReactRelay.tsx:17:16:17:42 | useLazy ... ry, {}) | testReactRelay.tsx:18:48:18:68 | data.co ... 0].text | Cross-site scripting vulnerability due to $@. | testReactRelay.tsx:17:16:17:42 | useLazy ... ry, {}) | user-provided value |
| testReactRelay.tsx:28:17:28:67 | usePrel ... r?.name | testReactRelay.tsx:28:17:28:56 | usePrel ... erence) | testReactRelay.tsx:28:17:28:67 | usePrel ... r?.name | Cross-site scripting vulnerability due to $@. | testReactRelay.tsx:28:17:28:56 | usePrel ... erence) | user-provided value |
@@ -12,6 +13,9 @@
| testReactRelay.tsx:113:48:113:58 | fragmentRef | testReactRelay.tsx:100:14:100:16 | res | testReactRelay.tsx:113:48:113:58 | fragmentRef | Cross-site scripting vulnerability due to $@. | testReactRelay.tsx:100:14:100:16 | res | user-provided value |
| testReactRelay.tsx:127:35:127:43 | data.user | testReactRelay.tsx:124:12:124:15 | data | testReactRelay.tsx:127:35:127:43 | data.user | Cross-site scripting vulnerability due to $@. | testReactRelay.tsx:124:12:124:15 | data | user-provided value |
| testReactRelay.tsx:137:50:137:53 | data | testReactRelay.tsx:136:16:136:39 | readFra ... y, key) | testReactRelay.tsx:137:50:137:53 | data | Cross-site scripting vulnerability due to $@. | testReactRelay.tsx:136:16:136:39 | readFra ... y, key) | user-provided value |
| testReactUseQueries.jsx:37:25:37:38 | repoQuery.data | testReactUseQueries.jsx:4:26:4:53 | fetch(' ... e.com') | testReactUseQueries.jsx:37:25:37:38 | repoQuery.data | Cross-site scripting vulnerability due to $@. | testReactUseQueries.jsx:4:26:4:53 | fetch(' ... e.com') | user-provided value |
| testUseQueries2.vue:40:10:40:23 | v-html=data3 | testUseQueries2.vue:6:28:6:63 | fetch(" ... ntent") | testUseQueries2.vue:40:10:40:23 | v-html=data3 | Cross-site scripting vulnerability due to $@. | testUseQueries2.vue:6:28:6:63 | fetch(" ... ntent") | user-provided value |
| testUseQueries2.vue:40:10:40:23 | v-html=data3 | testUseQueries2.vue:12:28:12:41 | fetch("${id}") | testUseQueries2.vue:40:10:40:23 | v-html=data3 | Cross-site scripting vulnerability due to $@. | testUseQueries2.vue:12:28:12:41 | fetch("${id}") | user-provided value |
edges
| test.jsx:5:11:5:63 | response | test.jsx:6:24:6:31 | response | provenance | |
| test.jsx:5:22:5:63 | await f ... ntent") | test.jsx:5:11:5:63 | response | provenance | |
@@ -20,8 +24,9 @@ edges
| test.jsx:6:18:6:38 | await r ... .json() | test.jsx:6:11:6:38 | data | provenance | |
| test.jsx:6:24:6:31 | response | test.jsx:6:24:6:38 | response.json() | provenance | |
| test.jsx:6:24:6:38 | response.json() | test.jsx:6:18:6:38 | await r ... .json() | provenance | |
| test.jsx:7:12:7:15 | data | test.jsx:15:11:17:5 | data | provenance | |
| test.jsx:7:12:7:15 | data | test.jsx:15:13:15:16 | data | provenance | |
| test.jsx:15:11:17:5 | data | test.jsx:27:29:27:32 | data | provenance | |
| test.jsx:15:13:15:16 | data | test.jsx:15:11:17:5 | data | provenance | |
| test.ts:8:9:8:79 | this.#h ... query') | test.ts:20:28:20:35 | response | provenance | |
| test.ts:20:28:20:35 | response | test.ts:21:57:21:64 | response | provenance | |
| test.ts:20:28:20:35 | response | test.ts:24:43:24:50 | response | provenance | |
@@ -31,6 +36,14 @@ edges
| test.ts:24:43:24:55 | response.name | test.ts:24:36:24:90 | `<h2>${ ... o}</p>` | provenance | |
| test.ts:24:67:24:74 | response | test.ts:24:67:24:84 | response.owner.bio | provenance | |
| test.ts:24:67:24:84 | response.owner.bio | test.ts:24:36:24:90 | `<h2>${ ... o}</p>` | provenance | |
| test.vue:7:11:13:6 | data | test.vue:15:21:15:24 | data | provenance | |
| test.vue:7:45:7:48 | data | test.vue:7:11:13:6 | data | provenance | |
| test.vue:10:15:10:84 | response | test.vue:11:16:11:23 | response | provenance | |
| test.vue:10:26:10:84 | await f ... sts/1") | test.vue:10:15:10:84 | response | provenance | |
| test.vue:10:32:10:84 | fetch(" ... sts/1") | test.vue:10:26:10:84 | await f ... sts/1") | provenance | |
| test.vue:11:16:11:23 | response | test.vue:11:16:11:30 | response.json() | provenance | |
| test.vue:11:16:11:30 | response.json() | test.vue:7:45:7:48 | data | provenance | |
| test.vue:15:21:15:24 | data | test.vue:22:10:22:22 | v-html=data | provenance | |
| testReactRelay.tsx:5:9:5:52 | commentData | testReactRelay.tsx:7:43:7:53 | commentData | provenance | |
| testReactRelay.tsx:5:23:5:52 | useFrag ... entRef) | testReactRelay.tsx:5:9:5:52 | commentData | provenance | |
| testReactRelay.tsx:7:43:7:53 | commentData | testReactRelay.tsx:7:43:7:58 | commentData.text | provenance | |
@@ -56,6 +69,25 @@ edges
| testReactRelay.tsx:127:35:127:38 | data | testReactRelay.tsx:127:35:127:43 | data.user | provenance | |
| testReactRelay.tsx:136:9:136:39 | data | testReactRelay.tsx:137:50:137:53 | data | provenance | |
| testReactRelay.tsx:136:16:136:39 | readFra ... y, key) | testReactRelay.tsx:136:9:136:39 | data | provenance | |
| testReactUseQueries.jsx:4:9:4:53 | response | testReactUseQueries.jsx:5:10:5:17 | response | provenance | |
| testReactUseQueries.jsx:4:20:4:53 | await f ... e.com') | testReactUseQueries.jsx:4:9:4:53 | response | provenance | |
| testReactUseQueries.jsx:4:26:4:53 | fetch(' ... e.com') | testReactUseQueries.jsx:4:20:4:53 | await f ... e.com') | provenance | |
| testReactUseQueries.jsx:5:10:5:17 | response | testReactUseQueries.jsx:5:10:5:24 | response.json() | provenance | |
| testReactUseQueries.jsx:5:10:5:24 | response.json() | testReactUseQueries.jsx:37:25:37:38 | repoQuery.data | provenance | |
| testUseQueries2.vue:6:11:6:63 | response | testUseQueries2.vue:7:24:7:31 | response | provenance | |
| testUseQueries2.vue:6:22:6:63 | await f ... ntent") | testUseQueries2.vue:6:11:6:63 | response | provenance | |
| testUseQueries2.vue:6:28:6:63 | fetch(" ... ntent") | testUseQueries2.vue:6:22:6:63 | await f ... ntent") | provenance | |
| testUseQueries2.vue:7:11:7:38 | data | testUseQueries2.vue:8:12:8:15 | data | provenance | |
| testUseQueries2.vue:7:18:7:38 | await r ... .json() | testUseQueries2.vue:7:11:7:38 | data | provenance | |
| testUseQueries2.vue:7:24:7:31 | response | testUseQueries2.vue:7:24:7:38 | response.json() | provenance | |
| testUseQueries2.vue:7:24:7:38 | response.json() | testUseQueries2.vue:7:18:7:38 | await r ... .json() | provenance | |
| testUseQueries2.vue:8:12:8:15 | data | testUseQueries2.vue:33:22:33:36 | results[0].data | provenance | |
| testUseQueries2.vue:12:11:12:41 | response | testUseQueries2.vue:13:12:13:19 | response | provenance | |
| testUseQueries2.vue:12:22:12:41 | await fetch("${id}") | testUseQueries2.vue:12:11:12:41 | response | provenance | |
| testUseQueries2.vue:12:28:12:41 | fetch("${id}") | testUseQueries2.vue:12:22:12:41 | await fetch("${id}") | provenance | |
| testUseQueries2.vue:13:12:13:19 | response | testUseQueries2.vue:13:12:13:26 | response.json() | provenance | |
| testUseQueries2.vue:13:12:13:26 | response.json() | testUseQueries2.vue:33:22:33:36 | results[0].data | provenance | |
| testUseQueries2.vue:33:22:33:36 | results[0].data | testUseQueries2.vue:40:10:40:23 | v-html=data3 | provenance | |
nodes
| test.jsx:5:11:5:63 | response | semmle.label | response |
| test.jsx:5:22:5:63 | await f ... ntent") | semmle.label | await f ... ntent") |
@@ -66,6 +98,7 @@ nodes
| test.jsx:6:24:6:38 | response.json() | semmle.label | response.json() |
| test.jsx:7:12:7:15 | data | semmle.label | data |
| test.jsx:15:11:17:5 | data | semmle.label | data |
| test.jsx:15:13:15:16 | data | semmle.label | data |
| test.jsx:27:29:27:32 | data | semmle.label | data |
| test.ts:8:9:8:79 | this.#h ... query') | semmle.label | this.#h ... query') |
| test.ts:20:28:20:35 | response | semmle.label | response |
@@ -76,6 +109,15 @@ nodes
| test.ts:24:43:24:55 | response.name | semmle.label | response.name |
| test.ts:24:67:24:74 | response | semmle.label | response |
| test.ts:24:67:24:84 | response.owner.bio | semmle.label | response.owner.bio |
| test.vue:7:11:13:6 | data | semmle.label | data |
| test.vue:7:45:7:48 | data | semmle.label | data |
| test.vue:10:15:10:84 | response | semmle.label | response |
| test.vue:10:26:10:84 | await f ... sts/1") | semmle.label | await f ... sts/1") |
| test.vue:10:32:10:84 | fetch(" ... sts/1") | semmle.label | fetch(" ... sts/1") |
| test.vue:11:16:11:23 | response | semmle.label | response |
| test.vue:11:16:11:30 | response.json() | semmle.label | response.json() |
| test.vue:15:21:15:24 | data | semmle.label | data |
| test.vue:22:10:22:22 | v-html=data | semmle.label | v-html=data |
| testReactRelay.tsx:5:9:5:52 | commentData | semmle.label | commentData |
| testReactRelay.tsx:5:23:5:52 | useFrag ... entRef) | semmle.label | useFrag ... entRef) |
| testReactRelay.tsx:7:43:7:53 | commentData | semmle.label | commentData |
@@ -111,4 +153,25 @@ nodes
| testReactRelay.tsx:136:9:136:39 | data | semmle.label | data |
| testReactRelay.tsx:136:16:136:39 | readFra ... y, key) | semmle.label | readFra ... y, key) |
| testReactRelay.tsx:137:50:137:53 | data | semmle.label | data |
| testReactUseQueries.jsx:4:9:4:53 | response | semmle.label | response |
| testReactUseQueries.jsx:4:20:4:53 | await f ... e.com') | semmle.label | await f ... e.com') |
| testReactUseQueries.jsx:4:26:4:53 | fetch(' ... e.com') | semmle.label | fetch(' ... e.com') |
| testReactUseQueries.jsx:5:10:5:17 | response | semmle.label | response |
| testReactUseQueries.jsx:5:10:5:24 | response.json() | semmle.label | response.json() |
| testReactUseQueries.jsx:37:25:37:38 | repoQuery.data | semmle.label | repoQuery.data |
| testUseQueries2.vue:6:11:6:63 | response | semmle.label | response |
| testUseQueries2.vue:6:22:6:63 | await f ... ntent") | semmle.label | await f ... ntent") |
| testUseQueries2.vue:6:28:6:63 | fetch(" ... ntent") | semmle.label | fetch(" ... ntent") |
| testUseQueries2.vue:7:11:7:38 | data | semmle.label | data |
| testUseQueries2.vue:7:18:7:38 | await r ... .json() | semmle.label | await r ... .json() |
| testUseQueries2.vue:7:24:7:31 | response | semmle.label | response |
| testUseQueries2.vue:7:24:7:38 | response.json() | semmle.label | response.json() |
| testUseQueries2.vue:8:12:8:15 | data | semmle.label | data |
| testUseQueries2.vue:12:11:12:41 | response | semmle.label | response |
| testUseQueries2.vue:12:22:12:41 | await fetch("${id}") | semmle.label | await fetch("${id}") |
| testUseQueries2.vue:12:28:12:41 | fetch("${id}") | semmle.label | fetch("${id}") |
| testUseQueries2.vue:13:12:13:19 | response | semmle.label | response |
| testUseQueries2.vue:13:12:13:26 | response.json() | semmle.label | response.json() |
| testUseQueries2.vue:33:22:33:36 | results[0].data | semmle.label | results[0].data |
| testUseQueries2.vue:40:10:40:23 | v-html=data3 | semmle.label | v-html=data3 |
subpaths

View File

@@ -0,0 +1,24 @@
<script>
import { useQuery, VueQueryClientProvider } from "@tanstack/vue-query";
export default {
data() {
const { isPending, isError, isFetching, data, error } = useQuery({
queryKey: ["post"],
queryFn: async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/posts/1"); // $ Source
return response.json();
},
});
return { data : data };
}
}
</script>
<template>
<VueQueryClientProvider :client="queryClient">
<div v-html="data"></div> <!--$ Alert[js/xss] -->
</VueQueryClientProvider>
</template>

View File

@@ -0,0 +1,42 @@
import { useQueries } from '@tanstack/react-query';
const fetchRepoData = async () => {
const response = await fetch('https://example.com'); // $ Source
return response.json();
};
async function fetchPost() {
const response = await fetch("www.example.com"); // $ MISSING: Source
return response.json();
}
export default function UseQueriesComponent() {
const results = useQueries({
queries: [
{
queryKey: ['repoData'],
queryFn: fetchRepoData,
},
{
queryKey: ['repoData'],
queryFn: () => fetchPost,
},
],
});
const repoQuery = results[0];
if (repoQuery.isLoading) return <p>Loading...</p>;
if (repoQuery.isError) return <p>Error: {repoQuery.error.message}</p>;
return (
<div>
<h1>Content with Dangerous HTML</h1>
<div
dangerouslySetInnerHTML={{
__html: repoQuery.data, // $ Alert
}}
/>
</div>
);
}

View File

@@ -0,0 +1,27 @@
<script>
import { useQueries } from "@tanstack/vue-query";
export default {
data() {
const ids = [1, 2, 3]
const results = useQueries({
queries: ids.map((id) => ({
queryKey: ['post', id],
queryFn: async () => {
const response = await fetch("${id}"); // $ MISSING: Source
return response.json();
},
staleTime: Infinity,
})),
});
return { data2 : results[0].data };
}
}
</script>
<template>
<VueQueryClientProvider :client="queryClient">
<div v-html="data2"></div> <!--$ MISSING: Alert -->
</VueQueryClientProvider>
</template>

View File

@@ -0,0 +1,42 @@
<script>
import { useQueries } from "@tanstack/vue-query";
import { computed } from "vue";
const fetchContent = async () => {
const response = await fetch("https://example.com/content"); // $ Source
const data = await response.json();
return data;
};
async function fetchPost() {
const response = await fetch("${id}"); // $ Source
return response.json();
}
export default {
data() {
const results = useQueries({
queries: [
{
queryKey: ["post", 1],
queryFn: fetchContent,
staleTime: Infinity,
},
{
queryKey: ["post", 2],
queryFn: () => fetchPost(),
staleTime: Infinity,
},
],
});
return { data3 : results[0].data };
},
};
</script>
<template>
<VueQueryClientProvider :client="queryClient">
<div v-html="data3"></div> <!--$ Alert -->
</VueQueryClientProvider>
</template>