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

rawwar pushed a commit to branch kalyan/paramiko
in repository https://gitbox.apache.org/repos/asf/airflow.git

commit c89cae352fb58658e402dc08278ea06b52350077
Author: kalyanr <[email protected]>
AuthorDate: Fri Jul 3 08:19:07 2026 +0530

    implement feedback
---
 devel-common/pyproject.toml                        |   3 +-
 providers/sftp/README.rst                          |   2 +-
 providers/sftp/docs/changelog.rst                  |   7 ++
 providers/sftp/docs/index.rst                      |   2 +-
 providers/sftp/pyproject.toml                      |   3 +-
 providers/ssh/README.rst                           |   2 +-
 providers/ssh/docs/changelog.rst                   |   7 ++
 providers/ssh/docs/connections/ssh.rst             |   2 +-
 providers/ssh/docs/index.rst                       |   2 +-
 providers/ssh/pyproject.toml                       |   3 +-
 .../ssh/src/airflow/providers/ssh/hooks/ssh.py     | 109 +++++++++++++++++----
 providers/ssh/tests/unit/ssh/hooks/test_ssh.py     |  74 ++++++++++++++
 uv.lock                                            |  88 +++++++++--------
 13 files changed, 237 insertions(+), 67 deletions(-)

diff --git a/devel-common/pyproject.toml b/devel-common/pyproject.toml
index e874dd286e5..674b2fc6516 100644
--- a/devel-common/pyproject.toml
+++ b/devel-common/pyproject.toml
@@ -118,8 +118,7 @@ dependencies = [
     "types-certifi>=2021.10.8.3",
     "types-croniter>=2.0.0.20240423",
     "types-docutils>=0.21.0.20240704",
-    # TODO: Bump to >= 4.0.0 once 
https://github.com/apache/airflow/issues/54079
-    "types-paramiko>=3.4.0.20240423,<4.0.0",
+    "types-paramiko>=4.0.0.20260402",
     "types-protobuf>=5.26.0.20240422",
     "types-python-dateutil>=2.9.0.20240316",
     "types-python-slugify>=8.0.2.20240310",
diff --git a/providers/sftp/README.rst b/providers/sftp/README.rst
index d22393492f2..51fdcd472fc 100644
--- a/providers/sftp/README.rst
+++ b/providers/sftp/README.rst
@@ -57,7 +57,7 @@ PIP package                                 Version required
 ``apache-airflow``                          ``>=2.11.0``
 ``apache-airflow-providers-ssh``            ``>=4.0.0``
 ``apache-airflow-providers-common-compat``  ``>=1.12.0``
-``paramiko``                                ``>=3.5.1,<4.0.0``
+``paramiko``                                ``>=4.0.0``
 ``asyncssh``                                ``>=2.12.0; python_version < 
"3.14"``
 ``asyncssh``                                ``>=2.22.0; python_version >= 
"3.14"``
 ==========================================  
======================================
diff --git a/providers/sftp/docs/changelog.rst 
b/providers/sftp/docs/changelog.rst
index 44a1c54cbb3..466d221f03f 100644
--- a/providers/sftp/docs/changelog.rst
+++ b/providers/sftp/docs/changelog.rst
@@ -27,6 +27,13 @@
 Changelog
 ---------
 
+Breaking changes
+~~~~~~~~~~~~~~~~
+
+* ``Bump minimum paramiko to 4.0.0; DSA/DSS keys are no longer supported 
(#54079)``
+
+  This provider requires paramiko 4.0+, which dropped DSS/DSA keys; see 
`paramiko changelog <https://www.paramiko.org/changelog.html>`__. To migrate: 
create a non-DSA key pair (Ed25519 or RSA are typical, e.g. ``ssh-keygen -t 
ed25519``), add the public key to the SFTP server, then update your Airflow 
SFTP (or shared SSH) connection so ``key_file`` or the ``private_key`` extra 
uses the new key, and ensure any ``host_key`` extra is not in ``ssh-dss`` form. 
Host key pinning should use ``ssh [...]
+
 5.8.2
 .....
 
diff --git a/providers/sftp/docs/index.rst b/providers/sftp/docs/index.rst
index 96013f5eb46..54e75eca425 100644
--- a/providers/sftp/docs/index.rst
+++ b/providers/sftp/docs/index.rst
@@ -104,7 +104,7 @@ PIP package                                 Version required
 ``apache-airflow``                          ``>=2.11.0``
 ``apache-airflow-providers-ssh``            ``>=4.0.0``
 ``apache-airflow-providers-common-compat``  ``>=1.12.0``
-``paramiko``                                ``>=3.5.1,<4.0.0``
+``paramiko``                                ``>=4.0.0``
 ``asyncssh``                                ``>=2.12.0; python_version < 
"3.14"``
 ``asyncssh``                                ``>=2.22.0; python_version >= 
"3.14"``
 ==========================================  
======================================
diff --git a/providers/sftp/pyproject.toml b/providers/sftp/pyproject.toml
index e56b35bbb6f..85665739ca7 100644
--- a/providers/sftp/pyproject.toml
+++ b/providers/sftp/pyproject.toml
@@ -63,8 +63,7 @@ dependencies = [
     "apache-airflow>=2.11.0",
     "apache-airflow-providers-ssh>=4.0.0",
     "apache-airflow-providers-common-compat>=1.12.0",
-    # TODO: Bump to >= 4.0.0 once 
https://github.com/apache/airflow/issues/54079 is handled
-    "paramiko>=3.5.1,<4.0.0",
+    "paramiko>=4.0.0",
     "asyncssh>=2.12.0; python_version < '3.14'",
     "asyncssh>=2.22.0; python_version >= '3.14'",
 ]
diff --git a/providers/ssh/README.rst b/providers/ssh/README.rst
index 799684e2f07..8654bcea8df 100644
--- a/providers/ssh/README.rst
+++ b/providers/ssh/README.rst
@@ -56,7 +56,7 @@ PIP package                                 Version required
 ``apache-airflow``                          ``>=2.11.0``
 ``apache-airflow-providers-common-compat``  ``>=1.12.0``
 ``asyncssh``                                ``>=2.12.0``
-``paramiko``                                ``>=3.5.1,<4.0.0``
+``paramiko``                                ``>=4.0.0``
 ==========================================  ==================
 
 The changelog for the provider package can be found in the
diff --git a/providers/ssh/docs/changelog.rst b/providers/ssh/docs/changelog.rst
index afcac791f85..47c5a6357be 100644
--- a/providers/ssh/docs/changelog.rst
+++ b/providers/ssh/docs/changelog.rst
@@ -27,6 +27,13 @@
 Changelog
 ---------
 
+Breaking changes
+~~~~~~~~~~~~~~~~
+
+* ``Bump minimum paramiko to 4.0.0; DSA/DSS private keys and ssh-dss host keys 
are no longer supported (#54079)``
+
+  Paramiko 4.0 removed DSS/DSA support—see `paramiko changelog 
<https://www.paramiko.org/changelog.html>`__ for upstream details. If you use a 
DSA private key in an SSH connection, generate a new key (for example 
``ssh-keygen -t ed25519`` or ``-t rsa``), install the public key on the server, 
and point your Airflow connection at the new key file or ``private_key`` extra. 
If you pin the remote host with a ``host_key`` extra in ``ssh-dss`` form, 
obtain the server's current RSA, ECDSA, or Ed [...]
+
 5.0.3
 .....
 
diff --git a/providers/ssh/docs/connections/ssh.rst 
b/providers/ssh/docs/connections/ssh.rst
index a248a60477c..2ac84750558 100644
--- a/providers/ssh/docs/connections/ssh.rst
+++ b/providers/ssh/docs/connections/ssh.rst
@@ -54,7 +54,7 @@ Extra (optional)
     * ``no_host_key_check`` - Set to ``false`` to restrict connecting to hosts 
with no entries in ``~/.ssh/known_hosts`` (Hosts file). This provides maximum 
protection against trojan horse attacks, but can be troublesome when the 
``/etc/ssh/ssh_known_hosts`` file is poorly maintained or connections to new 
hosts are frequently made. This option forces the user to manually add all new 
hosts. Default is ``true``, ssh will automatically add new host keys to the 
user known hosts files.
     * ``allow_host_key_change`` - Set to ``true`` if you want to allow 
connecting to hosts that has host key changed or when you get 'REMOTE HOST 
IDENTIFICATION HAS CHANGED' error.  This won't protect against 
Man-In-The-Middle attacks. Other possible solution is to remove the host entry 
from ``~/.ssh/known_hosts`` file. Default is ``false``.
     * ``look_for_keys`` - Set to ``false`` if you want to disable searching 
for discoverable private key files in ``~/.ssh/``
-    * ``host_key`` - The base64 encoded ssh-rsa public key of the host or 
"ssh-<key type> <key data>" (as you would find in the ``known_hosts`` file). 
Specifying this allows making the connection if and only if the public key of 
the endpoint matches this value.
+    * ``host_key`` - The base64-encoded public key of the host, or ``"<key 
type> <key data>"`` as in an OpenSSH ``known_hosts`` / ``ssh-keyscan`` line 
(without the host field). Specifying this allows making the connection if and 
only if the public key of the endpoint matches this value. Supported key type 
strings include ``ssh-rsa``, ``ssh-ed25519``, ``ecdsa-sha2-nistp256``, 
``ecdsa-sha2-nistp384``, and ``ecdsa-sha2-nistp521``. Examples: ``ssh-rsa 
AAAA...``, ``ssh-ed25519 AAAA...``, or ` [...]
     * ``disabled_algorithms`` - A dictionary mapping algorithm type to an 
iterable of algorithm identifiers, which will be disabled for the lifetime of 
the transport.
     * ``ciphers`` - A list of ciphers to use in order of preference.
 
diff --git a/providers/ssh/docs/index.rst b/providers/ssh/docs/index.rst
index d2fbdb05361..21f55e6c685 100644
--- a/providers/ssh/docs/index.rst
+++ b/providers/ssh/docs/index.rst
@@ -95,7 +95,7 @@ PIP package                                 Version required
 ``apache-airflow``                          ``>=2.11.0``
 ``apache-airflow-providers-common-compat``  ``>=1.12.0``
 ``asyncssh``                                ``>=2.12.0``
-``paramiko``                                ``>=3.5.1,<4.0.0``
+``paramiko``                                ``>=4.0.0``
 ==========================================  ==================
 
 Downloading official packages
diff --git a/providers/ssh/pyproject.toml b/providers/ssh/pyproject.toml
index 5a9d50fccf9..b0f3daf25be 100644
--- a/providers/ssh/pyproject.toml
+++ b/providers/ssh/pyproject.toml
@@ -62,8 +62,7 @@ dependencies = [
     "apache-airflow>=2.11.0",
     "apache-airflow-providers-common-compat>=1.12.0",
     "asyncssh>=2.12.0",
-    # TODO: Bump to >= 4.0.0 once 
https://github.com/apache/airflow/issues/54079 is handled
-    "paramiko>=3.5.1,<4.0.0",
+    "paramiko>=4.0.0",
 
 ]
 
diff --git a/providers/ssh/src/airflow/providers/ssh/hooks/ssh.py 
b/providers/ssh/src/airflow/providers/ssh/hooks/ssh.py
index 54b7edf6008..6b1bcb90aed 100644
--- a/providers/ssh/src/airflow/providers/ssh/hooks/ssh.py
+++ b/providers/ssh/src/airflow/providers/ssh/hooks/ssh.py
@@ -87,21 +87,34 @@ class SSHHook(BaseHook):
         once and some connections are transiently refused (e.g. ``sshd`` 
``MaxStartups`` throttling).
     """
 
-    # List of classes to try loading private keys as, ordered (roughly) by 
most common to least common
+    # List of classes to try loading private keys as, ordered (roughly) by 
most common to least common.
+    # DSA/DSS keys are not supported (removed in paramiko 4.0).
     _pkey_loaders: Sequence[type[paramiko.PKey]] = (
         paramiko.RSAKey,
         paramiko.ECDSAKey,
         paramiko.Ed25519Key,
-        paramiko.DSSKey,
     )
 
-    _host_key_mappings = {
+    # Map OpenSSH known_hosts key-type tokens (and legacy short names) to 
Paramiko key classes.
+    # DSA/DSS (ssh-dss) is intentionally absent — paramiko 4.0 removed DSS 
support.
+    _host_key_mappings: dict[str, type[paramiko.PKey]] = {
+        "ssh-rsa": paramiko.RSAKey,
         "rsa": paramiko.RSAKey,
-        "dss": paramiko.DSSKey,
-        "ecdsa": paramiko.ECDSAKey,
+        "ssh-ed25519": paramiko.Ed25519Key,
         "ed25519": paramiko.Ed25519Key,
+        "ecdsa-sha2-nistp256": paramiko.ECDSAKey,
+        "ecdsa-sha2-nistp384": paramiko.ECDSAKey,
+        "ecdsa-sha2-nistp521": paramiko.ECDSAKey,
+        # Legacy short name from older Airflow host_key parsing (ssh-ecdsa -> 
ecdsa).
+        "ecdsa": paramiko.ECDSAKey,
+        "ssh-ecdsa": paramiko.ECDSAKey,
     }
 
+    _SUPPORTED_HOST_KEY_TYPES_MSG = (
+        "ssh-rsa, ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, 
ecdsa-sha2-nistp521 "
+        "(bare base64 is treated as ssh-rsa)"
+    )
+
     conn_name_attr = "ssh_conn_id"
     default_conn_name = "ssh_default"
     conn_type = "ssh"
@@ -227,13 +240,7 @@ class SSHHook(BaseHook):
                     self.ciphers = extra_options.get("ciphers")
 
                 if host_key is not None:
-                    if host_key.startswith("ssh-"):
-                        key_type, host_key = host_key.split(None)[:2]
-                        key_constructor = self._host_key_mappings[key_type[4:]]
-                    else:
-                        key_constructor = paramiko.RSAKey
-                    decoded_host_key = decodebytes(host_key.encode("utf-8"))
-                    self.host_key = key_constructor(data=decoded_host_key)
+                    self.host_key = self._pkey_from_host_key(host_key)
                     self.no_host_key_check = False
 
         if self.cmd_timeout is NOTSET:
@@ -402,6 +409,72 @@ class SSHHook(BaseHook):
             logger=self.log,
         )
 
+    @classmethod
+    def _pkey_from_host_key(cls, host_key: str) -> paramiko.PKey:
+        """
+        Build a Paramiko public host key from a connection ``host_key`` extra.
+
+        Accepts either bare base64 (treated as ``ssh-rsa`` for backward 
compatibility) or an
+        OpenSSH ``known_hosts``-style ``"<key type> <base64 data>"`` string.
+
+        :param host_key: host key material from the connection extra
+        :return: Paramiko public key object
+        :raises ValueError: if the key type is unsupported (including DSA/DSS) 
or data is invalid
+        """
+        key_constructor, key_data = cls._parse_host_key(host_key)
+        decoded_host_key = decodebytes(key_data.encode("utf-8"))
+        return key_constructor(data=decoded_host_key)
+
+    @classmethod
+    def _parse_host_key(cls, host_key: str) -> tuple[type[paramiko.PKey], str]:
+        """
+        Parse a ``host_key`` extra into ``(key class, base64 key data)``.
+
+        Typed values use the first whitespace-separated token as the algorithm 
name. Supported
+        tokens include ``ssh-rsa``, ``ssh-ed25519``, and ``ecdsa-sha2-nistp*`` 
(as in OpenSSH
+        ``known_hosts`` / ``ssh-keyscan``). DSA/DSS (``ssh-dss``) is rejected. 
A single token of
+        base64 data defaults to RSA.
+        """
+        parts = host_key.split(None)
+        if len(parts) >= 2:
+            key_type, key_data = parts[0], parts[1]
+            if key_type in {"ssh-dss", "dss"}:
+                raise ValueError(
+                    "DSA/DSS host keys are not supported. Paramiko 4.0 removed 
DSS support; "
+                    "use an RSA, ECDSA, or Ed25519 host key and update the 
connection `host_key`."
+                )
+            # Typed line if it matches a known algorithm or looks like an 
OpenSSH key-type token.
+            if (
+                key_type in cls._host_key_mappings
+                or key_type.startswith("ssh-")
+                or key_type.startswith("ecdsa-sha2-")
+            ):
+                key_constructor = cls._host_key_mappings.get(key_type)
+                if key_constructor is None:
+                    raise ValueError(
+                        f"Unsupported SSH host key algorithm {key_type!r}. "
+                        f"Supported types are: 
{cls._SUPPORTED_HOST_KEY_TYPES_MSG}."
+                    )
+                return key_constructor, key_data
+
+        # Bare base64 (or value without a recognized key-type token): 
historical default is RSA.
+        return paramiko.RSAKey, host_key
+
+    @staticmethod
+    def _verify_pkey_usable(key: paramiko.PKey) -> None:
+        """
+        Ensure a loaded private key can sign data (guards against wrong-type 
loads).
+
+        Paramiko 5 removed SHA-1 ``ssh-rsa`` as a signing algorithm while keys 
still report
+        name ``ssh-rsa``. Prefer an algorithm from ``HASHES`` when the name is 
absent.
+        """
+        hashes = getattr(key, "HASHES", None) or {}
+        key_name = key.get_name()
+        if hashes and key_name not in hashes:
+            key.sign_ssh_data(b"", algorithm=next(iter(hashes)))
+        else:
+            key.sign_ssh_data(b"")
+
     def _pkey_from_private_key(self, private_key: str, passphrase: str | None 
= None) -> paramiko.PKey:
         """
         Create an appropriate Paramiko key for a given private key.
@@ -418,14 +491,16 @@ class SSHHook(BaseHook):
                 key = pkey_class.from_private_key(StringIO(private_key), 
password=passphrase)
                 # Test it actually works. If Paramiko loads an openssh 
generated key, sometimes it will
                 # happily load it as the wrong type, only to fail when 
actually used.
-                key.sign_ssh_data(b"")
+                # Paramiko 5+ no longer treats legacy key names (e.g. ssh-rsa) 
as signing algorithms;
+                # pass an explicit algorithm from the key's HASHES map when 
needed.
+                self._verify_pkey_usable(key)
                 return key
-            except (paramiko.ssh_exception.SSHException, ValueError):
+            except (paramiko.ssh_exception.SSHException, ValueError, KeyError):
                 continue
         raise AirflowException(
-            "Private key provided cannot be read by paramiko."
-            "Ensure key provided is valid for one of the following"
-            "key formats: RSA, DSS, ECDSA, or Ed25519"
+            "Private key provided cannot be read by paramiko. "
+            "Ensure key provided is valid for one of the following "
+            "key formats: RSA, ECDSA, or Ed25519."
         )
 
     def exec_ssh_client_command(
diff --git a/providers/ssh/tests/unit/ssh/hooks/test_ssh.py 
b/providers/ssh/tests/unit/ssh/hooks/test_ssh.py
index e277c1dc3f6..3d65a1907b8 100644
--- a/providers/ssh/tests/unit/ssh/hooks/test_ssh.py
+++ b/providers/ssh/tests/unit/ssh/hooks/test_ssh.py
@@ -69,6 +69,8 @@ TEST_HOST_KEY = generate_host_key(pkey=TEST_PKEY)
 
 TEST_PKEY_ECDSA = paramiko.ECDSAKey.generate()
 TEST_PRIVATE_KEY_ECDSA = generate_key_string(pkey=TEST_PKEY_ECDSA)
+TEST_HOST_KEY_ECDSA = TEST_PKEY_ECDSA.get_base64()
+TEST_HOST_KEY_ECDSA_TYPED = f"{TEST_PKEY_ECDSA.get_name()} 
{TEST_HOST_KEY_ECDSA}"
 
 TEST_TIMEOUT = 20
 TEST_CONN_TIMEOUT = 30
@@ -574,6 +576,78 @@ class TestSSHHook:
                 hook.remote_host, "ssh-rsa", hook.host_key
             )
 
+    @mock.patch.object(SSHHook, "get_connection")
+    def test_dss_host_key_in_connection_extra_raises(self, 
mock_get_connection):
+        mock_get_connection.return_value = Connection(
+            conn_id="ssh_dss_host_key",
+            conn_type="ssh",
+            host="remote_host",
+            login="user",
+            extra=json.dumps({"host_key": "ssh-dss AAAAB3NzaC1kc3MAAA==", 
"no_host_key_check": False}),
+        )
+        with pytest.raises(ValueError, match="DSA/DSS host keys"):
+            SSHHook(ssh_conn_id="ssh_dss_host_key")
+
+    @mock.patch.object(SSHHook, "get_connection")
+    def test_unsupported_host_key_algorithm_raises(self, mock_get_connection):
+        mock_get_connection.return_value = Connection(
+            conn_id="ssh_fake_alg",
+            conn_type="ssh",
+            host="remote_host",
+            login="user",
+            extra=json.dumps(
+                {"host_key": "ssh-fake AAAAB3NzaC1yc2EAAAADAQABAA==", 
"no_host_key_check": False}
+            ),
+        )
+        with pytest.raises(ValueError, match=r"Unsupported SSH host key 
algorithm 'ssh-fake'"):
+            SSHHook(ssh_conn_id="ssh_fake_alg")
+
+    @pytest.mark.parametrize(
+        ("host_key_value", "expected_key_type"),
+        [
+            (f"ssh-rsa {TEST_HOST_KEY}", paramiko.RSAKey),
+            (TEST_HOST_KEY_ECDSA_TYPED, paramiko.ECDSAKey),
+            # Bare base64 remains RSA for backward compatibility.
+            (TEST_HOST_KEY, paramiko.RSAKey),
+        ],
+    )
+    @mock.patch.object(SSHHook, "get_connection")
+    def test_supported_host_key_types_are_parsed(
+        self, mock_get_connection, host_key_value, expected_key_type
+    ):
+        mock_get_connection.return_value = Connection(
+            conn_id="ssh_typed_host_key",
+            conn_type="ssh",
+            host="remote_host",
+            login="user",
+            extra=json.dumps({"host_key": host_key_value, "no_host_key_check": 
False}),
+        )
+        hook = SSHHook(ssh_conn_id="ssh_typed_host_key")
+        assert isinstance(hook.host_key, expected_key_type)
+
+    @pytest.mark.parametrize(
+        ("host_key_value", "expected_key_class", "expected_data"),
+        [
+            (f"ssh-rsa {TEST_HOST_KEY}", paramiko.RSAKey, TEST_HOST_KEY),
+            (TEST_HOST_KEY_ECDSA_TYPED, paramiko.ECDSAKey, 
TEST_HOST_KEY_ECDSA),
+            (
+                "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY=",
+                paramiko.ECDSAKey,
+                "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY=",
+            ),
+            (
+                "ssh-ed25519 
AAAAC3NzaC1lZDI1NTE5AAAAIJustAPlaceholderBase64Value",
+                paramiko.Ed25519Key,
+                "AAAAC3NzaC1lZDI1NTE5AAAAIJustAPlaceholderBase64Value",
+            ),
+            (TEST_HOST_KEY, paramiko.RSAKey, TEST_HOST_KEY),
+        ],
+    )
+    def test_parse_host_key_selects_constructor(self, host_key_value, 
expected_key_class, expected_data):
+        key_class, key_data = SSHHook._parse_host_key(host_key_value)
+        assert key_class is expected_key_class
+        assert key_data == expected_data
+
     @mock.patch("airflow.providers.ssh.hooks.ssh.paramiko.SSHClient")
     def 
test_ssh_connection_with_no_host_key_where_no_host_key_check_is_false(self, 
ssh_client):
         hook = 
SSHHook(ssh_conn_id=self.CONN_SSH_WITH_NO_HOST_KEY_AND_NO_HOST_KEY_CHECK_FALSE)
diff --git a/uv.lock b/uv.lock
index 7176da8f800..c63dca91b69 100644
--- a/uv.lock
+++ b/uv.lock
@@ -642,7 +642,7 @@ name = "aiohttp-cors"
 version = "0.8.1"
 source = { registry = "https://pypi.org/simple"; }
 dependencies = [
-    { name = "aiohttp" },
+    { name = "aiohttp", marker = "python_full_version < '3.15'" },
 ]
 sdist = { url = 
"https://files.pythonhosted.org/packages/6f/6d/d89e846a5444b3d5eb8985a6ddb0daef3774928e1bfbce8e84ec97b0ffa7/aiohttp_cors-0.8.1.tar.gz";,
 hash = 
"sha256:ccacf9cb84b64939ea15f859a146af1f662a6b1d68175754a07315e305fb1403", size 
= 38626, upload-time = "2025-03-31T14:16:20.048Z" }
 wheels = [
@@ -2698,7 +2698,7 @@ requires-dist = [
     { name = "types-deprecated", marker = "extra == 'mypy'", specifier = 
">=1.2.9.20240311" },
     { name = "types-docutils", marker = "extra == 'mypy'", specifier = 
">=0.21.0.20240704" },
     { name = "types-markdown", marker = "extra == 'mypy'", specifier = 
">=3.6.0.20240316" },
-    { name = "types-paramiko", marker = "extra == 'mypy'", specifier = 
">=3.4.0.20240423,<4.0.0" },
+    { name = "types-paramiko", marker = "extra == 'mypy'", specifier = 
">=4.0.0.20260402" },
     { name = "types-protobuf", marker = "extra == 'mypy'", specifier = 
">=5.26.0.20240422" },
     { name = "types-pymysql", marker = "extra == 'mypy'", specifier = 
">=1.1.0.20240425" },
     { name = "types-python-dateutil", marker = "extra == 'mypy'", specifier = 
">=2.9.0.20240316" },
@@ -7508,7 +7508,7 @@ requires-dist = [
     { name = "apache-airflow-providers-ssh", editable = "providers/ssh" },
     { name = "asyncssh", marker = "python_full_version < '3.14'", specifier = 
">=2.12.0" },
     { name = "asyncssh", marker = "python_full_version >= '3.14'", specifier = 
">=2.22.0" },
-    { name = "paramiko", specifier = ">=3.5.1,<4.0.0" },
+    { name = "paramiko", specifier = ">=4.0.0" },
     { name = "sshfs", marker = "extra == 'sshfs'", specifier = ">=2023.1.0" },
 ]
 provides-extras = ["openlineage", "sshfs"]
@@ -7785,7 +7785,7 @@ requires-dist = [
     { name = "apache-airflow", editable = "." },
     { name = "apache-airflow-providers-common-compat", editable = 
"providers/common/compat" },
     { name = "asyncssh", specifier = ">=2.12.0" },
-    { name = "paramiko", specifier = ">=3.5.1,<4.0.0" },
+    { name = "paramiko", specifier = ">=4.0.0" },
 ]
 
 [package.metadata.requires-dev]
@@ -10822,7 +10822,7 @@ name = "colorful"
 version = "0.5.8"
 source = { registry = "https://pypi.org/simple"; }
 dependencies = [
-    { name = "colorama", marker = "sys_platform == 'win32'" },
+    { name = "colorama", marker = "python_full_version < '3.15' and 
sys_platform == 'win32'" },
 ]
 sdist = { url = 
"https://files.pythonhosted.org/packages/82/31/109ef4bedeb32b4202e02ddb133162457adc4eb890a9ed9c05c9dd126ed0/colorful-0.5.8.tar.gz";,
 hash = 
"sha256:bb16502b198be2f1c42ba3c52c703d5f651d826076817185f0294c1a549a7445", size 
= 209361, upload-time = "2025-10-29T11:53:21.663Z" }
 wheels = [
@@ -14467,6 +14467,15 @@ wheels = [
     { url = 
"https://files.pythonhosted.org/packages/97/9c/1646ca469bc2dc299ac393c8d31136c6c22a35ca1e373fa462ac01100d37/inputimeout-1.0.4-py3-none-any.whl";,
 hash = 
"sha256:f4e23d27753cfc25268eefc8d52a3edc46280ad831d226617c51882423475a43", size 
= 4639, upload-time = "2018-03-02T14:28:06.903Z" },
 ]
 
+[[package]]
+name = "invoke"
+version = "3.0.3"
+source = { registry = "https://pypi.org/simple"; }
+sdist = { url = 
"https://files.pythonhosted.org/packages/33/f6/227c48c5fe47fa178ccf1fda8f047d16c97ba926567b661e9ce2045c600c/invoke-3.0.3.tar.gz";,
 hash = 
"sha256:437b6a622223824380bfb4e64f612711a6b648c795f565efc8625af66fb57f0c", size 
= 343419, upload-time = "2026-04-07T15:17:48.307Z" }
+wheels = [
+    { url = 
"https://files.pythonhosted.org/packages/5a/de/bbc12563bbf979618d17625a4e753ff7a078523e28d870d3626daa97261a/invoke-3.0.3-py3-none-any.whl";,
 hash = 
"sha256:f11327165e5cbb89b2ad1d88d3292b5113332c43b8553b494da435d6ec6f5053", size 
= 160958, upload-time = "2026-04-07T15:17:46.875Z" },
+]
+
 [[package]]
 name = "ipdb"
 version = "0.13.13"
@@ -17348,9 +17357,9 @@ name = "opencensus"
 version = "0.11.4"
 source = { registry = "https://pypi.org/simple"; }
 dependencies = [
-    { name = "google-api-core" },
-    { name = "opencensus-context" },
-    { name = "six" },
+    { name = "google-api-core", marker = "python_full_version < '3.15'" },
+    { name = "opencensus-context", marker = "python_full_version < '3.15'" },
+    { name = "six", marker = "python_full_version < '3.15'" },
 ]
 sdist = { url = 
"https://files.pythonhosted.org/packages/15/a7/a46dcffa1b63084f9f17fe3c8cb20724c4c8f91009fd0b2cfdb27d5d2b35/opencensus-0.11.4.tar.gz";,
 hash = 
"sha256:cbef87d8b8773064ab60e5c2a1ced58bbaa38a6d052c41aec224958ce544eff2", size 
= 64966, upload-time = "2024-01-03T18:04:07.085Z" }
 wheels = [
@@ -18039,16 +18048,17 @@ all = [
 
 [[package]]
 name = "paramiko"
-version = "3.5.1"
+version = "5.0.0"
 source = { registry = "https://pypi.org/simple"; }
 dependencies = [
     { name = "bcrypt" },
     { name = "cryptography" },
+    { name = "invoke" },
     { name = "pynacl" },
 ]
-sdist = { url = 
"https://files.pythonhosted.org/packages/7d/15/ad6ce226e8138315f2451c2aeea985bf35ee910afb477bae7477dc3a8f3b/paramiko-3.5.1.tar.gz";,
 hash = 
"sha256:b2c665bc45b2b215bd7d7f039901b14b067da00f3a11e6640995fd58f2664822", size 
= 1566110, upload-time = "2025-02-04T02:37:59.783Z" }
+sdist = { url = 
"https://files.pythonhosted.org/packages/62/93/dcc25d52f49022ae6175d15e6bd751f1acc99b98bc61fc55e5155a7be2e7/paramiko-5.0.0.tar.gz";,
 hash = 
"sha256:36763b5b95c2a0dcfdf1abc48e48156ee425b21efe2f0e787c2dd5a95c0e5e79", size 
= 1548586, upload-time = "2026-05-09T18:28:52.256Z" }
 wheels = [
-    { url = 
"https://files.pythonhosted.org/packages/15/f8/c7bd0ef12954a81a1d3cea60a13946bd9a49a0036a5927770c461eade7ae/paramiko-3.5.1-py3-none-any.whl";,
 hash = 
"sha256:43b9a0501fc2b5e70680388d9346cf252cfb7d00b0667c39e80eb43a408b8f61", size 
= 227298, upload-time = "2025-02-04T02:37:57.672Z" },
+    { url = 
"https://files.pythonhosted.org/packages/82/5b/eadf6d45de38d30ab603f49393b6cd2cbe7e233af8cf90197e32782b68a9/paramiko-5.0.0-py3-none-any.whl";,
 hash = 
"sha256:b7044611c30140d9a75261653210e2002977b71a0497ff3ba0d98d7edbf62f7c", size 
= 208919, upload-time = "2026-05-09T18:28:50.295Z" },
 ]
 
 [[package]]
@@ -20692,14 +20702,14 @@ name = "ray"
 version = "2.55.1"
 source = { registry = "https://pypi.org/simple"; }
 dependencies = [
-    { name = "click" },
-    { name = "filelock" },
-    { name = "jsonschema" },
-    { name = "msgpack" },
-    { name = "packaging" },
-    { name = "protobuf" },
-    { name = "pyyaml" },
-    { name = "requests" },
+    { name = "click", marker = "python_full_version < '3.15'" },
+    { name = "filelock", marker = "python_full_version < '3.15'" },
+    { name = "jsonschema", marker = "python_full_version < '3.15'" },
+    { name = "msgpack", marker = "python_full_version < '3.15'" },
+    { name = "packaging", marker = "python_full_version < '3.15'" },
+    { name = "protobuf", marker = "python_full_version < '3.15'" },
+    { name = "pyyaml", marker = "python_full_version < '3.15'" },
+    { name = "requests", marker = "python_full_version < '3.15'" },
 ]
 wheels = [
     { url = 
"https://files.pythonhosted.org/packages/7e/d0/a85097dd53aaca1a44acc4dd0b3d2c0e9233179433e2ee326e4018ab3cf7/ray-2.55.1-cp310-cp310-macosx_12_0_arm64.whl";,
 hash = 
"sha256:2d5786661e192148719accc959def6cdcabd7a24cd9008005bf3d0e3c8cfd529", size 
= 65829601, upload-time = "2026-04-22T20:09:10.013Z" },
@@ -20724,20 +20734,20 @@ wheels = [
 
 [package.optional-dependencies]
 default = [
-    { name = "aiohttp" },
-    { name = "aiohttp-cors" },
-    { name = "colorful" },
-    { name = "grpcio" },
-    { name = "opencensus" },
-    { name = "opentelemetry-exporter-prometheus" },
-    { name = "opentelemetry-proto" },
-    { name = "opentelemetry-sdk" },
-    { name = "prometheus-client" },
-    { name = "py-spy" },
-    { name = "pydantic" },
-    { name = "requests" },
-    { name = "smart-open" },
-    { name = "virtualenv" },
+    { name = "aiohttp", marker = "python_full_version < '3.15'" },
+    { name = "aiohttp-cors", marker = "python_full_version < '3.15'" },
+    { name = "colorful", marker = "python_full_version < '3.15'" },
+    { name = "grpcio", marker = "python_full_version < '3.15'" },
+    { name = "opencensus", marker = "python_full_version < '3.15'" },
+    { name = "opentelemetry-exporter-prometheus", marker = 
"python_full_version < '3.15'" },
+    { name = "opentelemetry-proto", marker = "python_full_version < '3.15'" },
+    { name = "opentelemetry-sdk", marker = "python_full_version < '3.15'" },
+    { name = "prometheus-client", marker = "python_full_version < '3.15'" },
+    { name = "py-spy", marker = "python_full_version < '3.15'" },
+    { name = "pydantic", marker = "python_full_version < '3.15'" },
+    { name = "requests", marker = "python_full_version < '3.15'" },
+    { name = "smart-open", marker = "python_full_version < '3.15'" },
+    { name = "virtualenv", marker = "python_full_version < '3.15'" },
 ]
 
 [[package]]
@@ -21849,8 +21859,8 @@ name = "secretstorage"
 version = "3.5.0"
 source = { registry = "https://pypi.org/simple"; }
 dependencies = [
-    { name = "cryptography", marker = "python_full_version >= '3.14' or 
platform_machine != 'arm64' or sys_platform != 'darwin'" },
-    { name = "jeepney", marker = "python_full_version >= '3.14' or 
platform_machine != 'arm64' or sys_platform != 'darwin'" },
+    { name = "cryptography", marker = "(python_full_version >= '3.14' and 
sys_platform == 'darwin') or (python_full_version < '3.15' and sys_platform == 
'emscripten') or (python_full_version < '3.15' and sys_platform == 'win32') or 
(platform_machine != 'arm64' and sys_platform == 'darwin') or (sys_platform != 
'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32')" },
+    { name = "jeepney", marker = "(python_full_version >= '3.14' and 
sys_platform == 'darwin') or (python_full_version < '3.15' and sys_platform == 
'emscripten') or (python_full_version < '3.15' and sys_platform == 'win32') or 
(platform_machine != 'arm64' and sys_platform == 'darwin') or (sys_platform != 
'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32')" },
 ]
 sdist = { url = 
"https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz";,
 hash = 
"sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size 
= 19884, upload-time = "2025-11-23T19:02:53.191Z" }
 wheels = [
@@ -22049,7 +22059,7 @@ name = "smart-open"
 version = "7.7.1"
 source = { registry = "https://pypi.org/simple"; }
 dependencies = [
-    { name = "wrapt" },
+    { name = "wrapt", marker = "python_full_version < '3.15'" },
 ]
 sdist = { url = 
"https://files.pythonhosted.org/packages/db/c6/22e7a2acd5d27941e85e0d7ede398da5abe2e4677d2265c924157247c32e/smart_open-7.7.1.tar.gz";,
 hash = 
"sha256:9414ba5733e28309f29b28a303b0f1054ad23fe0275f1a1b600c80a724f4bd1a", size 
= 54952, upload-time = "2026-06-26T07:56:35.309Z" }
 wheels = [
@@ -23525,14 +23535,14 @@ wheels = [
 
 [[package]]
 name = "types-paramiko"
-version = "3.5.0.20250801"
+version = "5.0.0.20260617"
 source = { registry = "https://pypi.org/simple"; }
 dependencies = [
     { name = "cryptography" },
 ]
-sdist = { url = 
"https://files.pythonhosted.org/packages/d5/c4/4df427c835e9c795662241410732ab936c6f58be798ec25ce7015771c448/types_paramiko-3.5.0.20250801.tar.gz";,
 hash = 
"sha256:e79ff84eaf44f2a5ad811743edef5d9c0cd6632a264a526f00405b87db1ec99b", size 
= 28838, upload-time = "2025-08-01T03:48:52.777Z" }
+sdist = { url = 
"https://files.pythonhosted.org/packages/de/89/902652d7b62bb7cd2b73c7f08c8fd03945c223c3814022f0ad305e1018b9/types_paramiko-5.0.0.20260617.tar.gz";,
 hash = 
"sha256:50a5b0dc68b39d30097cb7d93b4915dbbc97ed740ea633bd492be25ca1f25df4", size 
= 28474, upload-time = "2026-06-17T06:56:52.955Z" }
 wheels = [
-    { url = 
"https://files.pythonhosted.org/packages/bd/5b/0f0bb1e45f7547d081ae9d490a88c6f6031d0f2c97459236e0a2a8b27207/types_paramiko-3.5.0.20250801-py3-none-any.whl";,
 hash = 
"sha256:3e02a0fcf2b7e7b213e0cd569f7223ff9af417052a4d149d84172ebaa6fd742e", size 
= 39705, upload-time = "2025-08-01T03:48:51.855Z" },
+    { url = 
"https://files.pythonhosted.org/packages/50/4d/eab89decb4201dfff665a3b92088f384ff1e165bf6caa447509733bc1bc6/types_paramiko-5.0.0.20260617-py3-none-any.whl";,
 hash = 
"sha256:83d87c396405666524441710ec59e693bca8b19021861456ac0a2401327812f8", size 
= 37123, upload-time = "2026-06-17T06:56:51.81Z" },
 ]
 
 [[package]]

Reply via email to