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]