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

vincbeck 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 21332eab89a Use secrets for fab users `--use-random-password` 
generation (#72092)
21332eab89a is described below

commit 21332eab89ad01bbeecf6eef2be4ca129f08dc81
Author: Samina <[email protected]>
AuthorDate: Wed Aug 26 19:16:59 2026 +0530

    Use secrets for fab users `--use-random-password` generation (#72092)
    
    random.choices draws from the Mersenne Twister PRNG that Python documents 
as unfit for security use, so the generated web-login credential is 
predictable, and string.printable also seeds the value with whitespace and 
control characters that land in the stored password.
---
 .../fab/auth_manager/cli_commands/user_command.py        |  5 +++--
 .../fab/auth_manager/cli_commands/test_user_command.py   | 16 ++++++++++++++++
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git 
a/providers/fab/src/airflow/providers/fab/auth_manager/cli_commands/user_command.py
 
b/providers/fab/src/airflow/providers/fab/auth_manager/cli_commands/user_command.py
index 4ae060e144f..adecdb458ff 100644
--- 
a/providers/fab/src/airflow/providers/fab/auth_manager/cli_commands/user_command.py
+++ 
b/providers/fab/src/airflow/providers/fab/auth_manager/cli_commands/user_command.py
@@ -22,8 +22,8 @@ import functools
 import getpass
 import json
 import os
-import random
 import re
+import secrets
 import string
 from typing import Any
 
@@ -113,7 +113,8 @@ def user_reset_password(args):
 
 def _create_password(args):
     if args.use_random_password:
-        password = "".join(random.choices(string.printable, k=16))
+        characters = string.ascii_letters + string.digits + string.punctuation
+        password = "".join(secrets.choice(characters) for _ in range(16))
     elif args.password:
         password = args.password
     else:
diff --git 
a/providers/fab/tests/unit/fab/auth_manager/cli_commands/test_user_command.py 
b/providers/fab/tests/unit/fab/auth_manager/cli_commands/test_user_command.py
index 0368d5b83ea..ef2664c8e9b 100644
--- 
a/providers/fab/tests/unit/fab/auth_manager/cli_commands/test_user_command.py
+++ 
b/providers/fab/tests/unit/fab/auth_manager/cli_commands/test_user_command.py
@@ -16,13 +16,17 @@
 # under the License.
 from __future__ import annotations
 
+import argparse
 import json
 import os
 import re
+import secrets
+import string
 import tempfile
 from contextlib import redirect_stdout
 from importlib import reload
 from io import StringIO
+from unittest import mock
 
 import pytest
 
@@ -39,6 +43,18 @@ TEST_USER2_EMAIL = "[email protected]"
 TEST_USER3_EMAIL = "[email protected]"
 
 
[email protected](
+    
"airflow.providers.fab.auth_manager.cli_commands.user_command.secrets.choice",
+    side_effect=secrets.choice,
+)
+def test_create_password_uses_csprng_without_whitespace(mock_choice):
+    args = argparse.Namespace(use_random_password=True, password=None)
+    password = user_command._create_password(args)
+    assert mock_choice.call_count == 16
+    assert len(password) == 16
+    assert not any(char in string.whitespace for char in password)
+
+
 def _does_user_belong_to_role(appbuilder, email, rolename):
     user = appbuilder.sm.find_user(email=email)
     role = appbuilder.sm.find_role(rolename)

Reply via email to