[oe][meta-python][scarthgap][PATCH] python3-flask: fix CVE-2026-27205

"Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)" <[email protected]>
Newsgroups org.openembedded.lists.openembedded-devel
Message-ID <[email protected]>
From: Darsh Kelaiya <[email protected]>

This patch applies the upstream fix as referenced in [2], using the
commit shown in [1].

[1] https://github.com/pallets/flask/commit/c17f379390731543eea33a570a47bd4ef76a54fa
[2] https://github.com/pallets/flask/security/advisories/GHSA-68rp-wp8r-4726

Signed-off-by: Darsh Kelaiya <[email protected]>
---
 .../python/python3-flask/CVE-2026-27205.patch | 260 ++++++++++++++++++
 .../python/python3-flask_3.0.3.bb             |   2 +
 2 files changed, 262 insertions(+)
 create mode 100644 meta-python/recipes-devtools/python/python3-flask/CVE-2026-27205.patch

diff --git a/meta-python/recipes-devtools/python/python3-flask/CVE-2026-27205.patch b/meta-python/recipes-devtools/python/python3-flask/CVE-2026-27205.patch
new file mode 100644
index 0000000000..05d6552d2b
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-flask/CVE-2026-27205.patch
@@ -0,0 +1,260 @@
+From 40ed97132b22a99bee3c8b7d819c2113a767aabe Mon Sep 17 00:00:00 2001
+From: David Lord <[email protected]>
+Date: Wed, 18 Feb 2026 19:02:54 -0800
+Subject: [PATCH] request context tracks session access
+
+CVE: CVE-2026-27205
+Upstream-Status: Backport [https://github.com/pallets/flask/commit/c17f379390731543eea33a570a47bd4ef76a54fa]
+
+Backport Changes:
+- Omitted CHANGES.rst because the upstream advisory entry targets the
+  Flask 3.1.3 release section, which is absent from Scarthgap 3.0.3 and
+  is not required for the runtime fix.
+- Retained the Flask 3.0.3 SecureCookieSession.__init__() t.Any
+  annotation because upstream's collections.abc.Mapping typing layout
+  is not present in this version. The session-access behavior and all
+  regression tests are preserved unchanged.
+
+(cherry picked from commit c17f379390731543eea33a570a47bd4ef76a54fa)
+Signed-off-by: Darsh Kelaiya <[email protected]>
+---
+ src/flask/app.py        |  4 +--
+ src/flask/ctx.py        | 22 +++++++++++-----
+ src/flask/sessions.py   | 32 +++++++----------------
+ src/flask/templating.py |  7 ++---
+ tests/test_basic.py     | 57 ++++++++++++++++++++++++++++-------------
+ 5 files changed, 70 insertions(+), 52 deletions(-)
+
+diff --git a/src/flask/app.py b/src/flask/app.py
+index 7622b5e8..d537ad80 100644
+--- a/src/flask/app.py
++++ b/src/flask/app.py
+@@ -1280,8 +1280,8 @@ class Flask(App):
+                 for func in reversed(self.after_request_funcs[name]):
+                     response = self.ensure_sync(func)(response)
+ 
+-        if not self.session_interface.is_null_session(ctx.session):
+-            self.session_interface.save_session(self, ctx.session, response)
++        if not self.session_interface.is_null_session(ctx._session):
++            self.session_interface.save_session(self, ctx._session, response)
+ 
+         return response
+ 
+diff --git a/src/flask/ctx.py b/src/flask/ctx.py
+index 9b164d39..cf285bc0 100644
+--- a/src/flask/ctx.py
++++ b/src/flask/ctx.py
+@@ -324,7 +324,7 @@ class RequestContext:
+         except HTTPException as e:
+             self.request.routing_exception = e
+         self.flashes: list[tuple[str, str]] | None = None
+-        self.session: SessionMixin | None = session
++        self._session: SessionMixin | None = session
+         # Functions that should be executed after the request on the response
+         # object.  These will be called before the regular "after_request"
+         # functions.
+@@ -351,7 +351,7 @@ class RequestContext:
+             self.app,
+             environ=self.request.environ,
+             request=self.request,
+-            session=self.session,
++            session=self._session,
+         )
+ 
+     def match_request(self) -> None:
+@@ -364,6 +364,16 @@ class RequestContext:
+         except HTTPException as e:
+             self.request.routing_exception = e
+ 
++    @property
++    def session(self) -> SessionMixin:
++        """The session data associated with this request. Not available until
++        this context has been pushed. Accessing this property, also accessed by
++        the :data:`~flask.session` proxy, sets :attr:`.SessionMixin.accessed`.
++        """
++        assert self._session is not None, "The session has not yet been opened."
++        self._session.accessed = True
++        return self._session
++
+     def push(self) -> None:
+         # Before we push the request context we have to ensure that there
+         # is an application context.
+@@ -381,12 +391,12 @@ class RequestContext:
+         # This allows a custom open_session method to use the request context.
+         # Only open a new session if this is the first time the request was
+         # pushed, otherwise stream_with_context loses the session.
+-        if self.session is None:
++        if self._session is None:
+             session_interface = self.app.session_interface
+-            self.session = session_interface.open_session(self.app, self.request)
++            self._session = session_interface.open_session(self.app, self.request)
+ 
+-            if self.session is None:
+-                self.session = session_interface.make_null_session(self.app)
++            if self._session is None:
++                self._session = session_interface.make_null_session(self.app)
+ 
+         # Match the request URL after loading the session, so that the
+         # session is available in custom URL converters.
+diff --git a/src/flask/sessions.py b/src/flask/sessions.py
+index ee19ad63..1e70ef7b 100644
+--- a/src/flask/sessions.py
++++ b/src/flask/sessions.py
+@@ -43,10 +43,15 @@ class SessionMixin(MutableMapping):  # type: ignore[type-arg]
+     #: ``True``.
+     modified = True
+ 
+-    #: Some implementations can detect when session data is read or
+-    #: written and set this when that happens. The mixin default is hard
+-    #: coded to ``True``.
+-    accessed = True
++    accessed = False
++    """Indicates if the session was accessed, even if it was not modified. This
++    is set when the session object is accessed through the request context,
++    including the global :data:`.session` proxy. A ``Vary: cookie`` header will
++    be added if this is ``True``.
++
++    .. versionchanged:: 3.1.3
++        This is tracked by the request context.
++    """
+ 
+ 
+ # TODO generic when Python > 3.8
+@@ -66,31 +71,12 @@ class SecureCookieSession(CallbackDict, SessionMixin):  # type: ignore[type-arg]
+     #: will only be written to the response if this is ``True``.
+     modified = False
+ 
+-    #: When data is read or written, this is set to ``True``. Used by
+-    # :class:`.SecureCookieSessionInterface` to add a ``Vary: Cookie``
+-    #: header, which allows caching proxies to cache different pages for
+-    #: different users.
+-    accessed = False
+-
+     def __init__(self, initial: t.Any = None) -> None:
+         def on_update(self: te.Self) -> None:
+             self.modified = True
+-            self.accessed = True
+ 
+         super().__init__(initial, on_update)
+ 
+-    def __getitem__(self, key: str) -> t.Any:
+-        self.accessed = True
+-        return super().__getitem__(key)
+-
+-    def get(self, key: str, default: t.Any = None) -> t.Any:
+-        self.accessed = True
+-        return super().get(key, default)
+-
+-    def setdefault(self, key: str, default: t.Any = None) -> t.Any:
+-        self.accessed = True
+-        return super().setdefault(key, default)
+-
+ 
+ class NullSession(SecureCookieSession):
+     """Class used to generate nicer error messages if sessions are not
+diff --git a/src/flask/templating.py b/src/flask/templating.py
+index 618a3b35..5f7480da 100644
+--- a/src/flask/templating.py
++++ b/src/flask/templating.py
+@@ -22,8 +22,8 @@ if t.TYPE_CHECKING:  # pragma: no cover
+ 
+ 
+ def _default_template_ctx_processor() -> dict[str, t.Any]:
+-    """Default template context processor.  Injects `request`,
+-    `session` and `g`.
++    """Default template context processor.  Replaces the ``request`` and ``g``
++    proxies with their concrete objects for faster access.
+     """
+     appctx = _cv_app.get(None)
+     reqctx = _cv_request.get(None)
+@@ -32,7 +32,8 @@ def _default_template_ctx_processor() -> dict[str, t.Any]:
+         rv["g"] = appctx.g
+     if reqctx is not None:
+         rv["request"] = reqctx.request
+-        rv["session"] = reqctx.session
++        # The session proxy cannot be replaced, accessing it gets
++        # RequestContext.session, which sets session.accessed.
+     return rv
+ 
+ 
+diff --git a/tests/test_basic.py b/tests/test_basic.py
+index 214cfee0..754fe589 100644
+--- a/tests/test_basic.py
++++ b/tests/test_basic.py
+@@ -18,6 +18,8 @@ from werkzeug.routing import BuildError
+ from werkzeug.routing import RequestRedirect
+ 
+ import flask
++from flask.globals import request_ctx
++from flask.testing import FlaskClient
+ 
+ require_cpython_gc = pytest.mark.skipif(
+     python_implementation() != "CPython",
+@@ -229,27 +231,46 @@ def test_endpoint_decorator(app, client):
+     assert client.get("/foo/bar").data == b"bar"
+ 
+ 
+-def test_session(app, client):
+-    @app.route("/set", methods=["POST"])
+-    def set():
+-        assert not flask.session.accessed
+-        assert not flask.session.modified
++def test_session_accessed(app: flask.Flask, client: FlaskClient) -> None:
++    @app.post("/")
++    def do_set():
+         flask.session["value"] = flask.request.form["value"]
+-        assert flask.session.accessed
+-        assert flask.session.modified
+         return "value set"
+ 
+-    @app.route("/get")
+-    def get():
+-        assert not flask.session.accessed
+-        assert not flask.session.modified
+-        v = flask.session.get("value", "None")
+-        assert flask.session.accessed
+-        assert not flask.session.modified
+-        return v
+-
+-    assert client.post("/set", data={"value": "42"}).data == b"value set"
+-    assert client.get("/get").data == b"42"
++    @app.get("/")
++    def do_get():
++        return flask.session.get("value", "None")
++
++    @app.get("/nothing")
++    def do_nothing() -> str:
++        return ""
++
++    with client:
++        rv = client.get("/nothing")
++        assert "cookie" not in rv.vary
++        assert not request_ctx._session.accessed
++        assert not request_ctx._session.modified
++
++    with client:
++        rv = client.post(data={"value": "42"})
++        assert rv.text == "value set"
++        assert "cookie" in rv.vary
++        assert request_ctx._session.accessed
++        assert request_ctx._session.modified
++
++    with client:
++        rv = client.get()
++        assert rv.text == "42"
++        assert "cookie" in rv.vary
++        assert request_ctx._session.accessed
++        assert not request_ctx._session.modified
++
++    with client:
++        rv = client.get("/nothing")
++        assert rv.text == ""
++        assert "cookie" not in rv.vary
++        assert not request_ctx._session.accessed
++        assert not request_ctx._session.modified
+ 
+ 
+ def test_session_path(app, client):
+-- 
+2.44.4
+
diff --git a/meta-python/recipes-devtools/python/python3-flask_3.0.3.bb b/meta-python/recipes-devtools/python/python3-flask_3.0.3.bb
index b68946ba0c..6e9db196ae 100644
--- a/meta-python/recipes-devtools/python/python3-flask_3.0.3.bb
+++ b/meta-python/recipes-devtools/python/python3-flask_3.0.3.bb
@@ -23,3 +23,5 @@ RDEPENDS:${PN} = " \
     python3-profile \
     python3-werkzeug \
 "
+
+SRC_URI += "file://CVE-2026-27205.patch"
-- 
2.35.6
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.