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

shahar1 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 7dfa25664a2 Fix SFTPHookAsync letting a connection's known_hosts extra 
override an explicit constructor path (#73593)
7dfa25664a2 is described below

commit 7dfa25664a2eec1fe5052790ada7977ada12ec8a
Author: namanjain24-sudo <[email protected]>
AuthorDate: Thu Sep 24 01:26:55 2026 +0530

    Fix SFTPHookAsync letting a connection's known_hosts extra override an 
explicit constructor path (#73593)
    
    _parse_extras() decided whether to take known_hosts from the connection's
    extra by comparing the already-expanded self.known_hosts against the
    literal, un-expanded "~/.ssh/known_hosts" default. Since __init__ always
    runs known_hosts through os.path.expanduser(), the two values can never
    be equal, so the comparison was always true: a connection-level
    known_hosts extra silently replaced even a path passed explicitly to the
    constructor.
    
    SSHHookAsync gets this right elsewhere in the same file, by expanding the
    default before comparing and only falling back to the extra when the
    constructor was left at that default. This applies the same pattern to
    SFTPHookAsync.
---
 .../sftp/src/airflow/providers/sftp/hooks/sftp.py  |  6 +++--
 providers/sftp/tests/unit/sftp/hooks/test_sftp.py  | 28 ++++++++++++++++++++++
 2 files changed, 32 insertions(+), 2 deletions(-)

diff --git a/providers/sftp/src/airflow/providers/sftp/hooks/sftp.py 
b/providers/sftp/src/airflow/providers/sftp/hooks/sftp.py
index f89ff3553a8..bab90ca031d 100644
--- a/providers/sftp/src/airflow/providers/sftp/hooks/sftp.py
+++ b/providers/sftp/src/airflow/providers/sftp/hooks/sftp.py
@@ -878,8 +878,10 @@ class SFTPHookAsync(BaseHook):
         extra_options = conn.extra_dejson
         if "key_file" in extra_options and self.key_file == "":
             self.key_file = extra_options["key_file"]
-        if "known_hosts" in extra_options and self.known_hosts != 
self.default_known_hosts:
-            self.known_hosts = extra_options["known_hosts"]
+        if "known_hosts" in extra_options:
+            expanded_default = os.path.expanduser(self.default_known_hosts)
+            if self.known_hosts == expanded_default:
+                self.known_hosts = extra_options["known_hosts"]
         if "passphrase" in extra_options or "private_key_passphrase" in 
extra_options:
             self.passphrase = extra_options.get("passphrase") or 
extra_options.get(
                 "private_key_passphrase", ""
diff --git a/providers/sftp/tests/unit/sftp/hooks/test_sftp.py 
b/providers/sftp/tests/unit/sftp/hooks/test_sftp.py
index 7b51940172a..cf074a4a30c 100644
--- a/providers/sftp/tests/unit/sftp/hooks/test_sftp.py
+++ b/providers/sftp/tests/unit/sftp/hooks/test_sftp.py
@@ -786,6 +786,34 @@ class MockAirflowConnectionWithPrivate:
 
 
 class TestSFTPHookAsync:
+    @patch("asyncssh.connect", new_callable=AsyncMock)
+    @patch("airflow.providers.sftp.hooks.sftp.get_async_connection")
+    @pytest.mark.asyncio
+    async def 
test_explicit_known_hosts_constructor_arg_is_not_overridden_by_connection_extra(
+        self, mock_get_connection, mock_connect
+    ):
+        """
+        An explicit `known_hosts` passed to the constructor must win over the 
connection's
+        `known_hosts` extra. `_parse_extras` should only fall back to the 
extra when the
+        constructor was left at its default, mirroring `SSHHookAsync`.
+
+        `no_host_key_check` is pinned to `False` so that unrelated 
default-skip behavior
+        does not also overwrite `known_hosts`, which would mask what this test 
checks.
+        """
+        mock_get_connection.return_value = SimpleNamespace(
+            host="localhost",
+            port=22,
+            login="username",
+            password="password",
+            extra="{}",
+            extra_dejson={"known_hosts": "/connection/known_hosts", 
"no_host_key_check": False},
+        )
+
+        hook = SFTPHookAsync(known_hosts="/explicit/known_hosts")
+        await hook._get_conn()
+
+        assert mock_connect.call_args.kwargs["known_hosts"] == 
"/explicit/known_hosts"
+
     @patch("asyncssh.connect", new_callable=AsyncMock)
     @patch("airflow.providers.sftp.hooks.sftp.get_async_connection")
     @pytest.mark.asyncio

Reply via email to