Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package ansible-core for openSUSE:Factory 
checked in at 2026-09-19 22:22:05
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/ansible-core (Old)
 and      /work/SRC/openSUSE:Factory/.ansible-core.new.383539 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "ansible-core"

Sat Sep 19 22:22:05 2026 rev:64 rq:1378953 version:2.21.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/ansible-core/ansible-core.changes        
2026-08-11 17:18:09.832660159 +0200
+++ /work/SRC/openSUSE:Factory/.ansible-core.new.383539/ansible-core.changes    
2026-09-19 22:23:14.205845227 +0200
@@ -1,0 +2,27 @@
+Wed Sep 16 05:26:13 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- update to 2.21.4:
+  
https://github.com/ansible/ansible/blob/v2.21.4/changelogs/CHANGELOG-v2.21.rst
+  * Minor Changes
+    - mask_url function in module_utils to allow for masking of
+      auth data embedded in urls.
+  * Bugfixes
+    - apt_key module now masks authentication information in all
+      displays and returns of uri information.
+    - get_url module now masks authentication information in all
+      displays and returns of uri information.
+    - module_utils - mask_url now masks the password in URLs that
+      contain a password but no username, such as
+      redis://:password@host, instead of returning them unmasked.
+    - module_utils.urls now all errors mask in line url
+      authentication information.
+    - rpm_key module now masks authentication information in all
+      displays and returns of uri information.
+    - tempfile - reject prefix and suffix values that contain path
+      components to prevent path traversal.
+    - uri module now masks authentication information in all
+      displays and returns of uri information.
+    - url lookup now masks authentication information in all
+      displays and returns of uri information.
+
+-------------------------------------------------------------------

Old:
----
  ansible_core-2.21.3.tar.gz
  ansible_core-2.21.3.tar.gz.sha256

New:
----
  ansible_core-2.21.4.tar.gz
  ansible_core-2.21.4.tar.gz.sha256

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

Other differences:
------------------
++++++ ansible-core.spec ++++++
--- /var/tmp/diff_new_pack.Vu2CV8/_old  2026-09-19 22:23:14.914874798 +0200
+++ /var/tmp/diff_new_pack.Vu2CV8/_new  2026-09-19 22:23:14.916874882 +0200
@@ -43,7 +43,7 @@
 %endif
 
 Name:           ansible-core
-Version:        2.21.3
+Version:        2.21.4
 Release:        0
 Summary:        Radically simple IT automation
 License:        GPL-3.0-or-later

++++++ ansible_core-2.21.3.tar.gz -> ansible_core-2.21.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.21.3/PKG-INFO 
new/ansible_core-2.21.4/PKG-INFO
--- old/ansible_core-2.21.3/PKG-INFO    2026-08-10 17:52:54.000000000 +0200
+++ new/ansible_core-2.21.4/PKG-INFO    2026-09-08 18:39:02.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: ansible-core
-Version: 2.21.3
+Version: 2.21.4
 Summary: Radically simple IT automation
 Author: Ansible Project
 License-Expression: GPL-3.0-or-later
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.21.3/ansible_core.egg-info/PKG-INFO 
new/ansible_core-2.21.4/ansible_core.egg-info/PKG-INFO
--- old/ansible_core-2.21.3/ansible_core.egg-info/PKG-INFO      2026-08-10 
17:52:54.000000000 +0200
+++ new/ansible_core-2.21.4/ansible_core.egg-info/PKG-INFO      2026-09-08 
18:39:02.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: ansible-core
-Version: 2.21.3
+Version: 2.21.4
 Summary: Radically simple IT automation
 Author: Ansible Project
 License-Expression: GPL-3.0-or-later
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.3/ansible_core.egg-info/SOURCES.txt 
new/ansible_core-2.21.4/ansible_core.egg-info/SOURCES.txt
--- old/ansible_core-2.21.3/ansible_core.egg-info/SOURCES.txt   2026-08-10 
17:52:54.000000000 +0200
+++ new/ansible_core-2.21.4/ansible_core.egg-info/SOURCES.txt   2026-09-08 
18:39:02.000000000 +0200
@@ -4237,6 +4237,7 @@
 test/integration/targets/throttle/undefined_throttle.yml
 test/integration/targets/throttle/group_vars/all.yml
 test/integration/targets/unarchive/aliases
+test/integration/targets/unarchive/auth_mask.yml
 test/integration/targets/unarchive/runme.sh
 test/integration/targets/unarchive/runme.yml
 test/integration/targets/unarchive/test_relative_tmp_dir.yml
@@ -5430,6 +5431,7 @@
 test/units/module_utils/urls/test_fetch_url.py
 test/units/module_utils/urls/test_generic_urlparse.py
 test/units/module_utils/urls/test_gzip.py
