]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-139633: Run netrc file permission check only once per parse (GH-139634)
authorCody Maloney <cmaloney@users.noreply.github.com>
Mon, 30 Mar 2026 19:05:18 +0000 (12:05 -0700)
committerGitHub <noreply@github.com>
Mon, 30 Mar 2026 19:05:18 +0000 (22:05 +0300)
Change the `.netrc` security check to be run once per parse of the
default file rather than once per line inside the file.

Lib/netrc.py
Lib/test/test_netrc.py
Misc/NEWS.d/next/Library/2025-10-05-15-38-02.gh-issue-139633.l3P839.rst [new file with mode: 0644]

index 750b5071e3c65f06bbe8c0bcfe9ab1e75d02fda7..a28ea297df894b60ee0d86f9b2cffffe432b7010 100644 (file)
@@ -152,23 +152,28 @@ class netrc:
                 else:
                     raise NetrcParseError("bad follower token %r" % tt,
                                           file, lexer.lineno)
-            self._security_check(fp, default_netrc, self.hosts[entryname][0])
-
-    def _security_check(self, fp, default_netrc, login):
-        if _can_security_check() and default_netrc and login != "anonymous":
-            prop = os.fstat(fp.fileno())
-            current_user_id = os.getuid()
-            if prop.st_uid != current_user_id:
-                fowner = _getpwuid(prop.st_uid)
-                user = _getpwuid(current_user_id)
-                raise NetrcParseError(
-                    f"~/.netrc file owner ({fowner}) does not match"
-                    f" current user ({user})")
-            if (prop.st_mode & (stat.S_IRWXG | stat.S_IRWXO)):
-                raise NetrcParseError(
-                    "~/.netrc access too permissive: access"
-                    " permissions must restrict access to only"
-                    " the owner")
+
+        if _can_security_check() and default_netrc:
+            for entry in self.hosts.values():
+                if entry[0] != "anonymous":
+                    # Raises on security issue; once passed once can exit.
+                    self._security_check(fp)
+                    return
+
+    def _security_check(self, fp):
+        prop = os.fstat(fp.fileno())
+        current_user_id = os.getuid()
+        if prop.st_uid != current_user_id:
+            fowner = _getpwuid(prop.st_uid)
+            user = _getpwuid(current_user_id)
+            raise NetrcParseError(
+                f"~/.netrc file owner ({fowner}) does not match"
+                f" current user ({user})")
+        if (prop.st_mode & (stat.S_IRWXG | stat.S_IRWXO)):
+            raise NetrcParseError(
+                "~/.netrc access too permissive: access"
+                " permissions must restrict access to only"
+                " the owner")
 
     def authenticators(self, host):
         """Return a (user, account, password) tuple for given host."""
index 9d720f627102e34599444de0206603af32771814..354081e96213a668b286a20f98963f29c7daf10b 100644 (file)
@@ -1,6 +1,9 @@
 import netrc, os, unittest, sys, textwrap
+from pathlib import Path
 from test import support
 from test.support import os_helper
+from unittest.mock import patch
+
 
 temp_filename = os_helper.TESTFN
 
@@ -309,6 +312,26 @@ class NetrcTestCase(unittest.TestCase):
             self.assertEqual(nrc.hosts['foo.domain.com'],
                              ('anonymous', '', 'pass'))
 
+    @unittest.skipUnless(os.name == 'posix', 'POSIX only test')
+    @unittest.skipUnless(hasattr(os, 'getuid'), "os.getuid is required")
+    @os_helper.skip_unless_working_chmod
+    def test_security_only_once(self):
+        # Make sure security check is only run once per parse when multiple
+        # entries are found.
+        with patch.object(netrc.netrc, "_security_check") as mock:
+            with os_helper.temp_dir() as tmp_dir:
+                netrc_path = Path(tmp_dir) / '.netrc'
+                netrc_path.write_text("""\
+                machine foo.domain.com login bar password pass
+                machine bar.domain.com login foo password pass
+                """)
+                netrc_path.chmod(0o600)
+                with os_helper.EnvironmentVarGuard() as environ:
+                    environ.set('HOME', tmp_dir)
+                    netrc.netrc()
+
+            mock.assert_called_once()
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2025-10-05-15-38-02.gh-issue-139633.l3P839.rst b/Misc/NEWS.d/next/Library/2025-10-05-15-38-02.gh-issue-139633.l3P839.rst
new file mode 100644 (file)
index 0000000..94bd180
--- /dev/null
@@ -0,0 +1,2 @@
+The :mod:`netrc` security check is now run once per parse rather than once
+per entry.