Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package azure-cli-core for openSUSE:Factory 
checked in at 2026-09-07 11:29:51
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/azure-cli-core (Old)
 and      /work/SRC/openSUSE:Factory/.azure-cli-core.new.1265 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "azure-cli-core"

Mon Sep  7 11:29:51 2026 rev:100 rq:1375727 version:2.90.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/azure-cli-core/azure-cli-core.changes    
2026-08-21 16:58:09.559261646 +0200
+++ /work/SRC/openSUSE:Factory/.azure-cli-core.new.1265/azure-cli-core.changes  
2026-09-07 11:31:35.045738142 +0200
@@ -1,0 +2,8 @@
+Tue Sep  1 09:24:15 UTC 2026 - John Paul Adrian Glaubitz 
<[email protected]>
+
+- New upstream release
+  + Version 2.90.0
+  + For detailed information about changes see the
+    HISTORY.rst file provided with this package
+
+-------------------------------------------------------------------

Old:
----
  azure_cli_core-2.89.1.tar.gz

New:
----
  azure_cli_core-2.90.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ azure-cli-core.spec ++++++
--- /var/tmp/diff_new_pack.mS9KQT/_old  2026-09-07 11:31:35.975770742 +0200
+++ /var/tmp/diff_new_pack.mS9KQT/_new  2026-09-07 11:31:35.977770812 +0200
@@ -24,7 +24,7 @@
 %global _sitelibdir %{%{pythons}_sitelib}
 
 Name:           azure-cli-core
-Version:        2.89.1
+Version:        2.90.0
 Release:        0
 Summary:        Microsoft Azure CLI Core Module
 License:        MIT

++++++ azure_cli_core-2.89.1.tar.gz -> azure_cli_core-2.90.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/azure_cli_core-2.89.1/HISTORY.rst 
new/azure_cli_core-2.90.0/HISTORY.rst
--- old/azure_cli_core-2.89.1/HISTORY.rst       2026-08-10 03:11:59.000000000 
+0200
+++ new/azure_cli_core-2.90.0/HISTORY.rst       2026-08-26 03:18:26.000000000 
+0200
@@ -3,6 +3,10 @@
 Release History
 ===============
 
