From a572ac60d28c8a2ab1c843fcd2c2e4025e31d87c Mon Sep 17 00:00:00 2001 From: Napalys Date: Thu, 3 Apr 2025 18:31:27 +0200 Subject: [PATCH 01/13] Added inline test expectations for `WebSocket` --- .../frameworks/WebSocket/browser.js | 18 +++++----- .../frameworks/WebSocket/client.js | 8 ++--- .../frameworks/WebSocket/server.js | 10 +++--- .../frameworks/WebSocket/sockjs.js | 8 ++--- .../frameworks/WebSocket/test.expected | 34 +++++++++---------- .../frameworks/WebSocket/test.qlref | 2 ++ 6 files changed, 41 insertions(+), 39 deletions(-) create mode 100644 javascript/ql/test/library-tests/frameworks/WebSocket/test.qlref diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/browser.js b/javascript/ql/test/library-tests/frameworks/WebSocket/browser.js index e138d397c7f0..7e15bb532c50 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/browser.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/browser.js @@ -1,32 +1,32 @@ (function () { - const socket = new WebSocket('ws://localhost:8080'); + const socket = new WebSocket('ws://localhost:8080'); // $clientSocket socket.addEventListener('open', function (event) { - socket.send('Hi from browser!'); + socket.send('Hi from browser!'); // $clientSend }); socket.addEventListener('message', function (event) { console.log('Message from server ', event.data); - }); + }); // $clientReceive socket.onmessage = function (event) { console.log("Message from server 2", event.data) - }; + }; // $clientReceive })(); (function () { - var sock = new SockJS('http://0.0.0.0:9999/echo'); + var sock = new SockJS('http://0.0.0.0:9999/echo'); // $clientSocket sock.onopen = function () { - sock.send('test'); + sock.send('test'); // $clientSend }; sock.onmessage = function (e) { console.log('message', e.data); sock.close(); - }; + }; // $clientReceive sock.addEventListener('message', function (event) { console.log('Using addEventListener ', event.data); - }); -}) \ No newline at end of file + }); // $clientReceive +})(); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/client.js b/javascript/ql/test/library-tests/frameworks/WebSocket/client.js index c98c71973bf6..c49df5afc3c2 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/client.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/client.js @@ -1,13 +1,13 @@ (function () { const WebSocket = require('ws'); - const ws = new WebSocket('ws://example.org'); + const ws = new WebSocket('ws://example.org'); // $clientSocket ws.on('open', function open() { - ws.send('Hi from client!'); + ws.send('Hi from client!'); // $clientSend }); ws.on('message', function incoming(data) { console.log(data); - }); -})(); \ No newline at end of file + }); // $clientReceive +})(); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/server.js b/javascript/ql/test/library-tests/frameworks/WebSocket/server.js index d135f652b459..2f9173ecfc9d 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/server.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/server.js @@ -3,11 +3,11 @@ const wss = new WebSocket.Server({ port: 8080 }); - wss.on('connection', function connection(ws) { - ws.on('message', function incoming(message) { + wss.on('connection', function connection(ws) { // $serverSocket + ws.on('message', function incoming(message) { // $remoteFlow console.log('received: %s', message); - }); + }); // $serverReceive - ws.send('Hi from server!'); + ws.send('Hi from server!'); // $serverSend }); -})(); \ No newline at end of file +})(); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/sockjs.js b/javascript/ql/test/library-tests/frameworks/WebSocket/sockjs.js index 3616caf90fd9..4b599cf5fede 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/sockjs.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/sockjs.js @@ -5,11 +5,11 @@ const sockjs = require('sockjs'); const app = express(); const server = http.createServer(app); const sockjs_echo = sockjs.createServer({}); -sockjs_echo.on('connection', function (conn) { - conn.on('data', function (message) { +sockjs_echo.on('connection', function (conn) { // $serverSocket + conn.on('data', function (message) { // $remoteFlow var data = JSON.parse(message); - conn.write(JSON.stringify(eval(data.test))); - }); + conn.write(JSON.stringify(eval(data.test))); // $serverSend + }); // $serverReceive }); sockjs_echo.installHandlers(server, { prefix: '/echo' }); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected b/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected index 96c5aedb737a..0ea8e8b690be 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected @@ -1,26 +1,17 @@ -clientSocket -| browser.js:2:17:2:52 | new Web ... :8080') | -| browser.js:19:13:19:50 | new Soc ... /echo') | -| client.js:4:13:4:45 | new Web ... e.org') | -clientSend -| browser.js:5:3:5:33 | socket. ... wser!') | -| browser.js:21:3:21:19 | sock.send('test') | -| client.js:7:3:7:28 | ws.send ... ient!') | clientReceive | browser.js:8:37:10:2 | functio ... ta);\\n\\t} | | browser.js:12:21:14:2 | functio ... ata)\\n\\t} | | browser.js:24:19:27:2 | functio ... e();\\n\\t} | | browser.js:29:35:31:2 | functio ... ta);\\n\\t} | | client.js:10:19:12:2 | functio ... ta);\\n\\t} | -serverSocket -| server.js:6:43:6:44 | ws | -| sockjs.js:8:40:8:43 | conn | -serverSend -| server.js:11:3:11:28 | ws.send ... rver!') | -| sockjs.js:11:9:11:51 | conn.wr ... test))) | -serverReceive -| server.js:7:3:9:4 | ws.on(' ... );\\n\\t\\t}) | -| sockjs.js:9:5:12:6 | conn.on ... \\n }) | +clientSend +| browser.js:5:3:5:33 | socket. ... wser!') | +| browser.js:21:3:21:19 | sock.send('test') | +| client.js:7:3:7:28 | ws.send ... ient!') | +clientSocket +| browser.js:2:17:2:52 | new Web ... :8080') | +| browser.js:19:13:19:50 | new Soc ... /echo') | +| client.js:4:13:4:45 | new Web ... e.org') | flowSteps | browser.js:5:15:5:32 | 'Hi from browser!' | server.js:7:38:7:44 | message | | browser.js:21:13:21:18 | 'test' | sockjs.js:9:31:9:37 | message | @@ -33,3 +24,12 @@ flowSteps remoteFlow | server.js:7:38:7:44 | message | | sockjs.js:9:31:9:37 | message | +serverReceive +| server.js:7:3:9:4 | ws.on(' ... );\\n\\t\\t}) | +| sockjs.js:9:5:12:6 | conn.on ... \\n }) | +serverSend +| server.js:11:3:11:28 | ws.send ... rver!') | +| sockjs.js:11:9:11:51 | conn.wr ... test))) | +serverSocket +| server.js:6:43:6:44 | ws | +| sockjs.js:8:40:8:43 | conn | diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/test.qlref b/javascript/ql/test/library-tests/frameworks/WebSocket/test.qlref new file mode 100644 index 000000000000..ab6773f15f90 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/test.qlref @@ -0,0 +1,2 @@ +query: test.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql From c7fad0966478a9ed43591bbde553e6f79c2f177f Mon Sep 17 00:00:00 2001 From: Napalys Date: Fri, 4 Apr 2025 08:33:26 +0200 Subject: [PATCH 02/13] Added test cases with custom exports/imports. --- .../frameworks/WebSocket/browser-custom.js | 34 +++++++++++++++++++ .../frameworks/WebSocket/browser.js | 3 ++ .../frameworks/WebSocket/client-custom.js | 13 +++++++ .../frameworks/WebSocket/client.js | 2 ++ .../frameworks/WebSocket/server-custom.js | 13 +++++++ .../frameworks/WebSocket/server.js | 2 ++ .../frameworks/WebSocket/test.expected | 3 ++ 7 files changed, 70 insertions(+) create mode 100644 javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js create mode 100644 javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js create mode 100644 javascript/ql/test/library-tests/frameworks/WebSocket/server-custom.js diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js b/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js new file mode 100644 index 000000000000..f14f1d19fa6c --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js @@ -0,0 +1,34 @@ +import { MyWebSocket, MySockJS } from './browser.js'; + +(function () { + const socket = new MyWebSocket('ws://localhost:9080'); // $ MISSING: clientSocket + + socket.addEventListener('open', function (event) { + socket.send('Hi from browser!'); // $ MISSING: clientSend + }); + + socket.addEventListener('message', function (event) { + console.log('Message from server ', event.data); + }); // $ MISSING: clientReceive + + socket.onmessage = function (event) { + console.log("Message from server 2", event.data) + }; // $ MISSING: clientReceive +})(); + + +(function () { + var sock = new MySockJS('http://0.0.0.0:9999/echo'); // $ MISSING: clientSocket + sock.onopen = function () { + sock.send('test'); // $ MISSING: clientSend + }; + + sock.onmessage = function (e) { + console.log('message', e.data); + sock.close(); + }; // $ MISSING: clientReceive + + sock.addEventListener('message', function (event) { + console.log('Using addEventListener ', event.data); + }); // $ MISSING: clientReceive +})(); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/browser.js b/javascript/ql/test/library-tests/frameworks/WebSocket/browser.js index 7e15bb532c50..2540abaf3e7b 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/browser.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/browser.js @@ -30,3 +30,6 @@ console.log('Using addEventListener ', event.data); }); // $clientReceive })(); + +export const MyWebSocket = WebSocket; +export const MySockJS = SockJS; diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js b/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js new file mode 100644 index 000000000000..92db7ac339cf --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js @@ -0,0 +1,13 @@ +const { MyWebSocketWS } = require('./client.js'); + +(function () { + const ws = new MyWebSocketWS('ws://example.org'); // $ MISSING: clientSocket + + ws.on('open', function open() { + ws.send('Hi from client!'); // $ MISSING: clientSend + }); + + ws.on('message', function incoming(data) { + console.log(data); + }); // $ MISSING: clientReceive +})(); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/client.js b/javascript/ql/test/library-tests/frameworks/WebSocket/client.js index c49df5afc3c2..710bf0aa7da6 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/client.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/client.js @@ -11,3 +11,5 @@ console.log(data); }); // $clientReceive })(); + +module.exports.MyWebSocketWS = require('ws'); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/server-custom.js b/javascript/ql/test/library-tests/frameworks/WebSocket/server-custom.js new file mode 100644 index 000000000000..e01bd38d57b3 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/server-custom.js @@ -0,0 +1,13 @@ +const { MyWebSocketServer } = require('./client.js'); + +(function () { + const wss = new MyWebSocketServer({ port: 8080 }); + + wss.on('connection', function connection(ws) { // $ MISSING: serverSocket + ws.on('message', function incoming(message) { // $ MISSING: remoteFlow + console.log('received: %s', message); + }); // $ MISSING: serverReceive + + ws.send('Hi from server!'); // $ MISSING: serverSend + }); +})(); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/server.js b/javascript/ql/test/library-tests/frameworks/WebSocket/server.js index 2f9173ecfc9d..3e1a408a4a40 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/server.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/server.js @@ -11,3 +11,5 @@ ws.send('Hi from server!'); // $serverSend }); })(); + +module.exports.MyWebSocketServer = require('ws').Server; diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected b/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected index 0ea8e8b690be..93068e683b48 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected @@ -13,9 +13,12 @@ clientSocket | browser.js:19:13:19:50 | new Soc ... /echo') | | client.js:4:13:4:45 | new Web ... e.org') | flowSteps +| browser-custom.js:1:10:1:20 | MyWebSocket | browser-custom.js:1:10:1:20 | MyWebSocket | +| browser-custom.js:1:23:1:30 | MySockJS | browser-custom.js:1:23:1:30 | MySockJS | | browser.js:5:15:5:32 | 'Hi from browser!' | server.js:7:38:7:44 | message | | browser.js:21:13:21:18 | 'test' | sockjs.js:9:31:9:37 | message | | client.js:7:11:7:27 | 'Hi from client!' | server.js:7:38:7:44 | message | +| client.js:15:32:15:44 | require('ws') | client-custom.js:1:9:1:21 | MyWebSocketWS | | server.js:11:11:11:27 | 'Hi from server!' | browser.js:9:39:9:48 | event.data | | server.js:11:11:11:27 | 'Hi from server!' | browser.js:13:40:13:49 | event.data | | server.js:11:11:11:27 | 'Hi from server!' | client.js:10:37:10:40 | data | From e16a20e69f4d9c59955de194da5cdeb925b5b860 Mon Sep 17 00:00:00 2001 From: Napalys Date: Fri, 4 Apr 2025 08:41:47 +0200 Subject: [PATCH 03/13] Updated `SocketClass` to use API Graphs. --- .../javascript/frameworks/WebSocket.qll | 24 +++++++++++++++---- javascript/ql/src/Security/trest/test.ql | 10 ++++++++ .../frameworks/WebSocket/browser-custom.js | 16 ++++++------- .../frameworks/WebSocket/client-custom.js | 6 ++--- .../frameworks/WebSocket/server-custom.js | 2 +- .../frameworks/WebSocket/test.expected | 19 +++++++++++++++ 6 files changed, 60 insertions(+), 17 deletions(-) create mode 100644 javascript/ql/src/Security/trest/test.ql diff --git a/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll b/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll index d2fc69751d6c..7437e68bda8e 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll @@ -47,6 +47,20 @@ private predicate areLibrariesCompatible( (client = LibraryNames::ws() or client = LibraryNames::websocket()) } +/** Treats `WebSocket` as an entry point for API graphs. */ +private class WebSocketEntryPoint extends API::EntryPoint { + WebSocketEntryPoint() { this = "global.WebSocket" } + + override DataFlow::SourceNode getASource() { result = DataFlow::globalVarRef("WebSocket") } +} + +/** Treats `SockJS` as an entry point for API graphs. */ +private class SockJSEntryPoint extends API::EntryPoint { + SockJSEntryPoint() { this = "global.SockJS" } + + override DataFlow::SourceNode getASource() { result = DataFlow::globalVarRef("SockJS") } +} + /** * Provides classes that model WebSockets clients. */ @@ -56,19 +70,19 @@ module ClientWebSocket { /** * A class that can be used to instantiate a WebSocket instance. */ - class SocketClass extends DataFlow::SourceNode { + class SocketClass extends API::Node { LibraryName library; // the name of the WebSocket library. Can be one of the libraries defined in `LibraryNames`. SocketClass() { - this = DataFlow::globalVarRef("WebSocket") and library = websocket() + this = any(WebSocketEntryPoint e).getANode() and library = websocket() or - this = DataFlow::moduleImport("ws") and library = ws() + this = API::moduleImport("ws") and library = ws() or // the sockjs-client library:https://www.npmjs.com/package/sockjs-client library = sockjs() and ( - this = DataFlow::moduleImport("sockjs-client") or - this = DataFlow::globalVarRef("SockJS") + this = API::moduleImport("sockjs-client") or + this = any(SockJSEntryPoint e).getANode() ) } diff --git a/javascript/ql/src/Security/trest/test.ql b/javascript/ql/src/Security/trest/test.ql new file mode 100644 index 000000000000..076271d9fb6a --- /dev/null +++ b/javascript/ql/src/Security/trest/test.ql @@ -0,0 +1,10 @@ +import javascript + +API::NewNode getAWebSocketInstance() { result instanceof ClientWebSocket::ClientSocket } + +from DataFlow::Node handler +where + handler = getAWebSocketInstance().getReturn().getMember("onmessage").asSource() + or + handler = getAWebSocketInstance().getAPropertyWrite("onmessage").getRhs() +select handler, "This is a WebSocket onmessage handler." diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js b/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js index f14f1d19fa6c..d3a739697d14 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js @@ -1,34 +1,34 @@ import { MyWebSocket, MySockJS } from './browser.js'; (function () { - const socket = new MyWebSocket('ws://localhost:9080'); // $ MISSING: clientSocket + const socket = new MyWebSocket('ws://localhost:9080'); // $ clientSocket socket.addEventListener('open', function (event) { - socket.send('Hi from browser!'); // $ MISSING: clientSend + socket.send('Hi from browser!'); // $ clientSend }); socket.addEventListener('message', function (event) { console.log('Message from server ', event.data); - }); // $ MISSING: clientReceive + }); // $ clientReceive socket.onmessage = function (event) { console.log("Message from server 2", event.data) - }; // $ MISSING: clientReceive + }; // $ clientReceive })(); (function () { - var sock = new MySockJS('http://0.0.0.0:9999/echo'); // $ MISSING: clientSocket + var sock = new MySockJS('http://0.0.0.0:9999/echo'); // $ clientSocket sock.onopen = function () { - sock.send('test'); // $ MISSING: clientSend + sock.send('test'); // $ clientSend }; sock.onmessage = function (e) { console.log('message', e.data); sock.close(); - }; // $ MISSING: clientReceive + }; // $ clientReceive sock.addEventListener('message', function (event) { console.log('Using addEventListener ', event.data); - }); // $ MISSING: clientReceive + }); // $ clientReceive })(); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js b/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js index 92db7ac339cf..6aefeb3a07f5 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js @@ -1,13 +1,13 @@ const { MyWebSocketWS } = require('./client.js'); (function () { - const ws = new MyWebSocketWS('ws://example.org'); // $ MISSING: clientSocket + const ws = new MyWebSocketWS('ws://example.org'); // $ clientSocket ws.on('open', function open() { - ws.send('Hi from client!'); // $ MISSING: clientSend + ws.send('Hi from client!'); // $ clientSend }); ws.on('message', function incoming(data) { console.log(data); - }); // $ MISSING: clientReceive + }); // $ clientReceive })(); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/server-custom.js b/javascript/ql/test/library-tests/frameworks/WebSocket/server-custom.js index e01bd38d57b3..6911d8f8523b 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/server-custom.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/server-custom.js @@ -1,4 +1,4 @@ -const { MyWebSocketServer } = require('./client.js'); +const { MyWebSocketServer } = require('./server.js'); (function () { const wss = new MyWebSocketServer({ port: 8080 }); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected b/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected index 93068e683b48..fb5fe14793cd 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected @@ -1,27 +1,46 @@ clientReceive +| browser-custom.js:10:37:12:2 | functio ... ta);\\n\\t} | +| browser-custom.js:14:21:16:2 | functio ... ata)\\n\\t} | +| browser-custom.js:26:19:29:2 | functio ... e();\\n\\t} | +| browser-custom.js:31:35:33:2 | functio ... ta);\\n\\t} | | browser.js:8:37:10:2 | functio ... ta);\\n\\t} | | browser.js:12:21:14:2 | functio ... ata)\\n\\t} | | browser.js:24:19:27:2 | functio ... e();\\n\\t} | | browser.js:29:35:31:2 | functio ... ta);\\n\\t} | +| client-custom.js:10:19:12:2 | functio ... ta);\\n\\t} | | client.js:10:19:12:2 | functio ... ta);\\n\\t} | clientSend +| browser-custom.js:7:3:7:33 | socket. ... wser!') | +| browser-custom.js:23:3:23:19 | sock.send('test') | | browser.js:5:3:5:33 | socket. ... wser!') | | browser.js:21:3:21:19 | sock.send('test') | +| client-custom.js:7:3:7:28 | ws.send ... ient!') | | client.js:7:3:7:28 | ws.send ... ient!') | clientSocket +| browser-custom.js:4:17:4:54 | new MyW ... :9080') | +| browser-custom.js:21:13:21:52 | new MyS ... /echo') | | browser.js:2:17:2:52 | new Web ... :8080') | | browser.js:19:13:19:50 | new Soc ... /echo') | +| client-custom.js:4:13:4:49 | new MyW ... e.org') | | client.js:4:13:4:45 | new Web ... e.org') | flowSteps | browser-custom.js:1:10:1:20 | MyWebSocket | browser-custom.js:1:10:1:20 | MyWebSocket | | browser-custom.js:1:23:1:30 | MySockJS | browser-custom.js:1:23:1:30 | MySockJS | +| browser-custom.js:7:15:7:32 | 'Hi from browser!' | server.js:7:38:7:44 | message | +| browser-custom.js:23:13:23:18 | 'test' | sockjs.js:9:31:9:37 | message | | browser.js:5:15:5:32 | 'Hi from browser!' | server.js:7:38:7:44 | message | | browser.js:21:13:21:18 | 'test' | sockjs.js:9:31:9:37 | message | +| client-custom.js:7:11:7:27 | 'Hi from client!' | server.js:7:38:7:44 | message | | client.js:7:11:7:27 | 'Hi from client!' | server.js:7:38:7:44 | message | | client.js:15:32:15:44 | require('ws') | client-custom.js:1:9:1:21 | MyWebSocketWS | +| server.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:11:39:11:48 | event.data | +| server.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:15:40:15:49 | event.data | | server.js:11:11:11:27 | 'Hi from server!' | browser.js:9:39:9:48 | event.data | | server.js:11:11:11:27 | 'Hi from server!' | browser.js:13:40:13:49 | event.data | +| server.js:11:11:11:27 | 'Hi from server!' | client-custom.js:10:37:10:40 | data | | server.js:11:11:11:27 | 'Hi from server!' | client.js:10:37:10:40 | data | +| sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser-custom.js:27:26:27:31 | e.data | +| sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser-custom.js:32:42:32:51 | event.data | | sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser.js:25:26:25:31 | e.data | | sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser.js:30:42:30:51 | event.data | remoteFlow From 455ce595835c963165d090956574322f5bf6d88b Mon Sep 17 00:00:00 2001 From: Napalys Date: Fri, 4 Apr 2025 08:59:19 +0200 Subject: [PATCH 04/13] Added test cases with export of an instance. --- .../frameworks/WebSocket/browser-custom.js | 33 ++++++++++++++++++- .../frameworks/WebSocket/browser.js | 2 ++ .../frameworks/WebSocket/client-custom.js | 12 ++++++- .../frameworks/WebSocket/client.js | 5 +-- .../frameworks/WebSocket/server-custom.js | 12 ++++++- .../frameworks/WebSocket/server.js | 5 +-- .../frameworks/WebSocket/test.expected | 8 +++++ 7 files changed, 70 insertions(+), 7 deletions(-) diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js b/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js index d3a739697d14..c411a90f35a5 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js @@ -1,4 +1,4 @@ -import { MyWebSocket, MySockJS } from './browser.js'; +import { MyWebSocket, MySockJS, myWebSocketInstance, mySockJSInstance } from './browser.js'; (function () { const socket = new MyWebSocket('ws://localhost:9080'); // $ clientSocket @@ -32,3 +32,34 @@ import { MyWebSocket, MySockJS } from './browser.js'; console.log('Using addEventListener ', event.data); }); // $ clientReceive })(); + + +(function () { + myWebSocketInstance.addEventListener('open', function (event) { + myWebSocketInstance.send('Hi from browser!'); // $ MISSING: clientSend + }); + + myWebSocketInstance.addEventListener('message', function (event) { + console.log('Message from server ', event.data); + }); // $ MISSING: clientReceive + + myWebSocketInstance.onmessage = function (event) { + console.log("Message from server 2", event.data) + }; // $ MISSING: clientReceive +})(); + + +(function () { + mySockJSInstance.onopen = function () { + mySockJSInstance.send('test'); // $ MISSING: clientSend + }; + + mySockJSInstance.onmessage = function (e) { + console.log('message', e.data); + mySockJSInstance.close(); + }; // $ MISSING: clientReceive + + mySockJSInstance.addEventListener('message', function (event) { + console.log('Using addEventListener ', event.data); + }); // $ MISSING: clientReceive +})(); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/browser.js b/javascript/ql/test/library-tests/frameworks/WebSocket/browser.js index 2540abaf3e7b..f966879f9f25 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/browser.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/browser.js @@ -33,3 +33,5 @@ export const MyWebSocket = WebSocket; export const MySockJS = SockJS; +export const myWebSocketInstance = new WebSocket('ws://localhost:8080'); // $ clientSocket +export const mySockJSInstance = new SockJS('http://0.0.0.0:9999/echo'); // $ clientSocket diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js b/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js index 6aefeb3a07f5..9718bd6185cf 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js @@ -1,4 +1,4 @@ -const { MyWebSocketWS } = require('./client.js'); +const { MyWebSocketWS, myWebSocketWSInstance } = require('./client.js'); (function () { const ws = new MyWebSocketWS('ws://example.org'); // $ clientSocket @@ -11,3 +11,13 @@ const { MyWebSocketWS } = require('./client.js'); console.log(data); }); // $ clientReceive })(); + +(function () { + myWebSocketWSInstance.on('open', function open() { + myWebSocketWSInstance.send('Hi from client!'); // $ MISSING: clientSend + }); + + myWebSocketWSInstance.on('message', function incoming(data) { + console.log(data); + }); // $ MISSING: clientReceive +})(); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/client.js b/javascript/ql/test/library-tests/frameworks/WebSocket/client.js index 710bf0aa7da6..54a200f33793 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/client.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/client.js @@ -1,6 +1,6 @@ -(function () { - const WebSocket = require('ws'); +const WebSocket = require('ws'); +(function () { const ws = new WebSocket('ws://example.org'); // $clientSocket ws.on('open', function open() { @@ -13,3 +13,4 @@ })(); module.exports.MyWebSocketWS = require('ws'); +module.exports.myWebSocketWSInstance = new WebSocket('ws://example.org'); // $ clientSocket diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/server-custom.js b/javascript/ql/test/library-tests/frameworks/WebSocket/server-custom.js index 6911d8f8523b..63893ba2f5ca 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/server-custom.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/server-custom.js @@ -1,4 +1,4 @@ -const { MyWebSocketServer } = require('./server.js'); +const { MyWebSocketServer, myWebSocketServerInstance } = require('./server.js'); (function () { const wss = new MyWebSocketServer({ port: 8080 }); @@ -11,3 +11,13 @@ const { MyWebSocketServer } = require('./server.js'); ws.send('Hi from server!'); // $ MISSING: serverSend }); })(); + +(function () { + myWebSocketServerInstance.on('connection', function connection(ws) { // $ MISSING: serverSocket + ws.on('message', function incoming(message) { // $ MISSING: remoteFlow + console.log('received: %s', message); + }); // $ MISSING: serverReceive + + ws.send('Hi from server!'); // $ MISSING: serverSend + }); +})(); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/server.js b/javascript/ql/test/library-tests/frameworks/WebSocket/server.js index 3e1a408a4a40..a1fd536501ea 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/server.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/server.js @@ -1,6 +1,6 @@ -(function () { - const WebSocket = require('ws'); +const WebSocket = require('ws'); +(function () { const wss = new WebSocket.Server({ port: 8080 }); wss.on('connection', function connection(ws) { // $serverSocket @@ -13,3 +13,4 @@ })(); module.exports.MyWebSocketServer = require('ws').Server; +module.exports.myWebSocketServerInstance = new WebSocket.Server({ port: 8080 }); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected b/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected index fb5fe14793cd..21e9f1aae64c 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected @@ -21,11 +21,16 @@ clientSocket | browser-custom.js:21:13:21:52 | new MyS ... /echo') | | browser.js:2:17:2:52 | new Web ... :8080') | | browser.js:19:13:19:50 | new Soc ... /echo') | +| browser.js:36:36:36:71 | new Web ... :8080') | +| browser.js:37:33:37:70 | new Soc ... /echo') | | client-custom.js:4:13:4:49 | new MyW ... e.org') | | client.js:4:13:4:45 | new Web ... e.org') | +| client.js:16:40:16:72 | new Web ... e.org') | flowSteps | browser-custom.js:1:10:1:20 | MyWebSocket | browser-custom.js:1:10:1:20 | MyWebSocket | | browser-custom.js:1:23:1:30 | MySockJS | browser-custom.js:1:23:1:30 | MySockJS | +| browser-custom.js:1:33:1:51 | myWebSocketInstance | browser-custom.js:1:33:1:51 | myWebSocketInstance | +| browser-custom.js:1:54:1:69 | mySockJSInstance | browser-custom.js:1:54:1:69 | mySockJSInstance | | browser-custom.js:7:15:7:32 | 'Hi from browser!' | server.js:7:38:7:44 | message | | browser-custom.js:23:13:23:18 | 'test' | sockjs.js:9:31:9:37 | message | | browser.js:5:15:5:32 | 'Hi from browser!' | server.js:7:38:7:44 | message | @@ -33,12 +38,15 @@ flowSteps | client-custom.js:7:11:7:27 | 'Hi from client!' | server.js:7:38:7:44 | message | | client.js:7:11:7:27 | 'Hi from client!' | server.js:7:38:7:44 | message | | client.js:15:32:15:44 | require('ws') | client-custom.js:1:9:1:21 | MyWebSocketWS | +| client.js:16:40:16:72 | new Web ... e.org') | client-custom.js:1:24:1:44 | myWebSo ... nstance | | server.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:11:39:11:48 | event.data | | server.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:15:40:15:49 | event.data | | server.js:11:11:11:27 | 'Hi from server!' | browser.js:9:39:9:48 | event.data | | server.js:11:11:11:27 | 'Hi from server!' | browser.js:13:40:13:49 | event.data | | server.js:11:11:11:27 | 'Hi from server!' | client-custom.js:10:37:10:40 | data | | server.js:11:11:11:27 | 'Hi from server!' | client.js:10:37:10:40 | data | +| server.js:15:36:15:55 | require('ws').Server | server-custom.js:1:9:1:25 | MyWebSocketServer | +| server.js:16:44:16:79 | new Web ... 8080 }) | server-custom.js:1:28:1:52 | myWebSo ... nstance | | sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser-custom.js:27:26:27:31 | e.data | | sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser-custom.js:32:42:32:51 | event.data | | sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser.js:25:26:25:31 | e.data | From 0dbf9512911f4299050816a5894d25742bb7b48e Mon Sep 17 00:00:00 2001 From: Napalys Date: Fri, 4 Apr 2025 09:12:42 +0200 Subject: [PATCH 05/13] Updated `ClientSocket` and `SendNode` with API graphs. --- .../ql/lib/semmle/javascript/frameworks/WebSocket.qll | 8 ++++---- .../library-tests/frameworks/WebSocket/browser-custom.js | 4 ++-- .../library-tests/frameworks/WebSocket/client-custom.js | 2 +- .../test/library-tests/frameworks/WebSocket/test.expected | 6 ++++++ 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll b/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll index 7437e68bda8e..09c9f98e3f02 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll @@ -95,10 +95,10 @@ module ClientWebSocket { /** * A client WebSocket instance. */ - class ClientSocket extends EventEmitter::Range, DataFlow::NewNode, ClientRequest::Range { + class ClientSocket extends EventEmitter::Range, API::NewNode, ClientRequest::Range { SocketClass socketClass; - ClientSocket() { this = socketClass.getAnInstantiation() } + ClientSocket() { this = socketClass.getAnInvocation() } /** * Gets the WebSocket library name. @@ -129,10 +129,10 @@ module ClientWebSocket { /** * A message sent from a WebSocket client. */ - class SendNode extends EventDispatch::Range, DataFlow::CallNode { + class SendNode extends EventDispatch::Range, API::CallNode { override ClientSocket emitter; - SendNode() { this = emitter.getAMemberCall("send") } + SendNode() { this = emitter.getReturn().getMember("send").getACall() } override string getChannel() { result = channelName() } diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js b/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js index c411a90f35a5..11765138f55c 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js @@ -36,7 +36,7 @@ import { MyWebSocket, MySockJS, myWebSocketInstance, mySockJSInstance } from './ (function () { myWebSocketInstance.addEventListener('open', function (event) { - myWebSocketInstance.send('Hi from browser!'); // $ MISSING: clientSend + myWebSocketInstance.send('Hi from browser!'); // $ clientSend }); myWebSocketInstance.addEventListener('message', function (event) { @@ -51,7 +51,7 @@ import { MyWebSocket, MySockJS, myWebSocketInstance, mySockJSInstance } from './ (function () { mySockJSInstance.onopen = function () { - mySockJSInstance.send('test'); // $ MISSING: clientSend + mySockJSInstance.send('test'); // $ clientSend }; mySockJSInstance.onmessage = function (e) { diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js b/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js index 9718bd6185cf..5d7ac4b564d8 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js @@ -14,7 +14,7 @@ const { MyWebSocketWS, myWebSocketWSInstance } = require('./client.js'); (function () { myWebSocketWSInstance.on('open', function open() { - myWebSocketWSInstance.send('Hi from client!'); // $ MISSING: clientSend + myWebSocketWSInstance.send('Hi from client!'); // $ clientSend }); myWebSocketWSInstance.on('message', function incoming(data) { diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected b/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected index 21e9f1aae64c..4d7e33f54022 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected @@ -12,9 +12,12 @@ clientReceive clientSend | browser-custom.js:7:3:7:33 | socket. ... wser!') | | browser-custom.js:23:3:23:19 | sock.send('test') | +| browser-custom.js:39:9:39:52 | myWebSo ... wser!') | +| browser-custom.js:54:9:54:37 | mySockJ ... 'test') | | browser.js:5:3:5:33 | socket. ... wser!') | | browser.js:21:3:21:19 | sock.send('test') | | client-custom.js:7:3:7:28 | ws.send ... ient!') | +| client-custom.js:17:3:17:47 | myWebSo ... ient!') | | client.js:7:3:7:28 | ws.send ... ient!') | clientSocket | browser-custom.js:4:17:4:54 | new MyW ... :9080') | @@ -33,9 +36,12 @@ flowSteps | browser-custom.js:1:54:1:69 | mySockJSInstance | browser-custom.js:1:54:1:69 | mySockJSInstance | | browser-custom.js:7:15:7:32 | 'Hi from browser!' | server.js:7:38:7:44 | message | | browser-custom.js:23:13:23:18 | 'test' | sockjs.js:9:31:9:37 | message | +| browser-custom.js:39:34:39:51 | 'Hi from browser!' | server.js:7:38:7:44 | message | +| browser-custom.js:54:31:54:36 | 'test' | sockjs.js:9:31:9:37 | message | | browser.js:5:15:5:32 | 'Hi from browser!' | server.js:7:38:7:44 | message | | browser.js:21:13:21:18 | 'test' | sockjs.js:9:31:9:37 | message | | client-custom.js:7:11:7:27 | 'Hi from client!' | server.js:7:38:7:44 | message | +| client-custom.js:17:30:17:46 | 'Hi from client!' | server.js:7:38:7:44 | message | | client.js:7:11:7:27 | 'Hi from client!' | server.js:7:38:7:44 | message | | client.js:15:32:15:44 | require('ws') | client-custom.js:1:9:1:21 | MyWebSocketWS | | client.js:16:40:16:72 | new Web ... e.org') | client-custom.js:1:24:1:44 | myWebSo ... nstance | From 49194b03400ad8f940e80bef5940f88e12afc575 Mon Sep 17 00:00:00 2001 From: Napalys Date: Fri, 4 Apr 2025 09:20:40 +0200 Subject: [PATCH 06/13] Updated `WebSocketReceiveNode` with API graphs. --- .../ql/lib/semmle/javascript/frameworks/WebSocket.qll | 6 +++--- .../frameworks/WebSocket/browser-custom.js | 8 ++++---- .../frameworks/WebSocket/client-custom.js | 2 +- .../library-tests/frameworks/WebSocket/test.expected | 10 ++++++++++ 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll b/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll index 09c9f98e3f02..0455bb691724 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll @@ -159,8 +159,8 @@ module ClientWebSocket { private DataFlow::FunctionNode getAMessageHandler( ClientWebSocket::ClientSocket emitter, string methodName ) { - exists(DataFlow::CallNode call | - call = emitter.getAMemberCall(methodName) and + exists(API::CallNode call | + call = emitter.getReturn().getMember(methodName).getACall() and call.getArgument(0).mayHaveStringValue("message") and result = call.getCallback(1) ) @@ -175,7 +175,7 @@ module ClientWebSocket { WebSocketReceiveNode() { this = getAMessageHandler(emitter, "addEventListener") or - this = emitter.getAPropertyWrite("onmessage").getRhs() + this = emitter.getReturn().getMember("onmessage").getAValueReachingSink() } override DataFlow::Node getReceivedItem(int i) { diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js b/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js index 11765138f55c..95c931f5cee3 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js @@ -41,11 +41,11 @@ import { MyWebSocket, MySockJS, myWebSocketInstance, mySockJSInstance } from './ myWebSocketInstance.addEventListener('message', function (event) { console.log('Message from server ', event.data); - }); // $ MISSING: clientReceive + }); // $ clientReceive myWebSocketInstance.onmessage = function (event) { console.log("Message from server 2", event.data) - }; // $ MISSING: clientReceive + }; // $ clientReceive })(); @@ -57,9 +57,9 @@ import { MyWebSocket, MySockJS, myWebSocketInstance, mySockJSInstance } from './ mySockJSInstance.onmessage = function (e) { console.log('message', e.data); mySockJSInstance.close(); - }; // $ MISSING: clientReceive + }; // $ clientReceive mySockJSInstance.addEventListener('message', function (event) { console.log('Using addEventListener ', event.data); - }); // $ MISSING: clientReceive + }); // $ clientReceive })(); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js b/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js index 5d7ac4b564d8..dd8c84e87c0d 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js @@ -19,5 +19,5 @@ const { MyWebSocketWS, myWebSocketWSInstance } = require('./client.js'); myWebSocketWSInstance.on('message', function incoming(data) { console.log(data); - }); // $ MISSING: clientReceive + }); // $ clientReceive })(); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected b/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected index 4d7e33f54022..a5c55cf63631 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected @@ -3,11 +3,16 @@ clientReceive | browser-custom.js:14:21:16:2 | functio ... ata)\\n\\t} | | browser-custom.js:26:19:29:2 | functio ... e();\\n\\t} | | browser-custom.js:31:35:33:2 | functio ... ta);\\n\\t} | +| browser-custom.js:42:53:44:5 | functio ... ;\\n } | +| browser-custom.js:46:37:48:5 | functio ... )\\n } | +| browser-custom.js:57:34:60:5 | functio ... ;\\n } | +| browser-custom.js:62:50:64:5 | functio ... ;\\n } | | browser.js:8:37:10:2 | functio ... ta);\\n\\t} | | browser.js:12:21:14:2 | functio ... ata)\\n\\t} | | browser.js:24:19:27:2 | functio ... e();\\n\\t} | | browser.js:29:35:31:2 | functio ... ta);\\n\\t} | | client-custom.js:10:19:12:2 | functio ... ta);\\n\\t} | +| client-custom.js:20:38:22:2 | functio ... ta);\\n\\t} | | client.js:10:19:12:2 | functio ... ta);\\n\\t} | clientSend | browser-custom.js:7:3:7:33 | socket. ... wser!') | @@ -47,14 +52,19 @@ flowSteps | client.js:16:40:16:72 | new Web ... e.org') | client-custom.js:1:24:1:44 | myWebSo ... nstance | | server.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:11:39:11:48 | event.data | | server.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:15:40:15:49 | event.data | +| server.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:43:45:43:54 | event.data | +| server.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:47:46:47:55 | event.data | | server.js:11:11:11:27 | 'Hi from server!' | browser.js:9:39:9:48 | event.data | | server.js:11:11:11:27 | 'Hi from server!' | browser.js:13:40:13:49 | event.data | | server.js:11:11:11:27 | 'Hi from server!' | client-custom.js:10:37:10:40 | data | +| server.js:11:11:11:27 | 'Hi from server!' | client-custom.js:20:56:20:59 | data | | server.js:11:11:11:27 | 'Hi from server!' | client.js:10:37:10:40 | data | | server.js:15:36:15:55 | require('ws').Server | server-custom.js:1:9:1:25 | MyWebSocketServer | | server.js:16:44:16:79 | new Web ... 8080 }) | server-custom.js:1:28:1:52 | myWebSo ... nstance | | sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser-custom.js:27:26:27:31 | e.data | | sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser-custom.js:32:42:32:51 | event.data | +| sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser-custom.js:58:32:58:37 | e.data | +| sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser-custom.js:63:48:63:57 | event.data | | sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser.js:25:26:25:31 | e.data | | sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser.js:30:42:30:51 | event.data | remoteFlow From 4b7a9cd399d68ac8bb1b06f05f07bd5d84142b63 Mon Sep 17 00:00:00 2001 From: Napalys Date: Fri, 4 Apr 2025 09:25:50 +0200 Subject: [PATCH 07/13] Added test case with `bind`. --- .../library-tests/frameworks/WebSocket/browser-custom.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js b/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js index 95c931f5cee3..a426b6768a66 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js @@ -63,3 +63,12 @@ import { MyWebSocket, MySockJS, myWebSocketInstance, mySockJSInstance } from './ console.log('Using addEventListener ', event.data); }); // $ clientReceive })(); + + +const recv_message = function (e) { + console.log('Received message:', e.data); +}; // $ MISSING: clientReceive + +(function () { + myWebSocketInstance.onmessage = recv_message.bind(this); +})(); From c5860e92ec84b3d681175b83ab534a80501c6418 Mon Sep 17 00:00:00 2001 From: Napalys Date: Fri, 4 Apr 2025 09:26:45 +0200 Subject: [PATCH 08/13] Updated `WebSocketReceiveNode` to match bind functions. --- .../ql/lib/semmle/javascript/frameworks/WebSocket.qll | 6 ++++++ .../library-tests/frameworks/WebSocket/browser-custom.js | 2 +- .../test/library-tests/frameworks/WebSocket/test.expected | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll b/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll index 0455bb691724..98aef6ed0961 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll @@ -176,6 +176,12 @@ module ClientWebSocket { this = getAMessageHandler(emitter, "addEventListener") or this = emitter.getReturn().getMember("onmessage").getAValueReachingSink() + or + exists(DataFlow::MethodCallNode bindCall | + bindCall = emitter.getReturn().getMember("onmessage").getAValueReachingSink() and + bindCall.getMethodName() = "bind" and + this = bindCall.getReceiver().getAFunctionValue() + ) } override DataFlow::Node getReceivedItem(int i) { diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js b/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js index a426b6768a66..ad0d30fb01ea 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js @@ -67,7 +67,7 @@ import { MyWebSocket, MySockJS, myWebSocketInstance, mySockJSInstance } from './ const recv_message = function (e) { console.log('Received message:', e.data); -}; // $ MISSING: clientReceive +}; // $ clientReceive (function () { myWebSocketInstance.onmessage = recv_message.bind(this); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected b/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected index a5c55cf63631..16fc0f0d267f 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected @@ -7,6 +7,7 @@ clientReceive | browser-custom.js:46:37:48:5 | functio ... )\\n } | | browser-custom.js:57:34:60:5 | functio ... ;\\n } | | browser-custom.js:62:50:64:5 | functio ... ;\\n } | +| browser-custom.js:68:22:70:1 | functio ... ata);\\n} | | browser.js:8:37:10:2 | functio ... ta);\\n\\t} | | browser.js:12:21:14:2 | functio ... ata)\\n\\t} | | browser.js:24:19:27:2 | functio ... e();\\n\\t} | @@ -54,6 +55,7 @@ flowSteps | server.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:15:40:15:49 | event.data | | server.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:43:45:43:54 | event.data | | server.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:47:46:47:55 | event.data | +| server.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:69:38:69:43 | e.data | | server.js:11:11:11:27 | 'Hi from server!' | browser.js:9:39:9:48 | event.data | | server.js:11:11:11:27 | 'Hi from server!' | browser.js:13:40:13:49 | event.data | | server.js:11:11:11:27 | 'Hi from server!' | client-custom.js:10:37:10:40 | data | From 6bcfd8c91ded71561d6a58ead3aca5c053465e54 Mon Sep 17 00:00:00 2001 From: Napalys Date: Fri, 4 Apr 2025 09:31:16 +0200 Subject: [PATCH 09/13] Updated `getAServer` with API graphs. --- .../javascript/frameworks/WebSocket.qll | 8 ++-- .../frameworks/WebSocket/server-custom.js | 16 ++++---- .../frameworks/WebSocket/test.expected | 40 +++++++++++++++++++ 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll b/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll index 98aef6ed0961..da110c6562c0 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll @@ -212,19 +212,19 @@ module ServerWebSocket { /** * Gets a server created by a library named `library`. */ - DataFlow::SourceNode getAServer(LibraryName library) { + API::InvokeNode getAServer(LibraryName library) { library = ws() and - result = DataFlow::moduleImport("ws").getAConstructorInvocation("Server") + result = API::moduleImport("ws").getMember("Server").getAnInvocation() or library = sockjs() and - result = DataFlow::moduleImport("sockjs").getAMemberCall("createServer") + result = API::moduleImport("sockjs").getMember("createServer").getAnInvocation() } /** * Gets a `socket.on("connection", (msg, req) => {})` call. */ private DataFlow::CallNode getAConnectionCall(LibraryName library) { - result = getAServer(library).getAMemberCall(EventEmitter::on()) and + result = getAServer(library).getReturn().getMember(EventEmitter::on()).getACall() and result.getArgument(0).mayHaveStringValue("connection") } diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/server-custom.js b/javascript/ql/test/library-tests/frameworks/WebSocket/server-custom.js index 63893ba2f5ca..65d49c0d73ee 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/server-custom.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/server-custom.js @@ -3,21 +3,21 @@ const { MyWebSocketServer, myWebSocketServerInstance } = require('./server.js'); (function () { const wss = new MyWebSocketServer({ port: 8080 }); - wss.on('connection', function connection(ws) { // $ MISSING: serverSocket - ws.on('message', function incoming(message) { // $ MISSING: remoteFlow + wss.on('connection', function connection(ws) { // $ serverSocket + ws.on('message', function incoming(message) { // $ remoteFlow console.log('received: %s', message); - }); // $ MISSING: serverReceive + }); // $ serverReceive - ws.send('Hi from server!'); // $ MISSING: serverSend + ws.send('Hi from server!'); // $ serverSend }); })(); (function () { - myWebSocketServerInstance.on('connection', function connection(ws) { // $ MISSING: serverSocket - ws.on('message', function incoming(message) { // $ MISSING: remoteFlow + myWebSocketServerInstance.on('connection', function connection(ws) { // $ serverSocket + ws.on('message', function incoming(message) { // $ remoteFlow console.log('received: %s', message); - }); // $ MISSING: serverReceive + }); // $ serverReceive - ws.send('Hi from server!'); // $ MISSING: serverSend + ws.send('Hi from server!'); // $ serverSend }); })(); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected b/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected index 16fc0f0d267f..7480e0668766 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected @@ -40,17 +40,49 @@ flowSteps | browser-custom.js:1:23:1:30 | MySockJS | browser-custom.js:1:23:1:30 | MySockJS | | browser-custom.js:1:33:1:51 | myWebSocketInstance | browser-custom.js:1:33:1:51 | myWebSocketInstance | | browser-custom.js:1:54:1:69 | mySockJSInstance | browser-custom.js:1:54:1:69 | mySockJSInstance | +| browser-custom.js:7:15:7:32 | 'Hi from browser!' | server-custom.js:7:38:7:44 | message | +| browser-custom.js:7:15:7:32 | 'Hi from browser!' | server-custom.js:17:38:17:44 | message | | browser-custom.js:7:15:7:32 | 'Hi from browser!' | server.js:7:38:7:44 | message | | browser-custom.js:23:13:23:18 | 'test' | sockjs.js:9:31:9:37 | message | +| browser-custom.js:39:34:39:51 | 'Hi from browser!' | server-custom.js:7:38:7:44 | message | +| browser-custom.js:39:34:39:51 | 'Hi from browser!' | server-custom.js:17:38:17:44 | message | | browser-custom.js:39:34:39:51 | 'Hi from browser!' | server.js:7:38:7:44 | message | | browser-custom.js:54:31:54:36 | 'test' | sockjs.js:9:31:9:37 | message | +| browser.js:5:15:5:32 | 'Hi from browser!' | server-custom.js:7:38:7:44 | message | +| browser.js:5:15:5:32 | 'Hi from browser!' | server-custom.js:17:38:17:44 | message | | browser.js:5:15:5:32 | 'Hi from browser!' | server.js:7:38:7:44 | message | | browser.js:21:13:21:18 | 'test' | sockjs.js:9:31:9:37 | message | +| client-custom.js:7:11:7:27 | 'Hi from client!' | server-custom.js:7:38:7:44 | message | +| client-custom.js:7:11:7:27 | 'Hi from client!' | server-custom.js:17:38:17:44 | message | | client-custom.js:7:11:7:27 | 'Hi from client!' | server.js:7:38:7:44 | message | +| client-custom.js:17:30:17:46 | 'Hi from client!' | server-custom.js:7:38:7:44 | message | +| client-custom.js:17:30:17:46 | 'Hi from client!' | server-custom.js:17:38:17:44 | message | | client-custom.js:17:30:17:46 | 'Hi from client!' | server.js:7:38:7:44 | message | +| client.js:7:11:7:27 | 'Hi from client!' | server-custom.js:7:38:7:44 | message | +| client.js:7:11:7:27 | 'Hi from client!' | server-custom.js:17:38:17:44 | message | | client.js:7:11:7:27 | 'Hi from client!' | server.js:7:38:7:44 | message | | client.js:15:32:15:44 | require('ws') | client-custom.js:1:9:1:21 | MyWebSocketWS | | client.js:16:40:16:72 | new Web ... e.org') | client-custom.js:1:24:1:44 | myWebSo ... nstance | +| server-custom.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:11:39:11:48 | event.data | +| server-custom.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:15:40:15:49 | event.data | +| server-custom.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:43:45:43:54 | event.data | +| server-custom.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:47:46:47:55 | event.data | +| server-custom.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:69:38:69:43 | e.data | +| server-custom.js:11:11:11:27 | 'Hi from server!' | browser.js:9:39:9:48 | event.data | +| server-custom.js:11:11:11:27 | 'Hi from server!' | browser.js:13:40:13:49 | event.data | +| server-custom.js:11:11:11:27 | 'Hi from server!' | client-custom.js:10:37:10:40 | data | +| server-custom.js:11:11:11:27 | 'Hi from server!' | client-custom.js:20:56:20:59 | data | +| server-custom.js:11:11:11:27 | 'Hi from server!' | client.js:10:37:10:40 | data | +| server-custom.js:21:11:21:27 | 'Hi from server!' | browser-custom.js:11:39:11:48 | event.data | +| server-custom.js:21:11:21:27 | 'Hi from server!' | browser-custom.js:15:40:15:49 | event.data | +| server-custom.js:21:11:21:27 | 'Hi from server!' | browser-custom.js:43:45:43:54 | event.data | +| server-custom.js:21:11:21:27 | 'Hi from server!' | browser-custom.js:47:46:47:55 | event.data | +| server-custom.js:21:11:21:27 | 'Hi from server!' | browser-custom.js:69:38:69:43 | e.data | +| server-custom.js:21:11:21:27 | 'Hi from server!' | browser.js:9:39:9:48 | event.data | +| server-custom.js:21:11:21:27 | 'Hi from server!' | browser.js:13:40:13:49 | event.data | +| server-custom.js:21:11:21:27 | 'Hi from server!' | client-custom.js:10:37:10:40 | data | +| server-custom.js:21:11:21:27 | 'Hi from server!' | client-custom.js:20:56:20:59 | data | +| server-custom.js:21:11:21:27 | 'Hi from server!' | client.js:10:37:10:40 | data | | server.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:11:39:11:48 | event.data | | server.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:15:40:15:49 | event.data | | server.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:43:45:43:54 | event.data | @@ -70,14 +102,22 @@ flowSteps | sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser.js:25:26:25:31 | e.data | | sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser.js:30:42:30:51 | event.data | remoteFlow +| server-custom.js:7:38:7:44 | message | +| server-custom.js:17:38:17:44 | message | | server.js:7:38:7:44 | message | | sockjs.js:9:31:9:37 | message | serverReceive +| server-custom.js:7:3:9:4 | ws.on(' ... );\\n\\t\\t}) | +| server-custom.js:17:3:19:4 | ws.on(' ... );\\n\\t\\t}) | | server.js:7:3:9:4 | ws.on(' ... );\\n\\t\\t}) | | sockjs.js:9:5:12:6 | conn.on ... \\n }) | serverSend +| server-custom.js:11:3:11:28 | ws.send ... rver!') | +| server-custom.js:21:3:21:28 | ws.send ... rver!') | | server.js:11:3:11:28 | ws.send ... rver!') | | sockjs.js:11:9:11:51 | conn.wr ... test))) | serverSocket +| server-custom.js:6:43:6:44 | ws | +| server-custom.js:16:65:16:66 | ws | | server.js:6:43:6:44 | ws | | sockjs.js:8:40:8:43 | conn | From 6fb5376c5f65259ef1e185c3d83820c5130b1c26 Mon Sep 17 00:00:00 2001 From: Napalys Date: Mon, 7 Apr 2025 11:44:40 +0200 Subject: [PATCH 10/13] Refactor `ReceivedItemAsRemoteFlow` to handle data from both client and server WebSocket sources --- .../javascript/frameworks/WebSocket.qll | 19 +++++----- .../frameworks/WebSocket/browser-custom.js | 18 +++++----- .../frameworks/WebSocket/browser.js | 8 ++--- .../frameworks/WebSocket/client-custom.js | 4 +-- .../frameworks/WebSocket/client.js | 2 +- .../frameworks/WebSocket/test.expected | 36 +++++++++++++------ 6 files changed, 53 insertions(+), 34 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll b/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll index da110c6562c0..5e40360a4d85 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll @@ -344,15 +344,18 @@ module ServerWebSocket { result = this.getCallback(1).getParameter(0) } } +} - /** - * A data flow node representing data received from a client, viewed as remote user input. - */ - private class ReceivedItemAsRemoteFlow extends RemoteFlowSource { - ReceivedItemAsRemoteFlow() { this = any(ReceiveNode rercv).getReceivedItem(_) } +/** + * A data flow node representing data received from a client or server, viewed as remote user input. + */ +private class ReceivedItemAsRemoteFlow extends RemoteFlowSource { + ReceivedItemAsRemoteFlow() { + this = any(ClientWebSocket::ReceiveNode rercv).getReceivedItem(_) or + this = any(ServerWebSocket::ReceiveNode rercv).getReceivedItem(_) + } - override string getSourceType() { result = "WebSocket client data" } + override string getSourceType() { result = "WebSocket transmitted data" } - override predicate isUserControlledObject() { any() } - } + override predicate isUserControlledObject() { any() } } diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js b/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js index ad0d30fb01ea..f7ac5b0f4fe3 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/browser-custom.js @@ -8,11 +8,11 @@ import { MyWebSocket, MySockJS, myWebSocketInstance, mySockJSInstance } from './ }); socket.addEventListener('message', function (event) { - console.log('Message from server ', event.data); + console.log('Message from server ', event.data); // $ remoteFlow }); // $ clientReceive socket.onmessage = function (event) { - console.log("Message from server 2", event.data) + console.log("Message from server 2", event.data); // $ remoteFlow }; // $ clientReceive })(); @@ -24,12 +24,12 @@ import { MyWebSocket, MySockJS, myWebSocketInstance, mySockJSInstance } from './ }; sock.onmessage = function (e) { - console.log('message', e.data); + console.log('message', e.data); // $ remoteFlow sock.close(); }; // $ clientReceive sock.addEventListener('message', function (event) { - console.log('Using addEventListener ', event.data); + console.log('Using addEventListener ', event.data); // $ remoteFlow }); // $ clientReceive })(); @@ -40,11 +40,11 @@ import { MyWebSocket, MySockJS, myWebSocketInstance, mySockJSInstance } from './ }); myWebSocketInstance.addEventListener('message', function (event) { - console.log('Message from server ', event.data); + console.log('Message from server ', event.data); // $ remoteFlow }); // $ clientReceive myWebSocketInstance.onmessage = function (event) { - console.log("Message from server 2", event.data) + console.log("Message from server 2", event.data); // $ remoteFlow }; // $ clientReceive })(); @@ -55,18 +55,18 @@ import { MyWebSocket, MySockJS, myWebSocketInstance, mySockJSInstance } from './ }; mySockJSInstance.onmessage = function (e) { - console.log('message', e.data); + console.log('message', e.data); // $ remoteFlow mySockJSInstance.close(); }; // $ clientReceive mySockJSInstance.addEventListener('message', function (event) { - console.log('Using addEventListener ', event.data); + console.log('Using addEventListener ', event.data); // $ remoteFlow }); // $ clientReceive })(); const recv_message = function (e) { - console.log('Received message:', e.data); + console.log('Received message:', e.data); // $ remoteFlow }; // $ clientReceive (function () { diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/browser.js b/javascript/ql/test/library-tests/frameworks/WebSocket/browser.js index f966879f9f25..4d82c113e088 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/browser.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/browser.js @@ -6,11 +6,11 @@ }); socket.addEventListener('message', function (event) { - console.log('Message from server ', event.data); + console.log('Message from server ', event.data); // $ remoteFlow }); // $clientReceive socket.onmessage = function (event) { - console.log("Message from server 2", event.data) + console.log("Message from server 2", event.data); // $ remoteFlow }; // $clientReceive })(); @@ -22,12 +22,12 @@ }; sock.onmessage = function (e) { - console.log('message', e.data); + console.log('message', e.data); // $ remoteFlow sock.close(); }; // $clientReceive sock.addEventListener('message', function (event) { - console.log('Using addEventListener ', event.data); + console.log('Using addEventListener ', event.data); // $ remoteFlow }); // $clientReceive })(); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js b/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js index dd8c84e87c0d..24eeebdb4ccf 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/client-custom.js @@ -7,7 +7,7 @@ const { MyWebSocketWS, myWebSocketWSInstance } = require('./client.js'); ws.send('Hi from client!'); // $ clientSend }); - ws.on('message', function incoming(data) { + ws.on('message', function incoming(data) { // $ remoteFlow console.log(data); }); // $ clientReceive })(); @@ -17,7 +17,7 @@ const { MyWebSocketWS, myWebSocketWSInstance } = require('./client.js'); myWebSocketWSInstance.send('Hi from client!'); // $ clientSend }); - myWebSocketWSInstance.on('message', function incoming(data) { + myWebSocketWSInstance.on('message', function incoming(data) { // $ remoteFlow console.log(data); }); // $ clientReceive })(); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/client.js b/javascript/ql/test/library-tests/frameworks/WebSocket/client.js index 54a200f33793..7061d51a954c 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/client.js +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/client.js @@ -7,7 +7,7 @@ const WebSocket = require('ws'); ws.send('Hi from client!'); // $clientSend }); - ws.on('message', function incoming(data) { + ws.on('message', function incoming(data) { // $ remoteFlow console.log(data); }); // $clientReceive })(); diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected b/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected index 7480e0668766..d08070a1a71a 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/test.expected @@ -1,17 +1,17 @@ clientReceive -| browser-custom.js:10:37:12:2 | functio ... ta);\\n\\t} | -| browser-custom.js:14:21:16:2 | functio ... ata)\\n\\t} | +| browser-custom.js:10:37:12:2 | functio ... Flow\\n\\t} | +| browser-custom.js:14:21:16:2 | functio ... Flow\\n\\t} | | browser-custom.js:26:19:29:2 | functio ... e();\\n\\t} | -| browser-custom.js:31:35:33:2 | functio ... ta);\\n\\t} | -| browser-custom.js:42:53:44:5 | functio ... ;\\n } | -| browser-custom.js:46:37:48:5 | functio ... )\\n } | +| browser-custom.js:31:35:33:2 | functio ... Flow\\n\\t} | +| browser-custom.js:42:53:44:5 | functio ... w\\n } | +| browser-custom.js:46:37:48:5 | functio ... w\\n } | | browser-custom.js:57:34:60:5 | functio ... ;\\n } | -| browser-custom.js:62:50:64:5 | functio ... ;\\n } | -| browser-custom.js:68:22:70:1 | functio ... ata);\\n} | -| browser.js:8:37:10:2 | functio ... ta);\\n\\t} | -| browser.js:12:21:14:2 | functio ... ata)\\n\\t} | +| browser-custom.js:62:50:64:5 | functio ... w\\n } | +| browser-custom.js:68:22:70:1 | functio ... eFlow\\n} | +| browser.js:8:37:10:2 | functio ... Flow\\n\\t} | +| browser.js:12:21:14:2 | functio ... Flow\\n\\t} | | browser.js:24:19:27:2 | functio ... e();\\n\\t} | -| browser.js:29:35:31:2 | functio ... ta);\\n\\t} | +| browser.js:29:35:31:2 | functio ... Flow\\n\\t} | | client-custom.js:10:19:12:2 | functio ... ta);\\n\\t} | | client-custom.js:20:38:22:2 | functio ... ta);\\n\\t} | | client.js:10:19:12:2 | functio ... ta);\\n\\t} | @@ -102,6 +102,22 @@ flowSteps | sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser.js:25:26:25:31 | e.data | | sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser.js:30:42:30:51 | event.data | remoteFlow +| browser-custom.js:11:39:11:48 | event.data | +| browser-custom.js:15:40:15:49 | event.data | +| browser-custom.js:27:26:27:31 | e.data | +| browser-custom.js:32:42:32:51 | event.data | +| browser-custom.js:43:45:43:54 | event.data | +| browser-custom.js:47:46:47:55 | event.data | +| browser-custom.js:58:32:58:37 | e.data | +| browser-custom.js:63:48:63:57 | event.data | +| browser-custom.js:69:38:69:43 | e.data | +| browser.js:9:39:9:48 | event.data | +| browser.js:13:40:13:49 | event.data | +| browser.js:25:26:25:31 | e.data | +| browser.js:30:42:30:51 | event.data | +| client-custom.js:10:37:10:40 | data | +| client-custom.js:20:56:20:59 | data | +| client.js:10:37:10:40 | data | | server-custom.js:7:38:7:44 | message | | server-custom.js:17:38:17:44 | message | | server.js:7:38:7:44 | message | From c4fa4176802a27405e418ba0a87325c502b34cc0 Mon Sep 17 00:00:00 2001 From: Napalys Date: Mon, 7 Apr 2025 12:11:33 +0200 Subject: [PATCH 11/13] Added change note --- javascript/ql/lib/change-notes/2025-04-07-websocket.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 javascript/ql/lib/change-notes/2025-04-07-websocket.md diff --git a/javascript/ql/lib/change-notes/2025-04-07-websocket.md b/javascript/ql/lib/change-notes/2025-04-07-websocket.md new file mode 100644 index 000000000000..a6f6e214f3af --- /dev/null +++ b/javascript/ql/lib/change-notes/2025-04-07-websocket.md @@ -0,0 +1,5 @@ +--- +category: minorAnalysis +--- +* Improved `WebSocket` analysis by refactoring the model to use API graphs. +* Added data received from `WebSocket` clients as a remote flow source. From 2dca95af9220bf0adbfa567edc3af2d114289c35 Mon Sep 17 00:00:00 2001 From: Napalys Klicius Date: Wed, 9 Apr 2025 14:26:00 +0200 Subject: [PATCH 12/13] Update javascript/ql/lib/change-notes/2025-04-07-websocket.md Co-authored-by: Asger F --- javascript/ql/lib/change-notes/2025-04-07-websocket.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/lib/change-notes/2025-04-07-websocket.md b/javascript/ql/lib/change-notes/2025-04-07-websocket.md index a6f6e214f3af..1d6cdb8e6b11 100644 --- a/javascript/ql/lib/change-notes/2025-04-07-websocket.md +++ b/javascript/ql/lib/change-notes/2025-04-07-websocket.md @@ -1,5 +1,5 @@ --- category: minorAnalysis --- -* Improved `WebSocket` analysis by refactoring the model to use API graphs. +* Improved detection of `WebSocket` and `SockJS` usage. * Added data received from `WebSocket` clients as a remote flow source. From 5243f90c90ac38387aff36a34e2d0b4ebaea9607 Mon Sep 17 00:00:00 2001 From: Napalys Date: Wed, 9 Apr 2025 14:56:24 +0200 Subject: [PATCH 13/13] Brought back old methods and marked them as `deprecated` --- .../javascript/frameworks/WebSocket.qll | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll b/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll index 5e40360a4d85..f71b1cf9e0d6 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/WebSocket.qll @@ -70,10 +70,35 @@ module ClientWebSocket { /** * A class that can be used to instantiate a WebSocket instance. */ - class SocketClass extends API::Node { + deprecated class SocketClass extends DataFlow::SourceNode { LibraryName library; // the name of the WebSocket library. Can be one of the libraries defined in `LibraryNames`. SocketClass() { + this = DataFlow::globalVarRef("WebSocket") and library = websocket() + or + this = DataFlow::moduleImport("ws") and library = ws() + or + // the sockjs-client library:https://www.npmjs.com/package/sockjs-client + library = sockjs() and + ( + this = DataFlow::moduleImport("sockjs-client") or + this = DataFlow::globalVarRef("SockJS") + ) + } + + /** + * Gets the WebSocket library name. + */ + LibraryName getLibrary() { result = library } + } + + /** + * A class that can be used to instantiate a WebSocket instance. + */ + class WebSocketClass extends API::Node { + LibraryName library; // the name of the WebSocket library. Can be one of the libraries defined in `LibraryNames`. + + WebSocketClass() { this = any(WebSocketEntryPoint e).getANode() and library = websocket() or this = API::moduleImport("ws") and library = ws() @@ -96,7 +121,7 @@ module ClientWebSocket { * A client WebSocket instance. */ class ClientSocket extends EventEmitter::Range, API::NewNode, ClientRequest::Range { - SocketClass socketClass; + WebSocketClass socketClass; ClientSocket() { this = socketClass.getAnInvocation() } @@ -212,7 +237,18 @@ module ServerWebSocket { /** * Gets a server created by a library named `library`. */ - API::InvokeNode getAServer(LibraryName library) { + deprecated DataFlow::SourceNode getAServer(LibraryName library) { + library = ws() and + result = DataFlow::moduleImport("ws").getAConstructorInvocation("Server") + or + library = sockjs() and + result = DataFlow::moduleImport("sockjs").getAMemberCall("createServer") + } + + /** + * Gets a server created by a library named `library`. + */ + API::InvokeNode getAServerInvocation(LibraryName library) { library = ws() and result = API::moduleImport("ws").getMember("Server").getAnInvocation() or @@ -224,7 +260,7 @@ module ServerWebSocket { * Gets a `socket.on("connection", (msg, req) => {})` call. */ private DataFlow::CallNode getAConnectionCall(LibraryName library) { - result = getAServer(library).getReturn().getMember(EventEmitter::on()).getACall() and + result = getAServerInvocation(library).getReturn().getMember(EventEmitter::on()).getACall() and result.getArgument(0).mayHaveStringValue("connection") }