+test/units/module_utils/urls/test_mask_url.py
 test/units/module_utils/urls/test_prepare_multipart.py
 test/units/module_utils/urls/test_split.py
 test/units/module_utils/urls/test_urls.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.21.3/changelogs/CHANGELOG-v2.21.rst 
new/ansible_core-2.21.4/changelogs/CHANGELOG-v2.21.rst
--- old/ansible_core-2.21.3/changelogs/CHANGELOG-v2.21.rst      2026-08-10 
17:52:54.000000000 +0200
+++ new/ansible_core-2.21.4/changelogs/CHANGELOG-v2.21.rst      2026-09-08 
18:39:02.000000000 +0200
@@ -4,6 +4,32 @@
 
 .. contents:: Topics
 
+v2.21.4
+=======
+
+Release Summary
+---------------
+
+| Release Date: 2026-09-08
+| `Porting Guide 
<https://docs.ansible.com/ansible-core/2.21/porting_guides/porting_guide_core_2.21.html>`__
+
+Minor Changes
+-------------
+
+- mask_url function in module_utils to allow for masking of auth data embedded 
in urls.
+
+Bugfixes
+--------
+
+- apt_key module now masks authentication information in all displays and 
returns of uri information.
+- get_url module now masks authentication information in all displays and 
returns of uri information.
+- module_utils - ``mask_url`` now masks the password in URLs that contain a 
password but no username, such as ``redis://:password@host``, instead of 
returning them unmasked.
+- module_utils.urls now all errors mask in line url authentication information.
+- rpm_key module now masks authentication information in all displays and 
returns of uri information.
+- tempfile - reject prefix and suffix values that contain path components to 
prevent path traversal.
+- uri module now masks authentication information in all displays and returns 
of uri information.
+- url lookup now masks authentication information in all displays and returns 
of uri information.
+
 v2.21.3
 =======
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.21.3/changelogs/changelog.yaml 
new/ansible_core-2.21.4/changelogs/changelog.yaml
--- old/ansible_core-2.21.3/changelogs/changelog.yaml   2026-08-10 
17:52:54.000000000 +0200
+++ new/ansible_core-2.21.4/changelogs/changelog.yaml   2026-09-08 
18:39:02.000000000 +0200
@@ -770,3 +770,49 @@
     - fix-tree-and-oneline-callback-deprecation.yml
     - powershell-exec-wrapper-select-first.yml
     release_date: '2026-08-03'
+  2.21.4:
+    changes:
+      release_summary: '| Release Date: 2026-09-08
+
+        | `Porting Guide 
<https://docs.ansible.com/ansible-core/2.21/porting_guides/porting_guide_core_2.21.html>`__
+
+        '
+    codename: The Rain Song
+    fragments:
+    - 2.21.4_summary.yaml
+    release_date: '2026-09-08'
+  2.21.4rc1:
+    changes:
+      bugfixes:
+      - apt_key module now masks authentication information in all displays 
and returns
+        of uri information.
+      - get_url module now masks authentication information in all displays 
and returns
+        of uri information.
+      - module_utils - ``mask_url`` now masks the password in URLs that 
contain a
+        password but no username, such as ``redis://:password@host``, instead 
of returning
+        them unmasked.
+      - module_utils.urls now all errors mask in line url authentication 
information.
+      - rpm_key module now masks authentication information in all displays 
and returns
+        of uri information.
+      - tempfile - reject prefix and suffix values that contain path 
components to
+        prevent path traversal.
+      - uri module now masks authentication information in all displays and 
returns
+        of uri information.
+      - url lookup now masks authentication information in all displays and 
returns
+        of uri information.
+      minor_changes:
+      - mask_url function in module_utils to allow for masking of auth data 
embedded
+        in urls.
+      release_summary: '| Release Date: 2026-08-31
+
+        | `Porting Guide 
<https://docs.ansible.com/ansible-core/2.21/porting_guides/porting_guide_core_2.21.html>`__
+
+        '
+    codename: The Rain Song
+    fragments:
+    - 2.21.4rc1_summary.yaml
+    - mask_inurl_auth.yml
+    - mask_url-password-auth.yml
+    - mask_url.yml
+    - tempfile-path-traversal.yml
+    release_date: '2026-08-31'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.3/lib/ansible/module_utils/ansible_release.py 
new/ansible_core-2.21.4/lib/ansible/module_utils/ansible_release.py
--- old/ansible_core-2.21.3/lib/ansible/module_utils/ansible_release.py 
2026-08-10 17:52:54.000000000 +0200
+++ new/ansible_core-2.21.4/lib/ansible/module_utils/ansible_release.py 
2026-09-08 18:39:02.000000000 +0200
@@ -17,6 +17,6 @@
 
 from __future__ import annotations
 
