From fb54159861708558b5f5658ebdc14709d984361c Mon Sep 17 00:00:00 2001 From: James Addison Date: Mon, 10 Mar 2025 16:34:12 +0000 Subject: [PATCH 1/4] secret key rotation: fix key list ordering The `itsdangerous` serializer interface[1] expects keys to be provided with the oldest key at index zero and the active signing key at the end of the list. We document[2] that `SECRET_KEY_FALLBACKS` should be configured with the most recent first (at index zero), so to achieve the expected behaviour, those should be inserted in reverse-order at the head of the list. [1] - https://itsdangerous.palletsprojects.com/en/stable/serializer/#itsdangerous.serializer.Serializer [2] - https://flask.palletsprojects.com/en/stable/config/#SECRET_KEY_FALLBACKS --- CHANGES.rst | 2 ++ src/flask/sessions.py | 3 ++- tests/test_basic.py | 15 +++++++++++---- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 51c99d42..fc474ebb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,8 @@ Version 3.1.1 Unreleased +- Fix signing key selection order when key rotation is enabled via + ``SECRET_KEY_FALLBACKS``. :ghsa:`4grg-w6v8-c28g` - Fix type hint for `cli_runner.invoke`. :issue:`5645` - ``flask --help`` loads the app and plugins first to make sure all commands are shown. :issue:5673` diff --git a/src/flask/sessions.py b/src/flask/sessions.py index 375de065..4ffde713 100644 --- a/src/flask/sessions.py +++ b/src/flask/sessions.py @@ -318,11 +318,12 @@ class SecureCookieSessionInterface(SessionInterface): if not app.secret_key: return None - keys: list[str | bytes] = [app.secret_key] + keys: list[str | bytes] = [] if fallbacks := app.config["SECRET_KEY_FALLBACKS"]: keys.extend(fallbacks) + keys.append(app.secret_key) # itsdangerous expects current key at top return URLSafeTimedSerializer( keys, # type: ignore[arg-type] salt=self.salt, diff --git a/tests/test_basic.py b/tests/test_basic.py index c737dc5f..c372a910 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -381,14 +381,21 @@ def test_session_secret_key_fallbacks(app, client) -> None: def get_session() -> dict[str, t.Any]: return dict(flask.session) - # Set session with initial secret key + # Set session with initial secret key, and two valid expiring keys + app.secret_key, app.config["SECRET_KEY_FALLBACKS"] = ( + "0 key", + ["-1 key", "-2 key"], + ) client.post() assert client.get().json == {"a": 1} # Change secret key, session can't be loaded and appears empty - app.secret_key = "new test key" + app.secret_key = "? key" assert client.get().json == {} - # Add initial secret key as fallback, session can be loaded - app.config["SECRET_KEY_FALLBACKS"] = ["test key"] + # Rotate the valid keys, session can be loaded + app.secret_key, app.config["SECRET_KEY_FALLBACKS"] = ( + "+1 key", + ["0 key", "-1 key"], + ) assert client.get().json == {"a": 1} From cbb6c36692f7d882e9026597624c0eb38e01f9cb Mon Sep 17 00:00:00 2001 From: David Lord Date: Sat, 29 Mar 2025 16:18:43 -0700 Subject: [PATCH 2/4] update docs about fallback order --- docs/config.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 5695bbd0..e7d4410a 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -127,13 +127,16 @@ The following configuration values are used internally by Flask: .. py:data:: SECRET_KEY_FALLBACKS - A list of old secret keys that can still be used for unsigning, most recent - first. This allows a project to implement key rotation without invalidating - active sessions or other recently-signed secrets. + A list of old secret keys that can still be used for unsigning. This allows + a project to implement key rotation without invalidating active sessions or + other recently-signed secrets. Keys should be removed after an appropriate period of time, as checking each additional key adds some overhead. + Order should not matter, but the default implementation will test the last + key in the list first, so it might make sense to order oldest to newest. + Flask's built-in secure cookie session supports this. Extensions that use :data:`SECRET_KEY` may not support this yet. From 7fff56f5172c48b6f3aedf17ee14ef5c2533dfd1 Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 13 May 2025 07:51:12 -0700 Subject: [PATCH 3/4] release version 3.1.1 --- CHANGES.rst | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index fc474ebb..243c56a5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,7 +1,7 @@ Version 3.1.1 ------------- -Unreleased +Released 2025-05-13 - Fix signing key selection order when key rotation is enabled via ``SECRET_KEY_FALLBACKS``. :ghsa:`4grg-w6v8-c28g` diff --git a/pyproject.toml b/pyproject.toml index fcd53c21..19a7aee4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "Flask" -version = "3.1.1.dev" +version = "3.1.1" description = "A simple framework for building complex web applications." readme = "README.md" license = "BSD-3-Clause" diff --git a/uv.lock b/uv.lock index 9fcfb2ae..a89fa5f4 100644 --- a/uv.lock +++ b/uv.lock @@ -387,7 +387,7 @@ wheels = [ [[package]] name = "flask" -version = "3.1.1.dev0" +version = "3.1.1" source = { editable = "." } dependencies = [ { name = "blinker" }, @@ -642,7 +642,7 @@ name = "importlib-metadata" version = "8.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "zipp" }, + { name = "zipp", marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } wheels = [ From bbaf13333fd00644313488243cfe547b13dcae78 Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 13 May 2025 08:09:39 -0700 Subject: [PATCH 4/4] fix syntax --- CHANGES.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 243c56a5..d400ef3e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,9 +5,9 @@ Released 2025-05-13 - Fix signing key selection order when key rotation is enabled via ``SECRET_KEY_FALLBACKS``. :ghsa:`4grg-w6v8-c28g` -- Fix type hint for `cli_runner.invoke`. :issue:`5645` +- Fix type hint for ``cli_runner.invoke``. :issue:`5645` - ``flask --help`` loads the app and plugins first to make sure all commands - are shown. :issue:5673` + are shown. :issue:`5673` - Mark sans-io base class as being able to handle views that return ``AsyncIterable``. This is not accurate for Flask, but makes typing easier for Quart. :pr:`5659`