Skip to content

Commit fb54159

Browse files
jayaddisondavidism
authored andcommitted
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
1 parent 941efd4 commit fb54159

File tree

3 files changed

+15
-5
lines changed

3 files changed

+15
-5
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`

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)