-__version__ = '2.21.3'
+__version__ = '2.21.4'
 __author__ = 'Ansible, Inc.'
 __codename__ = "The Rain Song"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.21.3/lib/ansible/module_utils/urls.py 
new/ansible_core-2.21.4/lib/ansible/module_utils/urls.py
--- old/ansible_core-2.21.3/lib/ansible/module_utils/urls.py    2026-08-10 
17:52:54.000000000 +0200
+++ new/ansible_core-2.21.4/lib/ansible/module_utils/urls.py    2026-09-08 
18:39:02.000000000 +0200
@@ -49,6 +49,7 @@
 import types  # pylint: disable=unused-import
 import urllib.error
 import urllib.request
+
 from contextlib import contextmanager
 from http import cookiejar
 from urllib.parse import unquote, urlparse, urlunparse
@@ -316,6 +317,24 @@
         return [self.get(k, None) for k in ('scheme', 'netloc', 'path', 
'params', 'query', 'fragment')]
 
 
+def mask_url(url: str) -> str:
+    """
+    Safely display a url by masking confidential data
+    from a string or the result from urlparse/split
+    """
+    if (parsed_url := urlparse(url)) and not parsed_url.username and not 
parsed_url.password:
+        return url
+
+    netloc: str
+    mask = '****'
+    if parsed_url.password:
+        netloc = 
parsed_url.netloc.replace(f'{parsed_url.username}:{parsed_url.password}@', 
f'{mask}:{mask}@')
+    else:
+        netloc = parsed_url.netloc.replace(f'{parsed_url.username}@', 
f'{mask}@')
+
+    return urlunparse(parsed_url._replace(netloc=netloc))
+
+
 def generic_urlparse(parts):
     """
     Returns a dictionary of url parts as parsed by urlparse,
@@ -1235,7 +1254,7 @@
         cookies = cookiejar.CookieJar()
 
     r = None
-    info = dict(url=url, status=-1)
+    info = dict(url=mask_url(url), status=-1)
     try:
         r = open_url(url, data=data, headers=headers, method=method,
                      use_proxy=use_proxy, force=force, 
last_mod_time=last_mod_time, timeout=timeout,
@@ -1272,7 +1291,7 @@
 
         info['cookies'] = cookie_dict
         # finally update the result with a message about the fetch
-        info.update(dict(msg="OK (%s bytes)" % r.headers.get('Content-Length', 
'unknown'), url=r.geturl(), status=r.code))
+        info.update(dict(msg="OK (%s bytes)" % r.headers.get('Content-Length', 
'unknown'), url=mask_url(r.geturl()), status=r.code))
     except (ConnectionError, ValueError) as e:
         module.fail_json(msg=to_native(e), **info)
     except MissingModuleError as e:
@@ -1392,14 +1411,14 @@
                               unredirected_headers=unredirected_headers, 
decompress=decompress, ciphers=ciphers,
                               ca_path=ca_path, cookies=cookies)
         if not rsp or (rsp.code and rsp.code >= 400):
-            module.fail_json(msg="Failure downloading %s, %s" % (url, 
info['msg']))
+            module.fail_json(msg="Failure downloading %s, %s" % 
(mask_url(url), info['msg']))
         data = rsp.read(bufsize)
         while data:
             fetch_temp_file.write(data)
             data = rsp.read(bufsize)
         fetch_temp_file.close()
     except Exception as e:
-        module.fail_json(msg="Failure downloading %s, %s" % (url, 
to_native(e)))
+        module.fail_json(msg="Failure downloading %s, %s" % (mask_url(url), 
to_native(e)))
     return fetch_temp_file.name
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.21.3/lib/ansible/modules/apt_key.py 
new/ansible_core-2.21.4/lib/ansible/modules/apt_key.py
--- old/ansible_core-2.21.3/lib/ansible/modules/apt_key.py      2026-08-10 
17:52:54.000000000 +0200
+++ new/ansible_core-2.21.4/lib/ansible/modules/apt_key.py      2026-09-08 
18:39:02.000000000 +0200
@@ -179,7 +179,7 @@
 from ansible.module_utils.common.text.converters import to_native
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.common.locale import get_best_parsable_locale
-from ansible.module_utils.urls import fetch_url
+from ansible.module_utils.urls import fetch_url, mask_url
 
 
 apt_key_bin = None
@@ -317,11 +317,11 @@
         # note: validate_certs and other args are pulled from module directly
         rsp, info = fetch_url(module, url, use_proxy=True)
         if info['status'] != 200:
-            module.fail_json(msg="Failed to download key at %s: %s" % (url, 
info['msg']))
+            module.fail_json(msg="Failed to download key at %s: %s" % 
(mask_url(url), info['msg']))
 
         return rsp.read()
     except Exception:
-        module.fail_json(msg=f"Error getting key id from url: {url}")
+        module.fail_json(msg=f"Error getting key id from url: {mask_url(url)}")
 
 
 def get_key_id_from_file(module, filename, data=None):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.21.3/lib/ansible/modules/get_url.py 
new/ansible_core-2.21.4/lib/ansible/modules/get_url.py
--- old/ansible_core-2.21.3/lib/ansible/modules/get_url.py      2026-08-10 
17:52:54.000000000 +0200
+++ new/ansible_core-2.21.4/lib/ansible/modules/get_url.py      2026-09-08 
18:39:02.000000000 +0200
@@ -378,7 +378,7 @@
 
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.common.text.converters import to_native
-from ansible.module_utils.urls import fetch_url, url_argument_spec
+from ansible.module_utils.urls import fetch_url, url_argument_spec, mask_url
 
 # ==============================================================
 # url handling
@@ -405,14 +405,14 @@
     elapsed = (datetime.now(timezone.utc) - start).seconds
 
     if info['status'] == 304:
-        module.exit_json(url=url, dest=dest, changed=False, 
msg=info.get('msg', ''), status_code=info['status'], elapsed=elapsed)
+        module.exit_json(url=mask_url(url), dest=dest, changed=False, 
msg=info.get('msg', ''), status_code=info['status'], elapsed=elapsed)
 
     # Exceptions in fetch_url may result in a status -1, the ensures a proper 
error to the user in all cases
     if info['status'] == -1:
-        module.fail_json(msg=info['msg'], url=url, dest=dest, elapsed=elapsed)
+        module.fail_json(msg=info['msg'], url=mask_url(url), dest=dest, 
elapsed=elapsed)
 
     if info['status'] != 200 and not url.startswith('file:/') and not 
(url.startswith('ftp:/') and info.get('msg', '').startswith('OK')):
-        module.fail_json(msg="Request failed", status_code=info['status'], 
response=info['msg'], url=url, dest=dest, elapsed=elapsed)
+        module.fail_json(msg="Request failed", status_code=info['status'], 
response=info['msg'], url=mask_url(url), dest=dest, elapsed=elapsed)
 
     # create a temporary file and copy content to do checksum-based replacement
     if tmp_dest:
@@ -555,7 +555,7 @@
         checksum_src=None,
         dest=dest,
         elapsed=0,
-        url=url,
+        url=mask_url(url),
     )
 
     dest_is_dir = os.path.isdir(dest)
@@ -587,7 +587,7 @@
                 checksum = None
 
             if checksum is None:
-                module.fail_json(msg="Unable to find a checksum for file '%s' 
in '%s'" % (filename, checksum_url))
+                module.fail_json(msg="Unable to find a checksum for file '%s' 
in '%s'" % (filename, mask_url(checksum_url)))
         # Remove any non-alphanumeric characters, including the infamous
         # Unicode zero-width space
         checksum = re.sub(r'\W+', '', checksum).lower()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.21.3/lib/ansible/modules/rpm_key.py 
new/ansible_core-2.21.4/lib/ansible/modules/rpm_key.py
--- old/ansible_core-2.21.3/lib/ansible/modules/rpm_key.py      2026-08-10 
17:52:54.000000000 +0200
+++ new/ansible_core-2.21.4/lib/ansible/modules/rpm_key.py      2026-09-08 
18:39:02.000000000 +0200
@@ -96,7 +96,7 @@
 
 # import module snippets
 from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.urls import fetch_url
+from ansible.module_utils.urls import fetch_url, mask_url
 from ansible.module_utils.compat.version import LooseVersion
 from ansible.module_utils.common.text.converters import to_native
 
@@ -478,11 +478,11 @@
         """Downloads a key from url, returns a valid path to a gpg key"""
         rsp, info = fetch_url(self.module, url)
         if info['status'] != 200:
-            self.module.fail_json(msg="failed to fetch key at %s , error was: 
%s" % (url, info['msg']))
+            self.module.fail_json(msg="failed to fetch key at %s , error was: 
%s" % (mask_url(url), info['msg']))
 
         key = rsp.read()
         if not is_pubkey(key):
-            self.module.fail_json(msg="Not a public key: %s" % url)
+            self.module.fail_json(msg="Not a public key: %s" % mask_url(url))
         tmpfd, tmpname = tempfile.mkstemp()
         self.module.add_cleanup_file(tmpname)
         with os.fdopen(tmpfd, "w+b") as tmpfile:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.21.3/lib/ansible/modules/tempfile.py 
new/ansible_core-2.21.4/lib/ansible/modules/tempfile.py
--- old/ansible_core-2.21.3/lib/ansible/modules/tempfile.py     2026-08-10 
17:52:54.000000000 +0200
+++ new/ansible_core-2.21.4/lib/ansible/modules/tempfile.py     2026-09-08 
18:39:02.000000000 +0200
@@ -53,6 +53,8 @@
 - module: ansible.windows.win_tempfile
 author:
   - Krzysztof Magosa (@krzysztof-magosa)
+notes:
+  - O(prefix) and O(suffix) must be file name components and must not contain 
path separators.
 """
 
 EXAMPLES = """
@@ -88,7 +90,7 @@
   sample: "/tmp/ansible.bMlvdk"
 """
 
