https://github.com/python/cpython/commit/a25e09721af7b9ea55a1a3baf5282bdf7670888e
commit: a25e09721af7b9ea55a1a3baf5282bdf7670888e
branch: main
author: Cody Maloney <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-03-30T22:05:18+03:00
summary:

gh-139633: Run netrc file permission check only once per parse (GH-139634)

Change the `.netrc` security check to be run once per parse of the
default file rather than once per line inside the file.

files:
A Misc/NEWS.d/next/Library/2025-10-05-15-38-02.gh-issue-139633.l3P839.rst
M Lib/netrc.py
M Lib/test/test_netrc.py

diff --git a/Lib/netrc.py b/Lib/netrc.py
index 750b5071e3c65f..a28ea297df894b 100644
--- a/Lib/netrc.py
+++ b/Lib/netrc.py
@@ -152,23 +152,28 @@ def _parse(self, file, fp, default_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."""
diff --git a/Lib/test/test_netrc.py b/Lib/test/test_netrc.py
index 9d720f627102e3..354081e96213a6 100644
--- a/Lib/test/test_netrc.py
+++ b/Lib/test/test_netrc.py
@@ -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 @@ def test_security(self):
             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
index 00000000000000..94bd18074f8d2d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-10-05-15-38-02.gh-issue-139633.l3P839.rst
@@ -0,0 +1,2 @@
+The :mod:`netrc` security check is now run once per parse rather than once
+per entry.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to