From 0cc12f4d830990300d42a703671c271fe2152e61 Mon Sep 17 00:00:00 2001 From: yoff Date: Tue, 19 May 2026 17:52:17 +0200 Subject: [PATCH 1/5] Python: Add test for instances --- .../experimental/meta/InlineInstanceTest.qll | 29 +++++++++++++++++++ .../flask/InlineInstanceTest.expected | 0 .../frameworks/flask/InlineInstanceTest.ql | 8 +++++ .../frameworks/flask/old_test.py | 2 +- .../frameworks/flask/response_test.py | 2 +- .../frameworks/flask/routing_test.py | 2 +- .../frameworks/flask/save_uploaded_file.py | 2 +- .../frameworks/flask/taint_test.py | 2 +- .../frameworks/flask/template_test.py | 2 +- 9 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 python/ql/test/experimental/meta/InlineInstanceTest.qll create mode 100644 python/ql/test/library-tests/frameworks/flask/InlineInstanceTest.expected create mode 100644 python/ql/test/library-tests/frameworks/flask/InlineInstanceTest.ql diff --git a/python/ql/test/experimental/meta/InlineInstanceTest.qll b/python/ql/test/experimental/meta/InlineInstanceTest.qll new file mode 100644 index 000000000000..d89f6c5faad7 --- /dev/null +++ b/python/ql/test/experimental/meta/InlineInstanceTest.qll @@ -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 { + 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 +} diff --git a/python/ql/test/library-tests/frameworks/flask/InlineInstanceTest.expected b/python/ql/test/library-tests/frameworks/flask/InlineInstanceTest.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/frameworks/flask/InlineInstanceTest.ql b/python/ql/test/library-tests/frameworks/flask/InlineInstanceTest.ql new file mode 100644 index 000000000000..10dff72385a3 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/flask/InlineInstanceTest.ql @@ -0,0 +1,8 @@ +import python +import semmle.python.frameworks.Flask +import semmle.python.ApiGraphs +import experimental.meta.InlineInstanceTest + +API::Node getInstance() { result = Flask::FlaskApp::instance() } + +import MakeInlineInstanceTest diff --git a/python/ql/test/library-tests/frameworks/flask/old_test.py b/python/ql/test/library-tests/frameworks/flask/old_test.py index 4c1ee89b5300..48b3b7c7f29d 100644 --- a/python/ql/test/library-tests/frameworks/flask/old_test.py +++ b/python/ql/test/library-tests/frameworks/flask/old_test.py @@ -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 diff --git a/python/ql/test/library-tests/frameworks/flask/response_test.py b/python/ql/test/library-tests/frameworks/flask/response_test.py index e775239d6423..7491c6d3e9c7 100644 --- a/python/ql/test/library-tests/frameworks/flask/response_test.py +++ b/python/ql/test/library-tests/frameworks/flask/response_test.py @@ -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" diff --git a/python/ql/test/library-tests/frameworks/flask/routing_test.py b/python/ql/test/library-tests/frameworks/flask/routing_test.py index 1bd8de5e7dee..837cb34d293e 100644 --- a/python/ql/test/library-tests/frameworks/flask/routing_test.py +++ b/python/ql/test/library-tests/frameworks/flask/routing_test.py @@ -1,7 +1,7 @@ import flask from flask import Flask, make_response -app = Flask(__name__) +app = Flask(__name__) # $ instance SOME_ROUTE = "/some/route" diff --git a/python/ql/test/library-tests/frameworks/flask/save_uploaded_file.py b/python/ql/test/library-tests/frameworks/flask/save_uploaded_file.py index 502d4cdcbc28..691029844d0d 100644 --- a/python/ql/test/library-tests/frameworks/flask/save_uploaded_file.py +++ b/python/ql/test/library-tests/frameworks/flask/save_uploaded_file.py @@ -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 diff --git a/python/ql/test/library-tests/frameworks/flask/taint_test.py b/python/ql/test/library-tests/frameworks/flask/taint_test.py index 85637d60f428..9cdee60ef584 100644 --- a/python/ql/test/library-tests/frameworks/flask/taint_test.py +++ b/python/ql/test/library-tests/frameworks/flask/taint_test.py @@ -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//") # $ routeSetup="/test_taint//" def test_taint(name = "World!", number="0", foo="foo"): # $ requestHandler routedParameter=name routedParameter=number diff --git a/python/ql/test/library-tests/frameworks/flask/template_test.py b/python/ql/test/library-tests/frameworks/flask/template_test.py index b10dd3e6645a..2c482e9cb82d 100644 --- a/python/ql/test/library-tests/frameworks/flask/template_test.py +++ b/python/ql/test/library-tests/frameworks/flask/template_test.py @@ -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 From 588cb7b8ff7f48e130042df57d8be4ef25a6c02d Mon Sep 17 00:00:00 2001 From: yoff Date: Tue, 19 May 2026 17:55:17 +0200 Subject: [PATCH 2/5] Python: add test for sub class --- .../frameworks/flask/flask_subclass.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 python/ql/test/library-tests/frameworks/flask/flask_subclass.py diff --git a/python/ql/test/library-tests/frameworks/flask/flask_subclass.py b/python/ql/test/library-tests/frameworks/flask/flask_subclass.py new file mode 100644 index 000000000000..03199077989e --- /dev/null +++ b/python/ql/test/library-tests/frameworks/flask/flask_subclass.py @@ -0,0 +1,14 @@ +from flask import Flask + + +class Sub(Flask): + def __init__(self, *args, **kwargs): + Flask.__init__(self, *args, **kwargs) + + +app = Sub(__name__) # $ MISSING: instance + + +@app.route("/") +def hello(): + return "world" \ No newline at end of file From 13c9d60cf3b34a3b9e1f8b50737072a74c19f68b Mon Sep 17 00:00:00 2001 From: yoff Date: Tue, 19 May 2026 18:03:52 +0200 Subject: [PATCH 3/5] Python: fix sub class test --- .../ql/lib/semmle/python/frameworks/Flask.qll | 17 ++++++++++++----- .../frameworks/flask/flask_subclass.py | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/python/ql/lib/semmle/python/frameworks/Flask.qll b/python/ql/lib/semmle/python/frameworks/Flask.qll index f819e8679075..cdfd1b454a1d 100644 --- a/python/ql/lib/semmle/python/frameworks/Flask.qll +++ b/python/ql/lib/semmle/python/frameworks/Flask.qll @@ -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() } } /** @@ -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*() } diff --git a/python/ql/test/library-tests/frameworks/flask/flask_subclass.py b/python/ql/test/library-tests/frameworks/flask/flask_subclass.py index 03199077989e..0fccd847266f 100644 --- a/python/ql/test/library-tests/frameworks/flask/flask_subclass.py +++ b/python/ql/test/library-tests/frameworks/flask/flask_subclass.py @@ -6,7 +6,7 @@ def __init__(self, *args, **kwargs): Flask.__init__(self, *args, **kwargs) -app = Sub(__name__) # $ MISSING: instance +app = Sub(__name__) # $ instance @app.route("/") From f2d018ffee2c284157e1495cea7b169092ed64ff Mon Sep 17 00:00:00 2001 From: yoff Date: Tue, 19 May 2026 18:13:29 +0200 Subject: [PATCH 4/5] Python: Add change note --- python/ql/lib/change-notes/2026-05-19-flask-subclasses.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 python/ql/lib/change-notes/2026-05-19-flask-subclasses.md diff --git a/python/ql/lib/change-notes/2026-05-19-flask-subclasses.md b/python/ql/lib/change-notes/2026-05-19-flask-subclasses.md new file mode 100644 index 000000000000..e2c5db289c6c --- /dev/null +++ b/python/ql/lib/change-notes/2026-05-19-flask-subclasses.md @@ -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. \ No newline at end of file From 7cd7958680bd1afc7b6d7e3a32fc36032cdd8a1a Mon Sep 17 00:00:00 2001 From: yoff Date: Thu, 21 May 2026 08:46:52 +0200 Subject: [PATCH 5/5] Python: update consumer --- python/ql/src/meta/ClassHierarchy/Find.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ql/src/meta/ClassHierarchy/Find.ql b/python/ql/src/meta/ClassHierarchy/Find.ql index 2c474cb21028..e13c683b6f10 100644 --- a/python/ql/src/meta/ClassHierarchy/Find.ql +++ b/python/ql/src/meta/ClassHierarchy/Find.ql @@ -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 {