Skip to content

Commit 1227a7e

Browse files
committed
Add Tanstack framework support and enhance data flow tracking for fetch responses
1 parent 05690c2 commit 1227a7e

File tree

6 files changed

+75
-2
lines changed

6 files changed

+75
-2
lines changed

javascript/ql/lib/javascript.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ import semmle.javascript.frameworks.Webix
139139
import semmle.javascript.frameworks.WebSocket
140140
import semmle.javascript.frameworks.XmlParsers
141141
import semmle.javascript.frameworks.xUnit
142+
import semmle.javascript.frameworks.Tanstack
142143
import semmle.javascript.linters.ESLint
143144
import semmle.javascript.linters.JSLint
144145
import semmle.javascript.linters.Linting

javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,4 +861,23 @@ module ClientRequest {
861861
result = form.getMember("append").getACall().getParameter(1).asSink()
862862
}
863863
}
864+
865+
private class ClientRequestThreatModel extends ThreatModelSource::Range {
866+
ClientRequestThreatModel() { this = any(ClientRequest r).getAResponseDataNode() }
867+
868+
override string getThreatModel() { result = "response" }
869+
870+
override string getSourceType() { result = "HTTP response data" }
871+
}
872+
873+
class FetchResponseStep extends TaintTracking::AdditionalTaintStep {
874+
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
875+
exists(DataFlow::MethodCallNode call |
876+
call.getMethodName() in ["json", "text", "blob", "arrayBuffer"] and
877+
node1 = call.getReceiver() and
878+
node2 = call and
879+
call.getNumArgument() = 0
880+
)
881+
}
882+
}
864883
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
private import javascript
2+
3+
class Fetch extends DataFlow::AdditionalFlowStep {
4+
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
5+
exists(DataFlow::MethodCallNode call |
6+
call.getMethodName() in ["json", "text", "blob", "arrayBuffer"] and
7+
node1 = call.getReceiver() and
8+
node2 = call
9+
)
10+
}
11+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
private import javascript
2+
3+
class TanstackStep extends DataFlow::AdditionalFlowStep {
4+
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
5+
exists(DataFlow::CallNode useQuery |
6+
useQuery = useQueryCall() and
7+
node1 =
8+
useQuery
9+
.getArgument(0)
10+
.getALocalSource()
11+
.getAPropertyWrite("queryFn")
12+
.getRhs()
13+
.getAFunctionValue()
14+
.getAReturn() and
15+
node2 = useQuery.getAPropertyRead("data")
16+
)
17+
}
18+
}
19+
20+
DataFlow::CallNode useQueryCall() {
21+
result = DataFlow::moduleImport("@tanstack/react-query").getAPropertyRead("useQuery").getACall()
22+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,24 @@
11
#select
2+
| test.jsx:25:29:25:32 | data | test.jsx:5:28:5:63 | fetch(" ... ntent") | test.jsx:25:29:25:32 | data | Cross-site scripting vulnerability due to $@. | test.jsx:5:28:5:63 | fetch(" ... ntent") | user-provided value |
23
edges
4+
| test.jsx:5:11:5:63 | response | test.jsx:6:24:6:31 | response | provenance | |
5+
| test.jsx:5:22:5:63 | await f ... ntent") | test.jsx:5:11:5:63 | response | provenance | |
6+
| test.jsx:5:28:5:63 | fetch(" ... ntent") | test.jsx:5:22:5:63 | await f ... ntent") | provenance | |
7+
| test.jsx:6:11:6:38 | data | test.jsx:7:12:7:15 | data | provenance | |
8+
| test.jsx:6:18:6:38 | await r ... .json() | test.jsx:6:11:6:38 | data | provenance | |
9+
| test.jsx:6:24:6:31 | response | test.jsx:6:24:6:38 | response.json() | provenance | |
10+
| test.jsx:6:24:6:38 | response.json() | test.jsx:6:18:6:38 | await r ... .json() | provenance | |
11+
| test.jsx:7:12:7:15 | data | test.jsx:11:11:15:5 | data | provenance | |
12+
| test.jsx:11:11:15:5 | data | test.jsx:25:29:25:32 | data | provenance | |
313
nodes
14+
| test.jsx:5:11:5:63 | response | semmle.label | response |
15+
| test.jsx:5:22:5:63 | await f ... ntent") | semmle.label | await f ... ntent") |
16+
| test.jsx:5:28:5:63 | fetch(" ... ntent") | semmle.label | fetch(" ... ntent") |
17+
| test.jsx:6:11:6:38 | data | semmle.label | data |
18+
| test.jsx:6:18:6:38 | await r ... .json() | semmle.label | await r ... .json() |
19+
| test.jsx:6:24:6:31 | response | semmle.label | response |
20+
| test.jsx:6:24:6:38 | response.json() | semmle.label | response.json() |
21+
| test.jsx:7:12:7:15 | data | semmle.label | data |
22+
| test.jsx:11:11:15:5 | data | semmle.label | data |
23+
| test.jsx:25:29:25:32 | data | semmle.label | data |
424
subpaths

javascript/ql/test/query-tests/Security/CWE-079/DomBasedXssWithResponseThreat/test.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from "react";
22
import { useQuery } from "@tanstack/react-query";
33

44
const fetchContent = async () => {
5-
const response = await fetch("https://example.com/content"); // $ MISSING: Source[js/xss]
5+
const response = await fetch("https://example.com/content"); // $ Source[js/xss]
66
const data = await response.json();
77
return data;
88
};
@@ -22,7 +22,7 @@ const ContentWithDangerousHtml = () => {
2222
<h1>Content with Dangerous HTML</h1>
2323
<div
2424
dangerouslySetInnerHTML={{
25-
__html: data, // $ MISSING: Alert[js/xss]
25+
__html: data, // $ Alert[js/xss]
2626
}}
2727
/>
2828
</div>

0 commit comments

Comments
 (0)