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

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 0a130b38b59 Use hmac.compare_digest for SimpleAuthManager password 
compare (CWE-208) (#66556)
0a130b38b59 is described below

commit 0a130b38b595f1245f9313fa262821f40d61e70c
Author: Jarek Potiuk <[email protected]>
AuthorDate: Thu May 7 19:00:16 2026 +0200

    Use hmac.compare_digest for SimpleAuthManager password compare (CWE-208) 
(#66556)
    
    The SimpleAuthManager login service compared submitted passwords against
    the stored value with `==`, which short-circuits on the first differing
    character and leaks password length / prefix information through timing
    analysis. Switch to `hmac.compare_digest()` so the comparison runs in
    constant time regardless of where the strings diverge, and use
    `.get(user.username, "")` so a user record without a corresponding
    password entry compares against an empty string rather than raising
    `KeyError`.
    
    SimpleAuthManager is dev-only, but the threat model the audit raised
    (timing analysis from a co-located process or local network) is plausible
    even in dev, and the fix is one line. ASVS L1/L2/L3 ยง 6.3.1 / CWE-208.
    
    Tests added for wrong-password and unknown-username rejection.
    
    Reported by the L3 ASVS sweep at apache/tooling-agents#23 (FINDING-008).
---
 .../auth/managers/simple/services/login.py         | 11 ++++++++-
 .../auth/managers/simple/services/test_login.py    | 26 ++++++++++++++++++++++
 2 files changed, 36 insertions(+), 1 deletion(-)

diff --git 
a/airflow-core/src/airflow/api_fastapi/auth/managers/simple/services/login.py 
b/airflow-core/src/airflow/api_fastapi/auth/managers/simple/services/login.py
index 8e64861de1a..799336ae0f8 100644
--- 
a/airflow-core/src/airflow/api_fastapi/auth/managers/simple/services/login.py
+++ 
b/airflow-core/src/airflow/api_fastapi/auth/managers/simple/services/login.py
@@ -17,6 +17,8 @@
 
 from __future__ import annotations
 
+import hmac
+
 from fastapi import HTTPException, status
 
 from airflow.api_fastapi.app import get_auth_manager
@@ -53,10 +55,17 @@ class SimpleAuthManagerLogin:
 
         users = SimpleAuthManager.get_users()
         passwords = SimpleAuthManager.get_passwords()
+        # Use hmac.compare_digest for constant-time password comparison 
(CWE-208).
+        # `passwords.get(..., "")` keeps the comparison constant-time against 
an
+        # empty string when the user record exists but the password entry is 
missing.
         found_users = [
             user
             for user in users
-            if user.username == body.username and passwords[user.username] == 
body.password
+            if user.username == body.username
+            and hmac.compare_digest(
+                passwords.get(user.username, "").encode("utf-8"),
+                body.password.encode("utf-8"),
+            )
         ]
 
         if len(found_users) == 0:
diff --git 
a/airflow-core/tests/unit/api_fastapi/auth/managers/simple/services/test_login.py
 
b/airflow-core/tests/unit/api_fastapi/auth/managers/simple/services/test_login.py
index 3318f803cc7..5b9b4850ff1 100644
--- 
a/airflow-core/tests/unit/api_fastapi/auth/managers/simple/services/test_login.py
+++ 
b/airflow-core/tests/unit/api_fastapi/auth/managers/simple/services/test_login.py
@@ -121,3 +121,29 @@ class TestLogin:
         with pytest.raises(HTTPException) as ex:
             SimpleAuthManagerLogin.create_token_all_admins()
         assert ex.value.status_code == 403
+
+    @pytest.mark.db_test
+    def test_create_token_wrong_password_rejected(self, auth_manager):
+        """Wrong password is rejected via constant-time comparison 
(CWE-208)."""
+        with conf_vars({("core", "simple_auth_manager_users"): 
f"{TEST_USER_1}:{TEST_ROLE_1}"}):
+            auth_manager.init()
+            with pytest.raises(HTTPException) as ex:
+                SimpleAuthManagerLogin.create_token(
+                    body=LoginBody(username=TEST_USER_1, 
password="wrong-password"),
+                    expiration_time_in_seconds=1,
+                )
+            assert ex.value.status_code == 401
+            assert "Invalid credentials" in ex.value.detail
+
+    @pytest.mark.db_test
+    def test_create_token_unknown_user_rejected(self, auth_manager):
+        """Unknown username is rejected without leaking timing info 
(CWE-208)."""
+        with conf_vars({("core", "simple_auth_manager_users"): 
f"{TEST_USER_1}:{TEST_ROLE_1}"}):
+            auth_manager.init()
+            with pytest.raises(HTTPException) as ex:
+                SimpleAuthManagerLogin.create_token(
+                    body=LoginBody(username="nonexistent", 
password="any-password"),
+                    expiration_time_in_seconds=1,
+                )
+            assert ex.value.status_code == 401
+            assert "Invalid credentials" in ex.value.detail

Reply via email to