diff --git a/javascript/ql/lib/change-notes/2025-03-20-superagent.md b/javascript/ql/lib/change-notes/2025-03-20-superagent.md new file mode 100644 index 000000000000..6516d49d66df --- /dev/null +++ b/javascript/ql/lib/change-notes/2025-03-20-superagent.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Improved support for `superagent` to handle the case where the package is directly called as a function, or via the `.del()` or `.agent()` method. diff --git a/javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll b/javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll index 720d917d4985..d4508da39021 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll @@ -513,6 +513,13 @@ module ClientRequest { } } + /** + * Gets the name of a superagent request method. + */ + private string getSuperagentRequestMethodName() { + result = [httpMethodName(), any(Http::RequestMethodName m), "del", "DEL"] + } + /** * A model of a URL request made using the `superagent` library. */ @@ -520,10 +527,22 @@ module ClientRequest { DataFlow::Node url; SuperAgentUrlRequest() { - exists(string moduleName, DataFlow::SourceNode callee | this = callee.getACall() | - moduleName = "superagent" and - callee = DataFlow::moduleMember(moduleName, httpMethodName()) and + exists(string moduleName | moduleName = "superagent" | + // Handle method calls like superagent.get(url) + this = API::moduleImport(moduleName).getMember(getSuperagentRequestMethodName()).getACall() and url = this.getArgument(0) + or + // Handle direct calls like superagent('GET', url) + this = API::moduleImport(moduleName).getACall() and + this.getArgument(0).mayHaveStringValue(getSuperagentRequestMethodName()) and + url = this.getArgument(1) + or + // Handle agent calls like superagent.agent().get(url) + exists(DataFlow::SourceNode agent | + agent = API::moduleImport(moduleName).getMember("agent").getACall() and + this = agent.getAMethodCall(httpMethodName()) and + url = this.getArgument(0) + ) ) } diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected index 62a3b33c63fd..f9ab265e10d8 100644 --- a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected @@ -10,6 +10,9 @@ test_ClientRequest | puppeteer.ts:6:11:6:42 | page.go ... e.com') | | puppeteer.ts:8:5:8:61 | page.ad ... css" }) | | puppeteer.ts:18:30:18:50 | page.go ... estUrl) | +| superagent.js:4:5:4:26 | superag ... ', url) | +| superagent.js:5:5:5:23 | superagent.del(url) | +| superagent.js:6:5:6:32 | superag ... st(url) | | tst.js:11:5:11:16 | request(url) | | tst.js:13:5:13:20 | request.get(url) | | tst.js:15:5:15:23 | request.delete(url) | @@ -91,9 +94,13 @@ test_ClientRequest | tst.js:286:20:286:55 | new Web ... :8080') | | tst.js:296:5:299:6 | axios({ ... \\n }) | | tst.js:312:12:312:36 | fetchPo ... o/bar') | +| tst.js:319:5:319:26 | superag ... ', url) | +| tst.js:320:5:320:23 | superagent.del(url) | +| tst.js:321:5:321:32 | superag ... st(url) | test_getADataNode | axiosTest.js:12:5:17:6 | axios({ ... \\n }) | axiosTest.js:15:18:15:55 | { 'Cont ... json' } | | axiosTest.js:12:5:17:6 | axios({ ... \\n }) | axiosTest.js:16:15:16:35 | {x: 'te ... 'test'} | +| superagent.js:6:5:6:32 | superag ... st(url) | superagent.js:6:39:6:42 | data | | tst.js:53:5:53:23 | axios({data: data}) | tst.js:53:18:53:21 | data | | tst.js:57:5:57:39 | axios.p ... data2}) | tst.js:57:19:57:23 | data1 | | tst.js:57:5:57:39 | axios.p ... data2}) | tst.js:57:33:57:37 | data2 | @@ -132,6 +139,7 @@ test_getADataNode | tst.js:249:1:251:2 | form.su ... e();\\n}) | tst.js:247:24:247:68 | request ... o.png') | | tst.js:257:1:262:2 | form.su ... rs()\\n}) | tst.js:255:25:255:35 | 'new_value' | | tst.js:286:20:286:55 | new Web ... :8080') | tst.js:288:21:288:35 | 'Hello Server!' | +| tst.js:321:5:321:32 | superag ... st(url) | tst.js:321:39:321:42 | data | test_getHost | tst.js:87:5:87:39 | http.ge ... host}) | tst.js:87:34:87:37 | host | | tst.js:89:5:89:23 | axios({host: host}) | tst.js:89:18:89:21 | host | @@ -154,6 +162,9 @@ test_getUrl | puppeteer.ts:6:11:6:42 | page.go ... e.com') | puppeteer.ts:6:21:6:41 | 'https: ... le.com' | | puppeteer.ts:8:5:8:61 | page.ad ... css" }) | puppeteer.ts:8:29:8:58 | "http:/ ... le.css" | | puppeteer.ts:18:30:18:50 | page.go ... estUrl) | puppeteer.ts:18:40:18:49 | requestUrl | +| superagent.js:4:5:4:26 | superag ... ', url) | superagent.js:4:23:4:25 | url | +| superagent.js:5:5:5:23 | superagent.del(url) | superagent.js:5:20:5:22 | url | +| superagent.js:6:5:6:32 | superag ... st(url) | superagent.js:6:29:6:31 | url | | tst.js:11:5:11:16 | request(url) | tst.js:11:13:11:15 | url | | tst.js:13:5:13:20 | request.get(url) | tst.js:13:17:13:19 | url | | tst.js:15:5:15:23 | request.delete(url) | tst.js:15:20:15:22 | url | @@ -240,9 +251,15 @@ test_getUrl | tst.js:296:5:299:6 | axios({ ... \\n }) | tst.js:296:11:299:5 | {\\n ... ,\\n } | | tst.js:296:5:299:6 | axios({ ... \\n }) | tst.js:298:14:298:44 | "http:/ ... -axios" | | tst.js:312:12:312:36 | fetchPo ... o/bar') | tst.js:312:26:312:35 | '/foo/bar' | +| tst.js:319:5:319:26 | superag ... ', url) | tst.js:319:23:319:25 | url | +| tst.js:320:5:320:23 | superagent.del(url) | tst.js:320:20:320:22 | url | +| tst.js:321:5:321:32 | superag ... st(url) | tst.js:321:29:321:31 | url | test_getAResponseDataNode | axiosTest.js:4:5:7:6 | axios({ ... \\n }) | axiosTest.js:4:5:7:6 | axios({ ... \\n }) | json | true | | axiosTest.js:12:5:17:6 | axios({ ... \\n }) | axiosTest.js:12:5:17:6 | axios({ ... \\n }) | json | true | +| superagent.js:4:5:4:26 | superag ... ', url) | superagent.js:4:5:4:26 | superag ... ', url) | stream | true | +| superagent.js:5:5:5:23 | superagent.del(url) | superagent.js:5:5:5:23 | superagent.del(url) | stream | true | +| superagent.js:6:5:6:32 | superag ... st(url) | superagent.js:6:5:6:32 | superag ... st(url) | stream | true | | tst.js:19:5:19:23 | requestPromise(url) | tst.js:19:5:19:23 | requestPromise(url) | text | true | | tst.js:21:5:21:23 | superagent.get(url) | tst.js:21:5:21:23 | superagent.get(url) | stream | true | | tst.js:25:5:25:14 | axios(url) | tst.js:25:5:25:14 | axios(url) | | true | @@ -314,3 +331,6 @@ test_getAResponseDataNode | tst.js:296:5:299:6 | axios({ ... \\n }) | tst.js:303:26:303:37 | err.response | json | false | | tst.js:296:5:299:6 | axios({ ... \\n }) | tst.js:304:27:304:38 | err.response | json | false | | tst.js:312:12:312:36 | fetchPo ... o/bar') | tst.js:312:12:312:36 | fetchPo ... o/bar') | fetch.response | true | +| tst.js:319:5:319:26 | superag ... ', url) | tst.js:319:5:319:26 | superag ... ', url) | stream | true | +| tst.js:320:5:320:23 | superagent.del(url) | tst.js:320:5:320:23 | superagent.del(url) | stream | true | +| tst.js:321:5:321:32 | superag ... st(url) | tst.js:321:5:321:32 | superag ... st(url) | stream | true | diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/superagent.js b/javascript/ql/test/library-tests/frameworks/ClientRequests/superagent.js new file mode 100644 index 000000000000..b96c4fa45df8 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/superagent.js @@ -0,0 +1,7 @@ +import { superagent } from "./superagentWrapper.js"; + +function test(url) { + superagent('GET', url); + superagent.del(url); + superagent.agent().post(url).send(data); +} diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/superagentWrapper.js b/javascript/ql/test/library-tests/frameworks/ClientRequests/superagentWrapper.js new file mode 100644 index 000000000000..58c47db2fffc --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/superagentWrapper.js @@ -0,0 +1,2 @@ +import superagent from 'superagent'; +export { superagent } diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js b/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js index b2b9d8256ca3..48c7d7786234 100644 --- a/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js @@ -314,3 +314,9 @@ function usePolyfill() { return response.text() }) } + +function useSuperagent(url){ + superagent('GET', url); + superagent.del(url); + superagent.agent().post(url).send(data); +}