+2.90.0
+++++++
+* Minor fixes
+
 2.89.1
 ++++++
 * Allow SSH certificate flow in Cloud Shell (#33860)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/azure_cli_core-2.89.1/PKG-INFO 
new/azure_cli_core-2.90.0/PKG-INFO
--- old/azure_cli_core-2.89.1/PKG-INFO  2026-08-10 03:12:55.453722500 +0200
+++ new/azure_cli_core-2.90.0/PKG-INFO  2026-08-26 03:19:17.575173000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: azure-cli-core
-Version: 2.89.1
+Version: 2.90.0
 Summary: Microsoft Azure Command-Line Tools Core Module
 Home-page: https://github.com/Azure/azure-cli
 Author: Microsoft Corporation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/azure_cli_core-2.89.1/azure/cli/core/__init__.py 
new/azure_cli_core-2.90.0/azure/cli/core/__init__.py
--- old/azure_cli_core-2.89.1/azure/cli/core/__init__.py        2026-08-10 
03:11:59.000000000 +0200
+++ new/azure_cli_core-2.90.0/azure/cli/core/__init__.py        2026-08-26 
03:18:26.000000000 +0200
@@ -4,7 +4,7 @@
 # 
--------------------------------------------------------------------------------------------
 # pylint: disable=line-too-long
 
-__version__ = "2.89.1"
+__version__ = "2.90.0"
 
 import os
 import sys
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/azure_cli_core-2.89.1/azure/cli/core/aaz/_arg_fmt.py 
new/azure_cli_core-2.90.0/azure/cli/core/aaz/_arg_fmt.py
--- old/azure_cli_core-2.89.1/azure/cli/core/aaz/_arg_fmt.py    2026-08-10 
03:11:59.000000000 +0200
+++ new/azure_cli_core-2.90.0/azure/cli/core/aaz/_arg_fmt.py    2026-08-26 
03:18:26.000000000 +0200
@@ -767,7 +767,9 @@
             # `next_link` is a URL that the next page request is sent to with 
an Azure access token.
             # Validate it shares the same origin as a trusted endpoint of the 
active cloud to prevent
             # the token from being sent to an attacker-controlled host such as
-            # `https://management.azure.com.attacker`.
+            # `https://management.azure.com.attacker`. URLs that are not 
normalized, such as
+            # `https://management.azure.com//attacker.example/leak`, are 
rejected as well, since they
+            # may be resolved to a different host by a downstream parser, 
proxy or server.
             if next_link is not None:
                 from azure.cli.core.util import is_trusted_cloud_endpoint
                 if not is_trusted_cloud_endpoint(next_link, ctx.cli_ctx):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/azure_cli_core-2.89.1/azure/cli/core/commandIndex.latest.json 
new/azure_cli_core-2.90.0/azure/cli/core/commandIndex.latest.json
--- old/azure_cli_core-2.89.1/azure/cli/core/commandIndex.latest.json   
2026-08-10 03:11:59.000000000 +0200
+++ new/azure_cli_core-2.90.0/azure/cli/core/commandIndex.latest.json   
2026-08-26 03:18:26.000000000 +0200
@@ -1,5 +1,5 @@
 {
-  "version": "2.89.1",
+  "version": "2.90.0",
   "cloudProfile": "latest",
   "commandIndex": {
     "account": [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/azure_cli_core-2.89.1/azure/cli/core/extension/operations.py 
new/azure_cli_core-2.90.0/azure/cli/core/extension/operations.py
--- old/azure_cli_core-2.89.1/azure/cli/core/extension/operations.py    
2026-08-10 03:11:59.000000000 +0200
+++ new/azure_cli_core-2.90.0/azure/cli/core/extension/operations.py    
2026-08-26 03:18:26.000000000 +0200
@@ -180,7 +180,7 @@
     return extension_name
 
 
-def _install_deps_for_psycopg2():  # pylint: disable=too-many-statements
+def _install_deps_for_psycopg2():  # pylint: disable=too-many-statements, 
too-many-branches
     # If we are in Cloud Shell, dependencies should have already been 
installed.
     from azure.cli.core.util import in_cloud_console
     if in_cloud_console():
@@ -215,8 +215,17 @@
                 '/usr/local/opt/openssl/lib/'
             ])
     elif system == 'Linux':
-        distname, _ = get_linux_distro()
-        distname = distname.lower().strip()
+        distname, distversion = get_linux_distro()
+        distname = (distname or '').lower().strip()
+        distversion = (distversion or '').strip()
+        # Extract major version from VERSION_ID (e.g., "4", "4.0", "4.0.1" all 
map to major=4)
+        azl_major_version = None
+        if 'azure linux' in distname and distversion:
+            try:
+                azl_major_version = int(distversion.partition('.')[0])
+            except ValueError:
+                pass
+        is_azure_linux_4 = azl_major_version == 4
         if installer == 'DEB' or any(x in distname for x in ['ubuntu', 
'debian']):
             exit_code = subprocess.call(['dpkg', '-s', 'gcc', 'libpq-dev', 
'python3-dev'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
             if exit_code != 0:
@@ -230,7 +239,7 @@
                 if exit_code == 0:
                     logger.debug("Install dependencies with '%s'", " 
".join(apt_install_cmd))
                     subprocess.call(apt_install_cmd, True)
-        elif installer == 'RPM' or any(x in distname for x in ['centos', 
'rhel', 'red hat', 'fedora', 'opensuse', 'suse', 'sles']):
+        elif installer == 'RPM' or any(x in distname for x in ['centos', 
'rhel', 'red hat', 'fedora', 'opensuse', 'suse', 'sles']) or is_azure_linux_4:
             if any(x in distname for x in ['centos', 'rhel', 'red hat', 
'fedora']):
                 yum_install_cmd = 'yum install -y gcc postgresql-devel 
python3-devel'.split()
                 if os.geteuid() != 0:  # pylint: disable=no-member
@@ -249,6 +258,29 @@
                 if exit_code == 0:
                     logger.debug("Install dependencies with '%s'", " 
".join(zypper_install_cmd))
                     subprocess.call(zypper_install_cmd)
+            elif is_azure_linux_4:
+                from shutil import which
+                if which('tdnf') is None:
+                    raise CLIError(
+                        'tdnf package manager not found. This extension 
requires Azure Linux 4 with tdnf '
+                        'to install required system dependencies: gcc, 
libpq-devel, python3-devel, binutils, '
+                        'glibc-devel, kernel-headers.'
+                    )
+                rpm_packages = ['gcc', 'libpq-devel', 'python3-devel', 
'binutils', 'glibc-devel', 'kernel-headers']
+                rpm_install_cmd = ['tdnf', 'install', '-y'] + rpm_packages
+                if os.geteuid() != 0:  # pylint: disable=no-member
+                    rpm_install_cmd.insert(0, 'sudo')
+                logger.debug("Install dependencies with '%s'", " 
".join(rpm_install_cmd))
+                logger.warning(
+                    'This extension depends on %s and will now be installed.',
+                    ' '.join(rpm_packages)
+                )
+                exit_code = subprocess.call(rpm_install_cmd)
+                if exit_code != 0:
+                    raise CLIError(
+                        'Failed to install required system dependencies for 
psycopg2: {}.'
+                        .format(' '.join(rpm_packages))
+                    )
         elif installer == 'DOCKER' or any(x in distname for x in ['alpine 
linux']):
             apk_install_cmd = 'apk add --no-cache libpq-dev'.split()
             logger.debug("Install dependencies with '%s'", " 
".join(apk_install_cmd))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/azure_cli_core-2.89.1/azure/cli/core/helpIndex.latest.json 
new/azure_cli_core-2.90.0/azure/cli/core/helpIndex.latest.json
--- old/azure_cli_core-2.89.1/azure/cli/core/helpIndex.latest.json      
2026-08-10 03:11:59.000000000 +0200
+++ new/azure_cli_core-2.90.0/azure/cli/core/helpIndex.latest.json      
2026-08-26 03:18:26.000000000 +0200
@@ -1,5 +1,5 @@
 {
-  "version": "2.89.1",
+  "version": "2.90.0",
   "cloudProfile": "latest",
   "helpIndex": {
     "groups": {
@@ -152,7 +152,7 @@
         "tags": ""
       },
       "eventhubs": {
-        "summary": "Eventhubs.",
+        "summary": "Manage Event Hub.",
         "tags": ""
       },
       "extension": {
@@ -292,7 +292,7 @@
         "tags": ""
       },
       "servicebus": {
-        "summary": "Servicebus.",
+        "summary": "Manage Service Bus.",
         "tags": ""
       },
       "sf": {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/azure_cli_core-2.89.1/azure/cli/core/util.py 
new/azure_cli_core-2.90.0/azure/cli/core/util.py
--- old/azure_cli_core-2.89.1/azure/cli/core/util.py    2026-08-10 
03:11:59.000000000 +0200
+++ new/azure_cli_core-2.90.0/azure/cli/core/util.py    2026-08-26 
03:18:26.000000000 +0200
@@ -1028,7 +1028,13 @@
     # default to Azure Resource Manager.
     # https://management.azure.com + 
/subscriptions/xxx/resourcegroups/xxx?api-version=2019-07-01
     if '://' not in url:
-        url = endpoints.resource_manager.rstrip('/') + url
+        # lstrip('/') prevents a protocol-relative path like 
'//attacker.example/leak' from
+        # producing 'https://management.azure.com//attacker.example/leak'.
+        url = endpoints.resource_manager.rstrip('/') + '/' + url.lstrip('/')
+
+    # Normalize before validating and before sending, so that the URL whose 
origin is validated is
+    # exactly the URL the request is sent to.
+    url = normalize_url(url)
 
     # Replace common tokens with real values. It is for smooth experience if 
users copy and paste the url from
     # Azure Rest API doc
@@ -1075,6 +1081,13 @@
     req = Request(method=method, url=url, headers=headers, 
params=uri_parameters, data=body)
     prepped = s.prepare_request(req)
 
+    # Final defense: make sure requests didn't resolve the URL to a different 
origin than the one
+    # whose origin was validated above before an access token was attached.
+    if 'Authorization' in prepped.headers and not is_same_origin(prepped.url, 
url):
+        from .azclierror import InvalidArgumentValueError
+        raise InvalidArgumentValueError(
+            "The request URL '{}' doesn't match the validated URL 
'{}'.".format(prepped.url, url))
+
     # Merge environment settings into session
     settings = s.merge_environment_settings(prepped.url, {}, None, not 
should_disable_connection_verify(), None)
     _log_request(prepped)
@@ -1202,6 +1215,92 @@
     return req.read()
 
 
+def _remove_dot_segments(path):
+    """Remove ``.`` and ``..`` segments from a URL path (RFC 3986 section 
5.2.4) and collapse
+    empty segments, so that ``/a/b/../c`` becomes ``/a/c`` and 
``//attacker.example/leak``
+    becomes ``/attacker.example/leak``.
+    """
+    leading_slash = path.startswith('/')
+    trailing_slash = path.endswith('/')
+    segments = []
+    for segment in path.split('/'):
+        # Empty segments are dropped so that a path can never be 
re-interpreted as an authority
+        # (protocol-relative URL) by a downstream parser, proxy or gateway.
+        if segment in ('', '.'):
+            continue
+        if segment == '..':
+            if segments:
+                segments.pop()
+            continue
+        segments.append(segment)
+    new_path = '/'.join(segments)
+    if leading_slash:
+        new_path = '/' + new_path
+    if trailing_slash and not new_path.endswith('/'):
+        new_path += '/'
+    return new_path
+
+
+def normalize_url(url):
+    """Normalize a URL so that it is parsed the same way by the CLI and by any 
downstream
+    consumer (requests/urllib3, proxies, gateways, servers).
+
+    Validating a URL that is not normalized is unsafe, because the validator 
and the consumer
+    may disagree on which host the request is actually sent to. This function:
+
+    - removes control characters, which are silently stripped by many URL 
parsers, and trims
+      leading/trailing whitespace;
+    - lowercases the scheme and the host;
+    - converts backslashes in the authority to ``/`` (WHATWG URL parsers treat 
``\\`` as ``/``),
+      moving anything after them into the path;
+    - removes ``.``/``..`` segments and collapses empty segments in the path, 
so that
+      ``https://management.azure.com//attacker.example/leak`` is normalized to
+      ``https://management.azure.com/attacker.example/leak``.
+
+    :param url: The URL to normalize.
+    :return: The normalized URL, or the input unchanged if it is not a 
parsable string.
+    :rtype: str
+    """
+    from urllib.parse import urlsplit, urlunsplit
+
+    if not isinstance(url, str):
+        return url
+
+    # Strip C0/DEL control characters. Browsers and urllib strip some of them 
(\t, \r, \n), so a
+    # validator must not consider them part of the host.
+    # Note: the space character (U+0020) is *not* removed from the middle of 
the URL, as it is a
+    # meaningful character that downstream consumers percent-encode as '%20' 
rather than drop.
+    # Only leading and trailing whitespace is trimmed.
+    cleaned = ''.join(c for c in url if ord(c) >= 0x20 and ord(c) != 
0x7f).strip()
+
+    try:
+        parts = urlsplit(cleaned)
+    except ValueError:
+        return cleaned
+
+    scheme = parts.scheme.lower()
+    netloc = parts.netloc
+    path = parts.path
+
+    if netloc:
+        # `urlsplit` doesn't treat '\' as a delimiter, but WHATWG parsers do. 
Anything after it
+        # belongs to the path, not to the authority.
+        netloc = netloc.replace('\\', '/')
+        if '/' in netloc:
+            netloc, _, rest = netloc.partition('/')
+            path = '/' + rest + path
+        # The host is case-insensitive, but userinfo isn't, so only lowercase 
the host part.
+        userinfo, sep, hostport = netloc.rpartition('@')
+        netloc = userinfo + sep + hostport.lower()
+
+    if path:
+        path = _remove_dot_segments(path)
+    elif netloc:
+        path = '/'
+
+    return urlunsplit((scheme, netloc, path, parts.query, parts.fragment))
+
+
 def is_same_origin(url, endpoint):
     """Check whether ``url`` and ``endpoint`` share the same origin (scheme + 
host + port).
 
@@ -1211,6 +1310,9 @@
     URL points to a trusted endpoint before sensitive data (e.g., an Azure 
access token) is sent
     to it.
 
+    The URLs are normalized with :func:`normalize_url` first, so that tricks 
relying on control
+    characters or backslashes in the authority can't hide the real host.
+
     :param url: The URL to validate, e.g., 
``https://management.azure.com/subscriptions/...``.
     :param endpoint: The trusted endpoint to validate against, e.g., 
``https://management.azure.com/``.
     :return: ``True`` if both share the same origin, otherwise ``False``.
@@ -1222,8 +1324,8 @@
         return False
 
     try:
-        url_parts = urlparse(url)
-        endpoint_parts = urlparse(endpoint)
+        url_parts = urlparse(normalize_url(url))
+        endpoint_parts = urlparse(normalize_url(endpoint))
 
     except (TypeError, ValueError):
         return False
@@ -1280,11 +1382,19 @@
 def is_trusted_cloud_endpoint(url, cli_ctx):
     """Check whether ``url`` shares the same origin as any endpoint of the 
active cloud.
 
+    The URL must already be in normalized form (see :func:`normalize_url`). A 
URL that is not
+    normalized is rejected, because the CLI and the downstream consumer 
(requests, a proxy or the
+    server) may not resolve it to the same host, e.g.
+    ``https://management.azure.com//attacker.example/leak``, whose path can be 
re-interpreted as
+    a protocol-relative URL.
+
     :param url: The URL to validate, e.g., 
``https://management.azure.com/subscriptions/...``.
     :param cli_ctx: The CLI context whose active cloud's endpoints are treated 
as trusted.
     :return: ``True`` if ``url`` shares the same origin as any cloud endpoint, 
otherwise ``False``.
     :rtype: bool
     """
+    if not isinstance(url, str) or normalize_url(url) != url:
+        return False
     return match_cloud_endpoint(url, cli_ctx) is not None
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/azure_cli_core-2.89.1/azure_cli_core.egg-info/PKG-INFO 
new/azure_cli_core-2.90.0/azure_cli_core.egg-info/PKG-INFO
--- old/azure_cli_core-2.89.1/azure_cli_core.egg-info/PKG-INFO  2026-08-10 
03:12:55.000000000 +0200
+++ new/azure_cli_core-2.90.0/azure_cli_core.egg-info/PKG-INFO  2026-08-26 
03:19:17.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: azure-cli-core
-Version: 2.89.1
+Version: 2.90.0
 Summary: Microsoft Azure Command-Line Tools Core Module
 Home-page: https://github.com/Azure/azure-cli
 Author: Microsoft Corporation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/azure_cli_core-2.89.1/setup.py 
new/azure_cli_core-2.90.0/setup.py
--- old/azure_cli_core-2.89.1/setup.py  2026-08-10 03:11:59.000000000 +0200
+++ new/azure_cli_core-2.90.0/setup.py  2026-08-26 03:18:26.000000000 +0200
@@ -8,7 +8,7 @@
 from codecs import open
 from setuptools import setup, find_packages
 
-VERSION = "2.89.1"
+VERSION = "2.90.0"
 
 # If we have source, validate that our version numbers match
 # This should prevent uploading releases with mismatched versions.

Reply via email to