Python: Add models for socketio#20914
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds support for the python-socketio library by modeling remote flow sources and request handlers. The implementation enables CodeQL to track tainted data flowing through Socket.IO event handlers and callbacks.
Key Changes
- Added comprehensive models for
python-socketioServer, AsyncServer, Namespace, and AsyncNamespace classes - Identified Socket.IO event handlers and callbacks as request handlers with appropriate routed parameters
- Modeled the return value of
call()methods as remote flow sources
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| python/ql/lib/semmle/python/frameworks/Socketio.qll | Core implementation providing models for Socket.IO servers, event handlers, callbacks, and remote flow sources |
| python/ql/lib/semmle/python/Frameworks.qll | Added import for the new Socketio module in alphabetical order |
| python/ql/test/library-tests/frameworks/socketio/test.py | Test file covering various Socket.IO event handler patterns including decorators and lambda handlers |
| python/ql/test/library-tests/frameworks/socketio/taint_test.py | Comprehensive taint tracking test covering both sync and async Socket.IO patterns, namespaces, and callbacks |
| python/ql/test/library-tests/frameworks/socketio/InlineTaintTest.ql | Query file for inline taint testing |
| python/ql/test/library-tests/frameworks/socketio/InlineTaintTest.expected | Expected test results for inline taint tests |
| python/ql/test/library-tests/frameworks/socketio/ConceptsTest.ql | Query file for concepts testing |
| python/ql/test/library-tests/frameworks/socketio/ConceptsTest.expected | Expected test results for concepts tests (empty, indicating no failures expected) |
| python/ql/lib/change-notes/2025-11-26-socketio.md | Release note documenting the addition of remote flow sources for python-socketio |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ensure_tainted(data) # $ tainted | ||
| res = sio.call("e1", sid=sid) | ||
| ensure_tainted(res) # $ tainted | ||
| sio.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x |
There was a problem hiding this comment.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
There was a problem hiding this comment.
Each of these cases is clearer as a lambda; to test that the request parameter is a remote flow source.
| res = sio.call("e1", sid=sid) | ||
| ensure_tainted(res) # $ tainted | ||
| sio.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x | ||
| sio.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x |
There was a problem hiding this comment.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
| sio.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x | |
| sio.send("hi", to=sid, callback=ensure_tainted) # $ tainted $ requestHandler routedParameter=x |
| ensure_tainted(data) # $ tainted | ||
| res = self.call("e1", sid=sid) | ||
| ensure_tainted(res) # $ tainted | ||
| self.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x |
There was a problem hiding this comment.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
| res = self.call("e1", sid=sid) | ||
| ensure_tainted(res) # $ tainted | ||
| self.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x | ||
| self.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x |
There was a problem hiding this comment.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
| self.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x | |
| self.send("hi", to=sid, callback=ensure_tainted) # $ tainted $ requestHandler routedParameter=x |
| ensure_tainted(data) # $ tainted | ||
| res = await asio.call("e1", sid=sid) | ||
| ensure_tainted(res) # $ tainted | ||
| await asio.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x |
There was a problem hiding this comment.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
| res = await asio.call("e1", sid=sid) | ||
| ensure_tainted(res) # $ tainted | ||
| await asio.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x | ||
| await asio.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x |
There was a problem hiding this comment.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
| await asio.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x | |
| await asio.send("hi", to=sid, callback=ensure_tainted) # $ tainted $ requestHandler routedParameter=x |
| ensure_tainted(data) # $ tainted | ||
| res = await self.call("e1", sid=sid) | ||
| ensure_tainted(res) # $ tainted | ||
| await self.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x |
There was a problem hiding this comment.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
| await self.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x | |
| await self.emit("e2", "hi", to=sid, callback=ensure_tainted) # $ tainted $ requestHandler routedParameter=x |
| res = await self.call("e1", sid=sid) | ||
| ensure_tainted(res) # $ tainted | ||
| await self.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x | ||
| await self.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x |
There was a problem hiding this comment.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
| await self.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x | |
| await self.send("hi", to=sid, callback=ensure_tainted) # $ tainted $ requestHandler routedParameter=x |
yoff
left a comment
There was a problem hiding this comment.
LGTM. I think we should perhaps also update docs/codeql/reusables/supported-frameworks.rst to add SocketIO for Python.
|
Sure - that can be a follow up after #20945 is also merged |
Adds remote flow sources for the
python-socketiolibrary.