Skip to content

Commit 73d6504

Browse files
authored
Merge commit from fork
Sessions: fix signing key selection when key rotation is enabled
2 parents 941efd4 + cbb6c36 commit 73d6504

File tree

4 files changed

+21
-8
lines changed

4 files changed

+21
-8
lines changed

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ Version 3.1.1
33

44
Unreleased
55

6+
- Fix signing key selection order when key rotation is enabled via
7+
``SECRET_KEY_FALLBACKS``. :ghsa:`4grg-w6v8-c28g`
68
- Fix type hint for `cli_runner.invoke`. :issue:`5645`
79
- ``flask --help`` loads the app and plugins first to make sure all commands
810
are shown. :issue:5673`

docs/config.rst

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,16 @@ The following configuration values are used internally by Flask:
127127

128128
.. py:data:: SECRET_KEY_FALLBACKS
129129
130-
A list of old secret keys that can still be used for unsigning, most recent
131-
first. This allows a project to implement key rotation without invalidating
132-
active sessions or other recently-signed secrets.
130+
A list of old secret keys that can still be used for unsigning. This allows
131+
a project to implement key rotation without invalidating active sessions or
132+
other recently-signed secrets.
133133

134134
Keys should be removed after an appropriate period of time, as checking each
135135
additional key adds some overhead.
136136

137+
Order should not matter, but the default implementation will test the last
138+
key in the list first, so it might make sense to order oldest to newest.
139+
137140
Flask's built-in secure cookie session supports this. Extensions that use
138141
:data:`SECRET_KEY` may not support this yet.
139142

src/flask/sessions.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,11 +318,12 @@ def get_signing_serializer(self, app: Flask) -> URLSafeTimedSerializer | None:
318318
if not app.secret_key:
319319
return None
320320

321-
keys: list[str | bytes] = [app.secret_key]
321+
keys: list[str | bytes] = []
322322

323323
if fallbacks := app.config["SECRET_KEY_FALLBACKS"]:
324324
keys.extend(fallbacks)
325325

326+
keys.append(app.secret_key) # itsdangerous expects current key at top
326327
return URLSafeTimedSerializer(
327328
keys, # type: ignore[arg-type]
328329
salt=self.salt,

tests/test_basic.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -381,14 +381,21 @@ def set_session() -> str:
381381
def get_session() -> dict[str, t.Any]:
382382
return dict(flask.session)
383383

384-
# Set session with initial secret key
384+
# Set session with initial secret key, and two valid expiring keys
385+
app.secret_key, app.config["SECRET_KEY_FALLBACKS"] = (
386+
"0 key",
387+
["-1 key", "-2 key"],
388+
)
385389
client.post()
386390
assert client.get().json == {"a": 1}
387391
# Change secret key, session can't be loaded and appears empty
388-
app.secret_key = "new test key"
392+
app.secret_key = "? key"
389393
assert client.get().json == {}
390-
# Add initial secret key as fallback, session can be loaded
391-
app.config["SECRET_KEY_FALLBACKS"] = ["test key"]
394+
# Rotate the valid keys, session can be loaded
395+
app.secret_key, app.config["SECRET_KEY_FALLBACKS"] = (
396+
"+1 key",
397+
["0 key", "-1 key"],
398+
)
392399
assert client.get().json == {"a": 1}
393400

394401

0 commit comments

Comments
 (0)