mirror of
https://github.com/github/codeql.git
synced 2026-04-24 08:15:14 +02:00
Merge branch 'main' into stdlib-optparse
This commit is contained in:
@@ -653,8 +653,7 @@ module AiohttpWebModel {
|
||||
/**
|
||||
* A call to `set_cookie` on a HTTP Response.
|
||||
*/
|
||||
class AiohttpResponseSetCookieCall extends Http::Server::CookieWrite::Range, DataFlow::CallCfgNode
|
||||
{
|
||||
class AiohttpResponseSetCookieCall extends Http::Server::SetCookieCall {
|
||||
AiohttpResponseSetCookieCall() {
|
||||
this = aiohttpResponseInstance().getMember("set_cookie").getACall()
|
||||
}
|
||||
@@ -706,6 +705,33 @@ module AiohttpWebModel {
|
||||
|
||||
override DataFlow::Node getValueArg() { result = value }
|
||||
}
|
||||
|
||||
/**
|
||||
* A dict-like write to an item of the `headers` attribute on a HTTP response, such as
|
||||
* `response.headers[name] = value`.
|
||||
*/
|
||||
class AiohttpResponseHeaderSubscriptWrite extends Http::Server::ResponseHeaderWrite::Range {
|
||||
DataFlow::Node index;
|
||||
DataFlow::Node value;
|
||||
|
||||
AiohttpResponseHeaderSubscriptWrite() {
|
||||
exists(API::Node i |
|
||||
value = aiohttpResponseInstance().getMember("headers").getSubscriptAt(i).asSink() and
|
||||
index = i.asSink() and
|
||||
// To give `this` a value, we need to choose between either LHS or RHS,
|
||||
// and just go with the RHS as it is readily available
|
||||
this = value
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { result = index }
|
||||
|
||||
override DataFlow::Node getValueArg() { result = value }
|
||||
|
||||
override predicate nameAllowsNewline() { none() }
|
||||
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2170,7 +2170,7 @@ module PrivateDjango {
|
||||
/**
|
||||
* A call to `set_cookie` on a HTTP Response.
|
||||
*/
|
||||
class DjangoResponseSetCookieCall extends Http::Server::CookieWrite::Range,
|
||||
class DjangoResponseSetCookieCall extends Http::Server::SetCookieCall,
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
DjangoResponseSetCookieCall() {
|
||||
@@ -2239,6 +2239,71 @@ module PrivateDjango {
|
||||
|
||||
override DataFlow::Node getValueArg() { result = value }
|
||||
}
|
||||
|
||||
/**
|
||||
* A dict-like write to an item of the `headers` attribute on a HTTP response, such as
|
||||
* `response.headers[name] = value`.
|
||||
*/
|
||||
class DjangoResponseHeaderSubscriptWrite extends Http::Server::ResponseHeaderWrite::Range {
|
||||
DataFlow::Node index;
|
||||
DataFlow::Node value;
|
||||
|
||||
DjangoResponseHeaderSubscriptWrite() {
|
||||
exists(SubscriptNode subscript, DataFlow::AttrRead headerLookup |
|
||||
// To give `this` a value, we need to choose between either LHS or RHS,
|
||||
// and just go with the LHS
|
||||
this.asCfgNode() = subscript
|
||||
|
|
||||
headerLookup
|
||||
.accesses(DjangoImpl::DjangoHttp::Response::HttpResponse::instance(), "headers") and
|
||||
exists(DataFlow::Node subscriptObj |
|
||||
subscriptObj.asCfgNode() = subscript.getObject()
|
||||
|
|
||||
headerLookup.flowsTo(subscriptObj)
|
||||
) and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
index.asCfgNode() = subscript.getIndex()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { result = index }
|
||||
|
||||
override DataFlow::Node getValueArg() { result = value }
|
||||
|
||||
override predicate nameAllowsNewline() { none() }
|
||||
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A dict-like write to an item of an HTTP response, which is treated as a header write,
|
||||
* such as `response[headerName] = value`
|
||||
*/
|
||||
class DjangoResponseSubscriptWrite extends Http::Server::ResponseHeaderWrite::Range {
|
||||
DataFlow::Node index;
|
||||
DataFlow::Node value;
|
||||
|
||||
DjangoResponseSubscriptWrite() {
|
||||
exists(SubscriptNode subscript |
|
||||
// To give `this` a value, we need to choose between either LHS or RHS,
|
||||
// and just go with the LHS
|
||||
this.asCfgNode() = subscript
|
||||
|
|
||||
subscript.getObject() =
|
||||
DjangoImpl::DjangoHttp::Response::HttpResponse::instance().asCfgNode() and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
index.asCfgNode() = subscript.getIndex()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { result = index }
|
||||
|
||||
override DataFlow::Node getValueArg() { result = value }
|
||||
|
||||
override predicate nameAllowsNewline() { none() }
|
||||
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@ private module FabricV1 {
|
||||
// -------------------------------------------------------------------------
|
||||
// fabric.api
|
||||
// -------------------------------------------------------------------------
|
||||
/** Gets a reference to the `fabric.api` module. */
|
||||
API::Node api() { result = fabric().getMember("api") }
|
||||
/** Gets a reference to the `fabric.api` module. Also known as `fabric.operations` */
|
||||
API::Node api() { result = fabric().getMember(["api", "operations"]) }
|
||||
|
||||
/** Provides models for the `fabric.api` module */
|
||||
module Api {
|
||||
|
||||
@@ -30,6 +30,51 @@ module FastApi {
|
||||
API::Node instance() { result = cls().getReturn() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `app.add_middleware` adding a generic middleware.
|
||||
*/
|
||||
private class AddMiddlewareCall extends DataFlow::CallCfgNode {
|
||||
AddMiddlewareCall() { this = App::instance().getMember("add_middleware").getACall() }
|
||||
|
||||
/**
|
||||
* Gets the string corresponding to the middleware
|
||||
*/
|
||||
string getMiddlewareName() { result = this.getArg(0).asExpr().(Name).getId() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `app.add_middleware` adding CORSMiddleware.
|
||||
*/
|
||||
class AddCorsMiddlewareCall extends Http::Server::CorsMiddleware::Range, AddMiddlewareCall {
|
||||
/**
|
||||
* Gets the string corresponding to the middleware
|
||||
*/
|
||||
override string getMiddlewareName() { result = this.getArg(0).asExpr().(Name).getId() }
|
||||
|
||||
/**
|
||||
* Gets the dataflow node corresponding to the allowed CORS origins
|
||||
*/
|
||||
override DataFlow::Node getOrigins() { result = this.getArgByName("allow_origins") }
|
||||
|
||||
/**
|
||||
* Gets the boolean value corresponding to if CORS credentials is enabled
|
||||
* (`true`) or disabled (`false`) by this node.
|
||||
*/
|
||||
override DataFlow::Node getCredentialsAllowed() {
|
||||
result = this.getArgByName("allow_credentials")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the dataflow node corresponding to the allowed CORS methods
|
||||
*/
|
||||
DataFlow::Node getMethods() { result = this.getArgByName("allow_methods") }
|
||||
|
||||
/**
|
||||
* Gets the dataflow node corresponding to the allowed CORS headers
|
||||
*/
|
||||
DataFlow::Node getHeaders() { result = this.getArgByName("allow_headers") }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `fastapi.APIRouter` class
|
||||
*
|
||||
@@ -348,7 +393,7 @@ module FastApi {
|
||||
/**
|
||||
* A call to `set_cookie` on a FastAPI Response.
|
||||
*/
|
||||
private class SetCookieCall extends Http::Server::CookieWrite::Range, DataFlow::MethodCallNode {
|
||||
private class SetCookieCall extends Http::Server::SetCookieCall, DataFlow::MethodCallNode {
|
||||
SetCookieCall() { this.calls(instance(), "set_cookie") }
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
@@ -361,28 +406,59 @@ module FastApi {
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `append` on a `headers` of a FastAPI Response, with the `Set-Cookie`
|
||||
* header-key.
|
||||
* A call to `append` on a `headers` of a FastAPI Response.
|
||||
*/
|
||||
private class HeadersAppendCookie extends Http::Server::CookieWrite::Range,
|
||||
private class HeadersAppend extends Http::Server::ResponseHeaderWrite::Range,
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
HeadersAppendCookie() {
|
||||
exists(DataFlow::AttrRead headers, DataFlow::Node keyArg |
|
||||
HeadersAppend() {
|
||||
exists(DataFlow::AttrRead headers |
|
||||
headers.accesses(instance(), "headers") and
|
||||
this.calls(headers, "append") and
|
||||
keyArg in [this.getArg(0), this.getArgByName("key")] and
|
||||
keyArg.getALocalSource().asExpr().(StringLiteral).getText().toLowerCase() = "set-cookie"
|
||||
this.calls(headers, "append")
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getHeaderArg() {
|
||||
override DataFlow::Node getNameArg() { result = [this.getArg(0), this.getArgByName("key")] }
|
||||
|
||||
override DataFlow::Node getValueArg() {
|
||||
result in [this.getArg(1), this.getArgByName("value")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { none() }
|
||||
override predicate nameAllowsNewline() { none() }
|
||||
|
||||
override DataFlow::Node getValueArg() { none() }
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A dict-like write to an item of the `headers` attribute on a HTTP response, such as
|
||||
* `response.headers[name] = value`.
|
||||
*/
|
||||
class HeaderSubscriptWrite extends Http::Server::ResponseHeaderWrite::Range {
|
||||
DataFlow::Node index;
|
||||
DataFlow::Node value;
|
||||
|
||||
HeaderSubscriptWrite() {
|
||||
exists(SubscriptNode subscript, DataFlow::AttrRead headerLookup |
|
||||
// To give `this` a value, we need to choose between either LHS or RHS,
|
||||
// and just go with the LHS
|
||||
this.asCfgNode() = subscript
|
||||
|
|
||||
headerLookup.accesses(instance(), "headers") and
|
||||
exists(DataFlow::Node subscriptObj | subscriptObj.asCfgNode() = subscript.getObject() |
|
||||
headerLookup.flowsTo(subscriptObj)
|
||||
) and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
index.asCfgNode() = subscript.getIndex()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { result = index }
|
||||
|
||||
override DataFlow::Node getValueArg() { result = value }
|
||||
|
||||
override predicate nameAllowsNewline() { none() }
|
||||
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,9 +583,7 @@ module Flask {
|
||||
*
|
||||
* See https://flask.palletsprojects.com/en/2.0.x/api/#flask.Response.set_cookie
|
||||
*/
|
||||
class FlaskResponseSetCookieCall extends Http::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
class FlaskResponseSetCookieCall extends Http::Server::SetCookieCall, DataFlow::MethodCallNode {
|
||||
FlaskResponseSetCookieCall() { this.calls(Flask::Response::instance(), "set_cookie") }
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
|
||||
@@ -255,7 +255,7 @@ module Pyramid {
|
||||
}
|
||||
|
||||
/** A call to `response.set_cookie`. */
|
||||
private class SetCookieCall extends Http::Server::CookieWrite::Range, DataFlow::MethodCallNode {
|
||||
private class SetCookieCall extends Http::Server::SetCookieCall, DataFlow::MethodCallNode {
|
||||
SetCookieCall() { this.calls(instance(), "set_cookie") }
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
|
||||
@@ -25,6 +25,74 @@ private import semmle.python.frameworks.data.ModelsAsData
|
||||
* - https://www.starlette.io/
|
||||
*/
|
||||
module Starlette {
|
||||
/**
|
||||
* Provides models for the `starlette.app` class
|
||||
*/
|
||||
module App {
|
||||
/** Gets import of `starlette.app`. */
|
||||
API::Node cls() { result = API::moduleImport("starlette").getMember("app") }
|
||||
|
||||
/** Gets a reference to a Starlette application (an instance of `starlette.app`). */
|
||||
API::Node instance() { result = cls().getAnInstance() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to any of the execute methods on a `app.add_middleware`.
|
||||
*/
|
||||
class AddMiddlewareCall extends DataFlow::CallCfgNode {
|
||||
AddMiddlewareCall() {
|
||||
this = [App::instance().getMember("add_middleware").getACall(), Middleware::instance()]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the string corresponding to the middleware
|
||||
*/
|
||||
string getMiddlewareName() { result = this.getArg(0).asExpr().(Name).getId() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to any of the execute methods on a `app.add_middleware` with CORSMiddleware.
|
||||
*/
|
||||
class AddCorsMiddlewareCall extends AddMiddlewareCall, Http::Server::CorsMiddleware::Range {
|
||||
/**
|
||||
* Gets the string corresponding to the middleware
|
||||
*/
|
||||
override string getMiddlewareName() { result = this.getArg(0).asExpr().(Name).getId() }
|
||||
|
||||
override DataFlow::Node getOrigins() { result = this.getArgByName("allow_origins") }
|
||||
|
||||
override DataFlow::Node getCredentialsAllowed() {
|
||||
result = this.getArgByName("allow_credentials")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the dataflow node corresponding to the allowed CORS methods
|
||||
*/
|
||||
DataFlow::Node getMethods() { result = this.getArgByName("allow_methods") }
|
||||
|
||||
/**
|
||||
* Gets the dataflow node corresponding to the allowed CORS headers
|
||||
*/
|
||||
DataFlow::Node getHeaders() { result = this.getArgByName("allow_headers") }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `starlette.middleware.Middleware` class
|
||||
*
|
||||
* See https://www.starlette.io/.
|
||||
*/
|
||||
module Middleware {
|
||||
/** Gets a reference to the `starlette.middleware.Middleware` class. */
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("starlette").getMember("middleware").getMember("Middleware")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("starlette.middleware.Middleware~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `starlette.middleware.Middleware`. */
|
||||
DataFlow::Node instance() { result = classRef().getACall() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `starlette.websockets.WebSocket` class
|
||||
*
|
||||
|
||||
95
python/ql/lib/semmle/python/frameworks/Streamlit.qll
Normal file
95
python/ql/lib/semmle/python/frameworks/Streamlit.qll
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `streamlit` PyPI package.
|
||||
* See https://pypi.org/project/streamlit/.
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.RemoteFlowSources
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import semmle.python.ApiGraphs
|
||||
import semmle.python.Concepts
|
||||
private import semmle.python.frameworks.SqlAlchemy
|
||||
|
||||
/**
|
||||
* Provides models for the `streamlit` PyPI package.
|
||||
* See https://pypi.org/project/streamlit/.
|
||||
*/
|
||||
module Streamlit {
|
||||
/**
|
||||
* The calls to the interactive streamlit widgets, which take untrusted input.
|
||||
*/
|
||||
private class StreamlitInput extends RemoteFlowSource::Range {
|
||||
StreamlitInput() {
|
||||
this =
|
||||
API::moduleImport("streamlit")
|
||||
.getMember(["text_input", "text_area", "chat_input"])
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override string getSourceType() { result = "Streamlit user input" }
|
||||
}
|
||||
|
||||
/**
|
||||
* The Streamlit SQLConnection class, which is used to create a connection to a SQL Database.
|
||||
* Streamlit wraps around SQL Alchemy for most database functionality, and adds some on top of it, such as the `query` method.
|
||||
* Streamlit can also connect to Snowflake and Snowpark databases, but the modeling is not the same, so we need to limit the scope to SQL databases.
|
||||
* https://docs.streamlit.io/develop/api-reference/connections/st.connections.sqlconnection#:~:text=to%20data.-,st.connections.SQLConnection,-Streamlit%20Version
|
||||
* We can connect to SQL databases for example with `import streamlit as st; conn = st.connection('pets_db', type='sql')`
|
||||
*/
|
||||
private class StreamlitSqlConnection extends API::CallNode {
|
||||
StreamlitSqlConnection() {
|
||||
exists(StringLiteral str, API::CallNode n |
|
||||
str.getText() = "sql" and
|
||||
n = API::moduleImport("streamlit").getMember("connection").getACall() and
|
||||
DataFlow::exprNode(str)
|
||||
.(DataFlow::LocalSourceNode)
|
||||
.flowsTo([n.getArg(1), n.getArgByName("type")]) and
|
||||
this = n
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `query` call that can execute raw queries on a connection to a SQL database.
|
||||
* https://docs.streamlit.io/develop/api-reference/connections/st.connection
|
||||
*/
|
||||
private class QueryMethodCall extends DataFlow::CallCfgNode, SqlExecution::Range {
|
||||
QueryMethodCall() {
|
||||
exists(StreamlitSqlConnection s | this = s.getReturn().getMember("query").getACall())
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("sql")] }
|
||||
}
|
||||
|
||||
/**
|
||||
* The Streamlit SQLConnection.connect() call, which returns a a new sqlalchemy.engine.Connection object.
|
||||
* Streamlit creates a connection to a SQL database basing off SQL Alchemy, so we can reuse the models that we already have.
|
||||
*/
|
||||
private class StreamlitSqlAlchemyConnection extends SqlAlchemy::Connection::InstanceSource {
|
||||
StreamlitSqlAlchemyConnection() {
|
||||
exists(StreamlitSqlConnection s | this = s.getReturn().getMember("connect").getACall())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The underlying SQLAlchemy Engine, accessed via `st.connection().engine`.
|
||||
* Streamlit creates an engine to a SQL database basing off SQL Alchemy, so we can reuse the models that we already have.
|
||||
*/
|
||||
private class StreamlitSqlAlchemyEngine extends SqlAlchemy::Engine::InstanceSource {
|
||||
StreamlitSqlAlchemyEngine() {
|
||||
exists(StreamlitSqlConnection s | this = s.getReturn().getMember("engine").asSource())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The SQLAlchemy Session, accessed via `st.connection().session`.
|
||||
* Streamlit can create a session to a SQL database basing off SQL Alchemy, so we can reuse the models that we already have.
|
||||
* For example, the modeling for `session` includes an `execute` method, which is used to execute raw SQL queries.
|
||||
* https://docs.streamlit.io/develop/api-reference/connections/st.connections.sqlconnection#:~:text=SQLConnection.engine-,SQLConnection.session,-Streamlit%20Version
|
||||
*/
|
||||
private class StreamlitSqlSession extends SqlAlchemy::Session::InstanceSource {
|
||||
StreamlitSqlSession() {
|
||||
exists(StreamlitSqlConnection s | this = s.getReturn().getMember("session").asSource())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,50 @@ module Tornado {
|
||||
|
||||
override string getAsyncMethodName() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A dict-like write to an item of an `HTTPHeaders` object.
|
||||
*/
|
||||
private class TornadoHeaderSubscriptWrite extends Http::Server::ResponseHeaderWrite::Range {
|
||||
DataFlow::Node index;
|
||||
DataFlow::Node value;
|
||||
|
||||
TornadoHeaderSubscriptWrite() {
|
||||
exists(SubscriptNode subscript |
|
||||
subscript.getObject() = instance().asCfgNode() and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
index.asCfgNode() = subscript.getIndex() and
|
||||
this.asCfgNode() = subscript
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { result = index }
|
||||
|
||||
override DataFlow::Node getValueArg() { result = value }
|
||||
|
||||
override predicate nameAllowsNewline() { none() }
|
||||
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `HTTPHeaders.add`.
|
||||
*/
|
||||
private class TornadoHeadersAppendCall extends Http::Server::ResponseHeaderWrite::Range,
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
TornadoHeadersAppendCall() { this.calls(instance(), "add") }
|
||||
|
||||
override DataFlow::Node getNameArg() { result = [this.getArg(0), this.getArgByName("name")] }
|
||||
|
||||
override DataFlow::Node getValueArg() {
|
||||
result in [this.getArg(1), this.getArgByName("value")]
|
||||
}
|
||||
|
||||
override predicate nameAllowsNewline() { none() }
|
||||
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -209,6 +253,25 @@ module Tornado {
|
||||
this.(DataFlow::AttrRead).getAttributeName() = "request"
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to `RequestHandler.set_header` or `RequestHandler.add_header` */
|
||||
private class TornadoSetHeaderCall extends Http::Server::ResponseHeaderWrite::Range,
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
TornadoSetHeaderCall() { this.calls(instance(), ["set_header", "add_header"]) }
|
||||
|
||||
override DataFlow::Node getNameArg() {
|
||||
result = [this.getArg(0), this.getArgByName("name")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getValueArg() {
|
||||
result in [this.getArg(1), this.getArgByName("value")]
|
||||
}
|
||||
|
||||
override predicate nameAllowsNewline() { none() }
|
||||
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -529,7 +592,7 @@ module Tornado {
|
||||
*
|
||||
* See https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.set_cookie
|
||||
*/
|
||||
class TornadoRequestHandlerSetCookieCall extends Http::Server::CookieWrite::Range,
|
||||
class TornadoRequestHandlerSetCookieCall extends Http::Server::SetCookieCall,
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
TornadoRequestHandlerSetCookieCall() {
|
||||
|
||||
@@ -235,9 +235,7 @@ private module Twisted {
|
||||
*
|
||||
* See https://twistedmatrix.com/documents/21.2.0/api/twisted.web.http.Request.html#addCookie
|
||||
*/
|
||||
class TwistedRequestAddCookieCall extends Http::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
class TwistedRequestAddCookieCall extends Http::Server::SetCookieCall, DataFlow::MethodCallNode {
|
||||
TwistedRequestAddCookieCall() { this.calls(Twisted::Request::instance(), "addCookie") }
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
|
||||
@@ -45,3 +45,25 @@ extensible predicate typeModel(string type1, string type2, string path);
|
||||
* Holds if `path` can be substituted for a token `TypeVar[name]`.
|
||||
*/
|
||||
extensible predicate typeVariableModel(string name, string path);
|
||||
|
||||
/**
|
||||
* Holds if the given extension tuple `madId` should pretty-print as `model`.
|
||||
*
|
||||
* This predicate should only be used in tests.
|
||||
*/
|
||||
predicate interpretModelForTest(QlBuiltins::ExtensionId madId, string model) {
|
||||
exists(string type, string path, string kind |
|
||||
sourceModel(type, path, kind, madId) and
|
||||
model = "Source: " + type + "; " + path + "; " + kind
|
||||
)
|
||||
or
|
||||
exists(string type, string path, string kind |
|
||||
sinkModel(type, path, kind, madId) and
|
||||
model = "Sink: " + type + "; " + path + "; " + kind
|
||||
)
|
||||
or
|
||||
exists(string type, string path, string input, string output, string kind |
|
||||
summaryModel(type, path, input, output, kind, madId) and
|
||||
model = "Summary: " + type + "; " + path + "; " + input + "; " + output + "; " + kind
|
||||
)
|
||||
}
|
||||
|
||||
@@ -134,9 +134,25 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathTokenBase token) {
|
||||
token.getAnArgument() = "any-named" and
|
||||
result = node.getKeywordParameter(_)
|
||||
)
|
||||
or
|
||||
// content based steps
|
||||
//
|
||||
// note: if we want to migrate to use `FlowSummaryImpl::Input::encodeContent` like
|
||||
// they do in Ruby, be aware that we currently don't make
|
||||
// `DataFlow::DictionaryElementContent` just from seeing a subscript read, so we would
|
||||
// need to add that. (also need to handle things like `DictionaryElementAny` which
|
||||
// doesn't have any value for .getAnArgument())
|
||||
(
|
||||
token.getName() = "DictionaryElement" and
|
||||
result = node.getSubscript(token.getAnArgument())
|
||||
or
|
||||
token.getName() = "DictionaryElementAny" and
|
||||
result = node.getASubscript() and
|
||||
not exists(token.getAnArgument())
|
||||
// TODO: ListElement/SetElement/TupleElement
|
||||
)
|
||||
// Some features don't have MaD tokens yet, they would need to be added to API-graphs first.
|
||||
// - decorators ("DecoratedClass", "DecoratedMember", "DecoratedParameter")
|
||||
// - Array/Map elements ("ArrayElement", "Element", "MapKey", "MapValue")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,7 +258,11 @@ InvokeNode getAnInvocationOf(API::Node node) { result = node.getACall() }
|
||||
*/
|
||||
bindingset[name]
|
||||
predicate isExtraValidTokenNameInIdentifyingAccessPath(string name) {
|
||||
name = ["Member", "Instance", "Awaited", "Call", "Method", "Subclass"]
|
||||
name =
|
||||
[
|
||||
"Member", "Instance", "Awaited", "Call", "Method", "Subclass", "DictionaryElement",
|
||||
"DictionaryElementAny"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,7 +270,7 @@ predicate isExtraValidTokenNameInIdentifyingAccessPath(string name) {
|
||||
* in an identifying access path.
|
||||
*/
|
||||
predicate isExtraValidNoArgumentTokenInIdentifyingAccessPath(string name) {
|
||||
name = ["Instance", "Awaited", "Call", "Subclass"]
|
||||
name = ["Instance", "Awaited", "Call", "Subclass", "DictionaryElementAny"]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -259,7 +279,7 @@ predicate isExtraValidNoArgumentTokenInIdentifyingAccessPath(string name) {
|
||||
*/
|
||||
bindingset[name, argument]
|
||||
predicate isExtraValidTokenArgumentInIdentifyingAccessPath(string name, string argument) {
|
||||
name = ["Member", "Method"] and
|
||||
name = ["Member", "Method", "DictionaryElement"] and
|
||||
exists(argument)
|
||||
or
|
||||
name = ["Argument", "Parameter"] and
|
||||
|
||||
@@ -22,9 +22,10 @@ private import semmle.python.dataflow.new.DataFlow
|
||||
/**
|
||||
* Gets the last decorator call for the function `func`, if `func` has decorators.
|
||||
*/
|
||||
private Expr lastDecoratorCall(Function func) {
|
||||
result = func.getDefinition().(FunctionExpr).getADecoratorCall() and
|
||||
not exists(Call other_decorator | other_decorator.getArg(0) = result)
|
||||
pragma[nomagic]
|
||||
private DataFlow::TypeTrackingNode lastDecoratorCall(Function func) {
|
||||
result.asExpr() = func.getDefinition().(FunctionExpr).getADecoratorCall() and
|
||||
not exists(Call other_decorator | other_decorator.getArg(0) = result.asExpr())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,7 +57,7 @@ private DataFlow::TypeTrackingNode poorMansFunctionTracker(DataFlow::TypeTracker
|
||||
//
|
||||
// Note that this means that we blindly ignore what the decorator actually does to
|
||||
// the function, which seems like an OK tradeoff.
|
||||
result.asExpr() = lastDecoratorCall(func)
|
||||
result = pragma[only_bind_out](lastDecoratorCall(func))
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = poorMansFunctionTracker(t2, func).track(t2, t))
|
||||
|
||||
Reference in New Issue
Block a user