From d947121efdae0e3be2c791a29c9194b9f3a31cef Mon Sep 17 00:00:00 2001 From: ahmadalguydi Date: Wed, 29 Jul 2026 19:05:29 +0300 Subject: [PATCH 1/2] Reject timed arguments in URLSafeSerializer.loads --- CHANGES.rst | 2 ++ src/itsdangerous/url_safe.py | 21 +++++++++++++++++++++ tests/test_itsdangerous/test_url_safe.py | 8 ++++++++ 3 files changed, 31 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 17c1a196..982db5ff 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,8 @@ Unreleased - Drop support for Python 3.8 and 3.9. - Remove previously deprecated code. +- Make ``URLSafeSerializer.loads`` reject unexpected timed-load arguments + instead of silently ignoring them. :issue:`429` Version 2.2.0 diff --git a/src/itsdangerous/url_safe.py b/src/itsdangerous/url_safe.py index 56a07933..3583707b 100644 --- a/src/itsdangerous/url_safe.py +++ b/src/itsdangerous/url_safe.py @@ -75,6 +75,27 @@ class URLSafeSerializer(URLSafeSerializerMixin, Serializer[str]): alphabet as well as ``'_'``, ``'-'`` and ``'.'``. """ + @t.overload + def loads(self, s: str | bytes, salt: str | bytes | None = None) -> t.Any: ... + + def loads( + self, s: str | bytes, salt: str | bytes | None = None, **kwargs: t.Any + ) -> t.Any: + """Reverse of :meth:`dumps` without timed-load arguments. + + Keep the signature explicit so arguments intended for + :class:`URLSafeTimedSerializer`, such as ``max_age``, fail instead of + being silently ignored. + """ + if kwargs: + unexpected = next(iter(kwargs)) + raise TypeError( + "URLSafeSerializer.loads() got an unexpected keyword argument " + f"{unexpected!r}" + ) + + return super().loads(s, salt) + class URLSafeTimedSerializer(URLSafeSerializerMixin, TimedSerializer[str]): """Works like :class:`.TimedSerializer` but dumps and loads into a diff --git a/tests/test_itsdangerous/test_url_safe.py b/tests/test_itsdangerous/test_url_safe.py index 37e48123..77236de2 100644 --- a/tests/test_itsdangerous/test_url_safe.py +++ b/tests/test_itsdangerous/test_url_safe.py @@ -22,3 +22,11 @@ class TestURLSafeTimedSerializer(TestURLSafeSerializer, TestTimedSerializer): @pytest.fixture() def serializer_factory(self): return partial(URLSafeTimedSerializer, secret_key="secret-key") + + +def test_url_safe_serializer_rejects_timed_arguments(): + serializer = URLSafeSerializer("secret-key") + signed = serializer.dumps("value") + + with pytest.raises(TypeError, match="max_age"): + serializer.loads(signed, max_age=60) From f1f4c24445ea2d3463b37fe1a242466c6c7d5db7 Mon Sep 17 00:00:00 2001 From: ahmadalguydi Date: Wed, 29 Jul 2026 19:08:51 +0300 Subject: [PATCH 2/2] Fix URLSafeSerializer typing override --- src/itsdangerous/url_safe.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/itsdangerous/url_safe.py b/src/itsdangerous/url_safe.py index 3583707b..1117eba6 100644 --- a/src/itsdangerous/url_safe.py +++ b/src/itsdangerous/url_safe.py @@ -75,11 +75,8 @@ class URLSafeSerializer(URLSafeSerializerMixin, Serializer[str]): alphabet as well as ``'_'``, ``'-'`` and ``'.'``. """ - @t.overload - def loads(self, s: str | bytes, salt: str | bytes | None = None) -> t.Any: ... - - def loads( - self, s: str | bytes, salt: str | bytes | None = None, **kwargs: t.Any + def loads( # type: ignore[override] + self, s: str | bytes, salt: str | bytes | None = None ) -> t.Any: """Reverse of :meth:`dumps` without timed-load arguments. @@ -87,13 +84,6 @@ def loads( :class:`URLSafeTimedSerializer`, such as ``max_age``, fail instead of being silently ignored. """ - if kwargs: - unexpected = next(iter(kwargs)) - raise TypeError( - "URLSafeSerializer.loads() got an unexpected keyword argument " - f"{unexpected!r}" - ) - return super().loads(s, salt)