-from os import close
+import os
 from tempfile import mkstemp, mkdtemp
 
 from ansible.module_utils.basic import AnsibleModule
@@ -105,18 +107,26 @@
         ),
     )
 
+    prefix = module.params['prefix']
+    suffix = module.params['suffix']
+
+    if prefix and prefix != os.path.basename(prefix):
+        module.fail_json(msg=f"prefix must be a valid file name component, got 
{prefix!r}")
+    if suffix and suffix != os.path.basename(suffix):
+        module.fail_json(msg=f"suffix must be a valid file name component, got 
{suffix!r}")
+
     try:
         if module.params['state'] == 'file':
             handle, path = mkstemp(
-                prefix=module.params['prefix'],
-                suffix=module.params['suffix'],
+                prefix=prefix,
+                suffix=suffix,
                 dir=module.params['path'],
             )
-            close(handle)
+            os.close(handle)
         else:
             path = mkdtemp(
-                prefix=module.params['prefix'],
-                suffix=module.params['suffix'],
+                prefix=prefix,
+                suffix=suffix,
                 dir=module.params['path'],
             )
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.21.3/lib/ansible/modules/uri.py 
new/ansible_core-2.21.4/lib/ansible/modules/uri.py
--- old/ansible_core-2.21.3/lib/ansible/modules/uri.py  2026-08-10 
17:52:54.000000000 +0200
+++ new/ansible_core-2.21.4/lib/ansible/modules/uri.py  2026-09-08 
18:39:02.000000000 +0200
@@ -447,6 +447,7 @@
 from ansible.module_utils.urls import (
     fetch_url,
     get_response_filename,
+    mask_url,
     parse_content_type,
     prepare_multipart,
     url_argument_spec,
@@ -635,6 +636,9 @@
     ciphers = module.params['ciphers']
     use_netrc = module.params['use_netrc']
 
+    # for errors and to compare to fetched responses
+    masked_url = mask_url(url)
+
     if not re.match('^[A-Z]+$', method):
         module.fail_json(msg="Parameter 'method' needs to be a single word in 
uppercase, like GET or POST.")
 
@@ -716,14 +720,14 @@
             # may have been stored in the info as 'body'
             content = info.pop('body', b'')
         except http.client.HTTPException as http_err:
-            module.fail_json(msg=f"HTTP Error while fetching {url}: 
{to_native(http_err)}")
+            module.fail_json(msg=f"HTTP Error while fetching {masked_url}: 
{to_native(http_err)}")
     elif r:
         content = r
     else:
         content = None
 
     resp = {}
-    resp['redirected'] = info['url'] != url
+    resp['redirected'] = info['url'] != masked_url
     resp.update(info)
 
     resp['elapsed'] = elapsed
@@ -751,7 +755,7 @@
         uresp[ukey] = value
 
     if 'location' in uresp:
-        uresp['location'] = urljoin(url, uresp['location'])
+        uresp['location'] = urljoin(masked_url, uresp['location'])
 
     # Default content_encoding to try
     if isinstance(content, bytes):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.3/lib/ansible/plugins/action/unarchive.py 
new/ansible_core-2.21.4/lib/ansible/plugins/action/unarchive.py
--- old/ansible_core-2.21.3/lib/ansible/plugins/action/unarchive.py     
2026-08-10 17:52:54.000000000 +0200
+++ new/ansible_core-2.21.4/lib/ansible/plugins/action/unarchive.py     
2026-09-08 18:39:02.000000000 +0200
@@ -21,6 +21,7 @@
 
 from ansible.errors import AnsibleActionFail, AnsibleActionSkip
 from ansible.module_utils.parsing.convert_bool import boolean
+from ansible.module_utils.urls import mask_url
 from ansible.plugins.action import ActionBase
 
 
@@ -64,10 +65,15 @@
                     raise AnsibleActionSkip("skipped, since %s exists" % 
creates)
 
             dest = self._remote_expand_user(dest)  # CCTODO: Fix path for 
Windows hosts.
-            source = os.path.expanduser(source)
 
+            # if not remote, we need to get source at controller
             if not remote_src:
-                source = self._loader.get_real_file(self._find_needle('files', 
source), decrypt=decrypt)
+                if '://' in source:
+                    # TODO: implement using open_url?
+                    raise AnsibleActionFail(f"Unsupported option, an URI src 
({mask_url(source)}) is only supported when remote_src is True")
+                else:
+                    source = os.path.expanduser(source)
+                    source = 
self._loader.get_real_file(self._find_needle('files', source), decrypt=decrypt)
 
             remote_stat = self._execute_remote_stat(dest, all_vars=task_vars, 
follow=True)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.3/lib/ansible/plugins/lookup/url.py 
new/ansible_core-2.21.4/lib/ansible/plugins/lookup/url.py
--- old/ansible_core-2.21.3/lib/ansible/plugins/lookup/url.py   2026-08-10 
17:52:54.000000000 +0200
+++ new/ansible_core-2.21.4/lib/ansible/plugins/lookup/url.py   2026-09-08 
18:39:02.000000000 +0200
@@ -211,8 +211,8 @@
 from urllib.error import HTTPError, URLError
 
 from ansible.errors import AnsibleError
-from ansible.module_utils.common.text.converters import to_text, to_native
-from ansible.module_utils.urls import open_url, ConnectionError, 
SSLValidationError
+from ansible.module_utils.common.text.converters import to_text
+from ansible.module_utils.urls import open_url, mask_url, ConnectionError, 
SSLValidationError
 from ansible.plugins.lookup import LookupBase
 from ansible.utils.display import Display
 
@@ -227,7 +227,7 @@
 
         ret = []
         for term in terms:
-            display.vvvv("url lookup connecting to %s" % term)
+            display.vvvv("url lookup connecting to %s" % mask_url(term))
             if self.get_option('follow_redirects') in ('yes', 'no'):
                 display.deprecated(
                     msg="Using 'yes' or 'no' for 'follow_redirects' parameter 
is deprecated.",
@@ -253,13 +253,13 @@
                     use_netrc=self.get_option('use_netrc')
                 )
             except HTTPError as e:
-                raise AnsibleError("Received HTTP error for %s : %s" % (term, 
to_native(e)))
+                raise AnsibleError(f"Received HTTP error for 
{mask_url(term)}") from e
             except URLError as e:
-                raise AnsibleError("Failed lookup url for %s : %s" % (term, 
to_native(e)))
+                raise AnsibleError(f"Failed lookup url for {mask_url(term)}") 
from e
             except SSLValidationError as e:
-                raise AnsibleError("Error validating the server's certificate 
for %s: %s" % (term, to_native(e)))
+                raise AnsibleError(f"Error validating the server's certificate 
for {mask_url(term)}") from e
             except ConnectionError as e:
-                raise AnsibleError("Error connecting to %s: %s" % (term, 
to_native(e)))
+                raise AnsibleError(f"Error connecting to {mask_url(term)}") 
from e
 
             if self.get_option('split_lines'):
                 for line in response.read().splitlines():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.21.3/lib/ansible/release.py 
new/ansible_core-2.21.4/lib/ansible/release.py
--- old/ansible_core-2.21.3/lib/ansible/release.py      2026-08-10 
17:52:54.000000000 +0200
+++ new/ansible_core-2.21.4/lib/ansible/release.py      2026-09-08 
18:39:02.000000000 +0200
@@ -17,6 +17,6 @@
 
 from __future__ import annotations
 
-__version__ = '2.21.3'
+__version__ = '2.21.4'
 __author__ = 'Ansible, Inc.'
 __codename__ = "The Rain Song"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.3/test/integration/targets/dnf-oldest/tasks/main.yml 
new/ansible_core-2.21.4/test/integration/targets/dnf-oldest/tasks/main.yml
--- old/ansible_core-2.21.3/test/integration/targets/dnf-oldest/tasks/main.yml  
2026-08-10 17:52:54.000000000 +0200
+++ new/ansible_core-2.21.4/test/integration/targets/dnf-oldest/tasks/main.yml  
2026-09-08 18:39:02.000000000 +0200
@@ -1,9 +1,9 @@
 - when: ansible_distribution == "Fedora"
   block:
-    - name: Ensure libdnf is not installed
-      command: dnf remove -y python3-libdnf5
+    - name: Downgrade the dnf5 stack to the original version
+      command: dnf distro-sync -y --disable-repo=* --enable-repo=fedora 
--allowerasing libdnf5* dnf5* python3-libdnf5
 
-    - name: Downgrade dnf to the original version
+    - name: Ensure the original python3-libdnf5 bindings are installed
       command: dnf install -y --disable-repo=* --enable-repo=fedora 
python3-libdnf5
 
     - name: Run DNF tests
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.3/test/integration/targets/get_url/tasks/main.yml 
new/ansible_core-2.21.4/test/integration/targets/get_url/tasks/main.yml
--- old/ansible_core-2.21.3/test/integration/targets/get_url/tasks/main.yml     
2026-08-10 17:52:54.000000000 +0200
+++ new/ansible_core-2.21.4/test/integration/targets/get_url/tasks/main.yml     
2026-09-08 18:39:02.000000000 +0200
@@ -835,3 +835,15 @@
 - assert:
     that:
       - get_dir_filename.dest == remote_tmp_dir ~ "/filename.json"
+
+- name: Test auth fail downloading to dir
+  get_url:
+    url: 'https://secretuser:secretpassword@{{ httpbin_host }}/nonexistant'
+    dest: "{{ remote_tmp_dir }}"
+  ignore_errors: true
+  register: auth_error
+
+- name: Ensure secret is not visible
+  assert:
+    that:
+      - "'secret' not in auth_error|to_json"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.3/test/integration/targets/tempfile/tasks/main.yml 
new/ansible_core-2.21.4/test/integration/targets/tempfile/tasks/main.yml
--- old/ansible_core-2.21.3/test/integration/targets/tempfile/tasks/main.yml    
2026-08-10 17:52:54.000000000 +0200
+++ new/ansible_core-2.21.4/test/integration/targets/tempfile/tasks/main.yml    
2026-09-08 18:39:02.000000000 +0200
@@ -35,6 +35,36 @@
   register: temp_dir_non_existent_path
   ignore_errors: yes
 
+- name: Reject path components in prefix for file
+  tempfile:
+    path: "{{ remote_tmp_dir }}"
+    prefix: "../../nested"
+  register: temp_file_invalid_prefix
+  ignore_errors: yes
+
+- name: Reject path components in suffix for file
+  tempfile:
+    path: "{{ remote_tmp_dir }}"
+    suffix: "../world"
+  register: temp_file_invalid_suffix
+  ignore_errors: yes
+
+- name: Reject path components in prefix for directory
+  tempfile:
+    state: directory
+    path: "{{ remote_tmp_dir }}"
+    prefix: "../../nested"
+  register: temp_dir_invalid_prefix
+  ignore_errors: yes
+
+- name: Reject path components in suffix for directory
+  tempfile:
+    state: directory
+    path: "{{ remote_tmp_dir }}"
+    suffix: "../world"
+  register: temp_dir_invalid_suffix
+  ignore_errors: yes
+
 - name: Check results
   assert:
     that:
@@ -61,3 +91,12 @@
       - temp_file_non_existent_path is failed
 
       - temp_dir_non_existent_path is failed
+
+      - temp_file_invalid_prefix is failed
+      - '"prefix must be a valid file name component" in 
temp_file_invalid_prefix.msg'
+      - temp_file_invalid_suffix is failed
+      - '"suffix must be a valid file name component" in 
temp_file_invalid_suffix.msg'
+      - temp_dir_invalid_prefix is failed
+      - '"prefix must be a valid file name component" in 
temp_dir_invalid_prefix.msg'
+      - temp_dir_invalid_suffix is failed
+      - '"suffix must be a valid file name component" in 
temp_dir_invalid_suffix.msg'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.3/test/integration/targets/unarchive/auth_mask.yml 
new/ansible_core-2.21.4/test/integration/targets/unarchive/auth_mask.yml
--- old/ansible_core-2.21.3/test/integration/targets/unarchive/auth_mask.yml    
1970-01-01 01:00:00.000000000 +0100
+++ new/ansible_core-2.21.4/test/integration/targets/unarchive/auth_mask.yml    
2026-09-08 18:39:02.000000000 +0200
@@ -0,0 +1,18 @@
+- hosts: testhost
+  gather_facts: false
+  tasks:
+  - name: unarchive auth fail a tar from an URL download from remote
+    unarchive: src="http://secretuser:secretpassword@localhost/nothere"; 
dest="/tmp/" remote_src=no
+    ignore_errors: true
+    register: auth1
+  
+  - name: unarchive auth fail a tar from an URL download from controller
+    unarchive: src="http://secretuser:secretpassword@localhost/nothere"; 
dest="/tmp/" remote_src=yes
+    ignore_errors: true
+    register: auth2
+  
+  - name: ensure not disclosure
+    assert:
+      that:
+        - "'secret' not in auth1|to_json"
+        - "'secret' not in auth2|to_json"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.3/test/integration/targets/unarchive/runme.sh 
new/ansible_core-2.21.4/test/integration/targets/unarchive/runme.sh
--- old/ansible_core-2.21.3/test/integration/targets/unarchive/runme.sh 
2026-08-10 17:52:54.000000000 +0200
+++ new/ansible_core-2.21.4/test/integration/targets/unarchive/runme.sh 
2026-09-08 18:39:02.000000000 +0200
@@ -6,3 +6,8 @@
 
 # https://github.com/ansible/ansible/issues/80710
 ANSIBLE_REMOTE_TMP=./ansible ansible-playbook -i ../../inventory 
test_relative_tmp_dir.yml -v "$@"
+
+# ensure uri inline secrets are masked
+ULOG="${OUTPUT_DIR}/$$-unarchive.log"
+ansible-playbook auth_mask.yml -i ../../inventory "$@" 2>&1 | tee "${ULOG}"
+[ "$(grep -Ec 'secret' "${ULOG}")" = "0" ]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.3/test/integration/targets/uri/tasks/main.yml 
new/ansible_core-2.21.4/test/integration/targets/uri/tasks/main.yml
--- old/ansible_core-2.21.3/test/integration/targets/uri/tasks/main.yml 
2026-08-10 17:52:54.000000000 +0200
+++ new/ansible_core-2.21.4/test/integration/targets/uri/tasks/main.yml 
2026-09-08 18:39:02.000000000 +0200
@@ -774,3 +774,15 @@
   assert:
     that:
       - uri_check.msg == "This action (uri) does not support check mode."
+
+- name: Test auth fail downloading to dir
+  uri:
+    url: 'https://secretuser:secretpassword@{{ httpbin_host }}/nonexistant'
+    dest: "{{ remote_tmp_dir }}"
+  ignore_errors: true
+  register: auth_error
+
+- name: Ensure secret is not visible
+  assert:
+    that:
+      - "'secret' not in auth_error|to_json"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.3/test/units/module_utils/urls/test_mask_url.py 
new/ansible_core-2.21.4/test/units/module_utils/urls/test_mask_url.py
--- old/ansible_core-2.21.3/test/units/module_utils/urls/test_mask_url.py       
1970-01-01 01:00:00.000000000 +0100
+++ new/ansible_core-2.21.4/test/units/module_utils/urls/test_mask_url.py       
2026-09-08 18:39:02.000000000 +0200
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+# (c) 2026 The Ansible Project
+# GNU General Public License v3.0+ (see COPYING or 
https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import annotations
+
+import pytest
+
+from ansible.module_utils.urls import mask_url
+
+
+# for test data use 'secret' as part of any parameter that requires masking, 
avoid elsewhere
[email protected](
+    'url, wanted',
+    (
+        ('http://nothingtoseehere.com', ('nothingtoseehere.com', 'http')),
+        ('http://nothingtoseehere.com:80/stuff.asp?he=no', 
('http://nothingtoseehere.com:80/stuff.asp?he=no',)),
+        
('http://nothingtoseehere.com:80?password=intheclear&user=wrongbutweignore', 
('wrongbut', 'intheclear', 'password')),
+        ('https://[email protected]/index.html', ('hideme.com', 
'index.html', '*')),
+        
('https://[email protected]/index.html?token=nothidden&user=alsonothidden', 
('token', 'nothidden', 'alsonothidden', 'user')),
+        ('https://secretuser:[email protected]/randomfile.html', 
('randomfile.html')),
+        ('https://secretuser:[email protected]:443/protected.html', 
('protected.html', '443')),
+        ('ftp://secretuser:[email protected]/subdir/intheclear.txt', 
('subdir', 'intheclear.txt', 'ftp', 'files.insecure')),
+        ('sftp://secretuser:[email protected]/subdir2/encrypted', 
('encrypted', 'sftp')),
+        ('ftps://secretuser:[email protected]/yolo.asc', ('yolo.asc', 
'file.secure')),
+        ('ftps://file.server/yolo.asc', ('yolo.asc')),
+        ('ftps://secretuser:[email protected]/yolo.asc', 
('file.server/yolo.asc')),
+        ('redis://:[email protected]:6379/0', ('cache.internal', 
'6379', 'redis')),
+        ('amqp://:[email protected]:5672/vhost', ('rabbit.internal', 
'5672', 'vhost')),
+    )
+)
+def test_mask_url(url, wanted):
+
+    masked = mask_url(url)
+    assert 'secret' not in masked
+
+    for notmasked in wanted:
+        assert notmasked in masked

++++++ ansible_core-2.21.3.tar.gz.sha256 -> ansible_core-2.21.4.tar.gz.sha256 
++++++
--- /work/SRC/openSUSE:Factory/ansible-core/ansible_core-2.21.3.tar.gz.sha256   
2026-08-11 17:18:12.388768475 +0200
+++ 
/work/SRC/openSUSE:Factory/.ansible-core.new.383539/ansible_core-2.21.4.tar.gz.sha256
       2026-09-19 22:23:14.389852902 +0200
@@ -1 +1 @@
-4194fbd82273cbacfd06d86d74d2d7168c3c4b8426c03e93562cd7217f811ae1  
ansible_core-2.21.3.tar.gz
+81a9329f4f12cfa5008dcab5d1bf23ae69b7effc08c0f00048ab2461147ae95a  
ansible_core-2.21.4.tar.gz

Reply via email to