Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions python/ql/lib/change-notes/2026-05-19-flask-subclasses.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* `Flask::instance` will now also return instances of subclasses defined in te source tree. Previously, these were filtered out. `Flask::classRef` has been deprecated in favor of `Flask::subclassRef` since it already returned some subclasses.
17 changes: 12 additions & 5 deletions python/ql/lib/semmle/python/frameworks/Flask.qll
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,21 @@ module Flask {
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.
*/
module FlaskApp {
/** Gets a reference to the `flask.Flask` class. */
API::Node classRef() {
result = API::moduleImport("flask").getMember("Flask") or
/**
* Gets a reference to the `flask.Flask` class or any subclass.
*
* Deprecated: Use `subclassRef()` instead, this predicate always returned some subclasses.
*/
deprecated API::Node classRef() { result = subclassRef() }

/** Gets a reference to the `flask.Flask` class or any subclass. */
API::Node subclassRef() {
result = API::moduleImport("flask").getMember("Flask").getASubclass*() or
result = ModelOutput::getATypeNode("flask.Flask~Subclass").getASubclass*()
}

/** Gets a reference to an instance of `flask.Flask` (a flask application). */
API::Node instance() { result = classRef().getReturn() }
API::Node instance() { result = subclassRef().getReturn() }
}

/**
Expand Down Expand Up @@ -132,7 +139,7 @@ module Flask {
API::Node classRef() {
result = API::moduleImport("flask").getMember("Response")
or
result = [FlaskApp::classRef(), FlaskApp::instance()].getMember("response_class")
result = [FlaskApp::subclassRef(), FlaskApp::instance()].getMember("response_class")
or
result = ModelOutput::getATypeNode("flask.Response~Subclass").getASubclass*()
}
Expand Down
2 changes: 1 addition & 1 deletion python/ql/src/meta/ClassHierarchy/Find.ql
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ class DjangoHttpRequest extends FindSubclassesSpec {
class FlaskClass extends FindSubclassesSpec {
FlaskClass() { this = "flask.Flask~Subclass" }

override API::Node getAlreadyModeledClass() { result = Flask::FlaskApp::classRef() }
override API::Node getAlreadyModeledClass() { result = Flask::FlaskApp::subclassRef() }
}

class FlaskBlueprint extends FindSubclassesSpec {
Expand Down
29 changes: 29 additions & 0 deletions python/ql/test/experimental/meta/InlineInstanceTest.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Defines a InlineExpectationsTest for class instances, that is,
* for any API::Node that is an instance of a class (e.g. `Flask`).
*/

import python
import semmle.python.ApiGraphs
import utils.test.InlineExpectationsTest
private import semmle.python.dataflow.new.internal.PrintNode

signature API::Node getInstanceSig();

module MakeInlineInstanceTest<getInstanceSig/0 getInstance> {
private module InlineInstanceTest implements TestSig {
string getARelevantTag() { result = "instance" }

predicate hasActualResult(Location location, string element, string tag, string value) {
exists(location.getFile().getRelativePath()) and
exists(API::Node instance | instance = getInstance() |
location = instance.getLocation() and
element = prettyNode(instance.asSource()) and
value = "" and
tag = "instance"
)
}
}

import MakeTest<InlineInstanceTest>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import python
import semmle.python.frameworks.Flask
import semmle.python.ApiGraphs

Check warning

Code scanning / CodeQL

Redundant import Warning test

Redundant import, the module is already imported inside
experimental.meta.InlineInstanceTest
.
import experimental.meta.InlineInstanceTest

API::Node getInstance() { result = Flask::FlaskApp::instance() }

import MakeInlineInstanceTest<getInstance/0>
14 changes: 14 additions & 0 deletions python/ql/test/library-tests/frameworks/flask/flask_subclass.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from flask import Flask


class Sub(Flask):
def __init__(self, *args, **kwargs):
Flask.__init__(self, *args, **kwargs)


app = Sub(__name__) # $ instance


@app.route("/")
def hello():
return "world"
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import flask

from flask import Flask, request, make_response
app = Flask(__name__)
app = Flask(__name__) # $ instance

@app.route("/") # $ routeSetup="/"
def hello_world(): # $ requestHandler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from flask import Flask, make_response, jsonify, Response, request, redirect
from werkzeug.datastructures import Headers

app = Flask(__name__)
app = Flask(__name__) # $ instance


@app.route("/html1") # $ routeSetup="/html1"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import flask

from flask import Flask, make_response
app = Flask(__name__)
app = Flask(__name__) # $ instance


SOME_ROUTE = "/some/route"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from flask import Flask, request
app = Flask(__name__)
app = Flask(__name__) # $ instance

@app.route("/save-uploaded-file") # $ routeSetup="/save-uploaded-file"
def test_taint(): # $ requestHandler
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from flask import Flask, request, render_template_string, stream_template_string
app = Flask(__name__)
app = Flask(__name__) # $ instance

@app.route("/test_taint/<name>/<int:number>") # $ routeSetup="/test_taint/<name>/<int:number>"
def test_taint(name = "World!", number="0", foo="foo"): # $ requestHandler routedParameter=name routedParameter=number
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from flask import Flask, Response, stream_with_context, render_template_string, stream_template_string
app = Flask(__name__)
app = Flask(__name__) # $ instance

@app.route("/a") # $ routeSetup="/a"
def a(): # $ requestHandler
Expand Down
Loading