This is an automated email from the ASF dual-hosted git repository.

arm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


The following commit(s) were added to refs/heads/main by this push:
     new 1d2e7ab  #596 - finite session lifetime by config - 72 hour default.
1d2e7ab is described below

commit 1d2e7ab4239bec5fc3dea81ef97fbb76819616a9
Author: Alastair McFarlane <[email protected]>
AuthorDate: Wed Jan 28 14:32:15 2026 +0000

    #596 - finite session lifetime by config - 72 hour default.
---
 atr/config.py |  1 +
 atr/server.py | 34 ++++++++++++++++++++++++++++++++++
 2 files changed, 35 insertions(+)

diff --git a/atr/config.py b/atr/config.py
index e1ff073..42a834a 100644
--- a/atr/config.py
+++ b/atr/config.py
@@ -68,6 +68,7 @@ def _config_secrets_get(
 
 
 class AppConfig:
+    ABSOLUTE_SESSION_MAX_SECONDS = 
decouple.config("ABSOLUTE_SESSION_MAX_SECONDS", default=60 * 60 * 72, cast=int)
     ALLOW_TESTS = decouple.config("ALLOW_TESTS", default=False, cast=bool)
     DISABLE_CHECK_CACHE = decouple.config("DISABLE_CHECK_CACHE", 
default=False, cast=bool)
     APP_HOST = decouple.config("APP_HOST", default="127.0.0.1")
diff --git a/atr/server.py b/atr/server.py
index 28bb1df..032424e 100644
--- a/atr/server.py
+++ b/atr/server.py
@@ -413,6 +413,40 @@ def _app_setup_request_lifecycle(app: base.QuartApp) -> 
None:
     async def bind_request_context_vars() -> None:
         await _reset_request_log_context()
 
+    @app.before_request
+    async def validate_session_lifetime() -> None:
+        """Enforce absolute maximum session lifetime per ASVS 7.3.2."""
+        session = await asfquart.session.read()
+        if session is None:
+            return
+
+        conf = config.get()
+        max_lifetime_seconds = conf.ABSOLUTE_SESSION_MAX_SECONDS
+        max_lifetime = datetime.timedelta(seconds=max_lifetime_seconds)
+
+        # Check if session has a creation timestamp in metadata
+        created_at_str = session.metadata.get("created_at")
+
+        if created_at_str is None:
+            # First time seeing this session, record creation time
+            session.metadata["created_at"] = 
datetime.datetime.now(datetime.UTC).isoformat()
+            asfquart.session.write(session)
+            return
+
+        # Parse the creation timestamp and check session age
+        try:
+            created_at = datetime.datetime.fromisoformat(created_at_str)
+        except (ValueError, TypeError):
+            # Invalid timestamp, treat as expired
+            asfquart.session.clear()
+            raise base.ASFQuartException("Session expired", errorcode=401)
+
+        session_age = datetime.datetime.now(datetime.UTC) - created_at
+
+        if session_age > max_lifetime:
+            asfquart.session.clear()
+            raise base.ASFQuartException("Session expired", errorcode=401)
+
     @app.after_request
     async def log_request(response: quart.Response) -> quart.Response:
         logger.info(


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to