diff --git a/0001-backport-fix-cve-2025-68463.patch b/0001-backport-fix-cve-2025-68463.patch new file mode 100644 index 0000000000000000000000000000000000000000..c0ecc0d085b69525bf51f5726cea1c2141e33067 --- /dev/null +++ b/0001-backport-fix-cve-2025-68463.patch @@ -0,0 +1,166 @@ +From 7c649d8e4e399b77cf1584ae816ffe08265b9a72 Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Sun, 25 Jan 2026 01:27:43 +0100 +Subject: [PATCH] Bio/Entrez/Parser.py: backport CVE-2025-68463 for Biopython 1.86 + +Rebased to Biopython 1.86 tree. + +--- +diff --git a/Bio/Entrez/Parser.py b/Bio/Entrez/Parser.py +index dd401a2..9f8d226 100644 +--- a/Bio/Entrez/Parser.py ++++ b/Bio/Entrez/Parser.py +@@ -367,6 +367,7 @@ class DataHandler(metaclass=DataHandlerMeta): + self.dtd_urls = [] + self.element = None + self.level = 0 ++ self.bypass_url_security = False + self.data = [] + self.attributes = None + self.allowed_tags = None +@@ -579,6 +580,7 @@ class DataHandler(metaclass=DataHandlerMeta): + key = "%s noNamespaceSchemaLocation" % self.schema_namespace + schema = attrs[key] + handle = self.open_xsd_file(os.path.basename(schema)) ++ self.verify_security(schema) + # if there is no local xsd file grab the url and parse the file + if not handle: + handle = urlopen(schema) +@@ -1092,6 +1094,21 @@ class DataHandler(metaclass=DataHandlerMeta): + handle.write(text) + handle.close() + ++ def verify_security(self, url, verify_hostname=True): ++ """Check if the given URL is from a trustable source. ++ ++ When ``self.bypass_url_security`` evaluates to ``True``, ++ all URL security checks will be skipped. ++ """ ++ if not self.bypass_url_security: ++ parts = urlparse(url) ++ scheme = parts.scheme ++ hostname = parts.hostname ++ if scheme != "https" or ( ++ verify_hostname and not hostname.endswith(".nlm.nih.gov") ++ ): ++ raise ValueError(f"Expected secure URL to NCBI, found {url!r}") ++ + def externalEntityRefHandler(self, context, base, systemId, publicId): + """Handle external entity reference in order to cache DTD locally. + +@@ -1120,26 +1137,32 @@ class DataHandler(metaclass=DataHandlerMeta): + url = source.rstrip("/") + "/" + systemId + else: + raise ValueError("Unexpected URL scheme %r" % urlinfo.scheme) ++ ++ self.verify_security(url, verify_hostname=not self.dtd_urls) ++ + self.dtd_urls.append(url) +- # First, try to load the local version of the DTD file +- location, filename = os.path.split(systemId) +- handle = self.open_dtd_file(filename) +- if not handle: +- # DTD is not available as a local file. Try accessing it through +- # the internet instead. +- try: +- handle = urlopen(url) +- except OSError: +- raise RuntimeError(f"Failed to access {filename} at {url}") from None +- text = handle.read() ++ try: ++ # First, try to load the local version of the DTD file ++ location, filename = os.path.split(systemId) ++ handle = self.open_dtd_file(filename) ++ if not handle: ++ # DTD is not available as a local file. Try accessing it through ++ # the internet instead. ++ try: ++ handle = urlopen(url) ++ except OSError: ++ raise RuntimeError(f"Failed to access {filename} at {url}") from None ++ text = handle.read() ++ handle.close() ++ self.save_dtd_file(filename, text) ++ handle = BytesIO(text) ++ ++ parser = self.parser.ExternalEntityParserCreate(context) ++ parser.ElementDeclHandler = self.elementDecl ++ parser.ParseFile(handle) + handle.close() +- self.save_dtd_file(filename, text) +- handle = BytesIO(text) +- +- parser = self.parser.ExternalEntityParserCreate(context) +- parser.ElementDeclHandler = self.elementDecl +- parser.ParseFile(handle) +- handle.close() +- self.dtd_urls.pop() ++ finally: ++ self.dtd_urls.pop() ++ + self.parser.StartElementHandler = self.startElementHandler + return 1 +diff --git a/Tests/test_Entrez_parser.py b/Tests/test_Entrez_parser.py +index fa7b4ee..06275db 100644 +--- a/Tests/test_Entrez_parser.py ++++ b/Tests/test_Entrez_parser.py +@@ -9,6 +9,8 @@ import os + import pickle + import unittest + from io import BytesIO ++from textwrap import dedent ++from unittest.mock import call, Mock + + from Bio import Entrez + from Bio import StreamModeError +@@ -12347,6 +12349,50 @@ We designed and generated pulmonary imaging biomarker pipelines to facilitate hi + self.assertRaises(CorruptedXMLError, next, records) + + ++class UrlSecurityCheckTest(unittest.TestCase): ++ """Test for DTD and XSL URL validation.""" ++ ++ def test_continued_url_security_checking(self): ++ from Bio.Entrez import Parser ++ ++ content = dedent( ++ """\ ++ ++ ++ ++ ++ """ ++ ).encode("utf-8") ++ ++ handler = Parser.DataHandler( ++ validate=True, escape=False, ignore_errors=False ++ ) ++ handler.verify_security = Mock(side_effect=handler.verify_security) ++ ++ with self.assertRaises(ValueError) as caught: ++ handler.read(BytesIO(content)) ++ ++ self.assertIn("Expected secure URL to NCBI", caught.exception.args[0]) ++ self.assertEqual( ++ handler.verify_security.call_args_list, ++ [ ++ call( ++ "https://www.ncbi.nlm.nih.gov/dtd/NCBI_BlastOutput.dtd", ++ verify_hostname=True, ++ ), ++ call( ++ "https://www.ncbi.nlm.nih.gov/dtd/NCBI_Entity.mod.dtd", ++ verify_hostname=False, ++ ), ++ call( ++ "https://www.ncbi.nlm.nih.gov/dtd/NCBI_BlastOutput.mod.dtd", ++ verify_hostname=False, ++ ), ++ call("https://host.invalid/404.dtd"), ++ ], ++ ) ++ ++ + if __name__ == "__main__": + runner = unittest.TextTestRunner(verbosity=2) + unittest.main(testRunner=runner) diff --git a/biopython-1.83.tar.gz b/biopython-1.86.tar.gz similarity index 67% rename from biopython-1.83.tar.gz rename to biopython-1.86.tar.gz index 3f2b75b358e17171339afb815b0be8d35c65b147..42b8f0436c1ac13d2cf331c199d535629499516b 100644 Binary files a/biopython-1.83.tar.gz and b/biopython-1.86.tar.gz differ diff --git a/python-biopython.spec b/python-biopython.spec index 6edf5e1c88f9aa9488746416b5b9486b0f3954ec..6b27b0bad4a80c2e8b0b9cf49c73088a763cb777 100644 --- a/python-biopython.spec +++ b/python-biopython.spec @@ -1,6 +1,6 @@ %define anolis_release 1 %global pypi_name biopython -%global pypi_version 1.83 +%global pypi_version 1.86 Name: python-%{pypi_name} Version: %{pypi_version} @@ -11,6 +11,9 @@ License: None URL: https://biopython.org/ Source0: %{pypi_source} +# from https://github.com/biopython/biopython/pull/5148 +Patch0001: 0001-backport-fix-cve-2025-68463.patch + BuildRequires: python3-devel BuildRequires: python3dist(numpy) BuildRequires: python3dist(setuptools) @@ -30,7 +33,7 @@ in Python by an international team of developers. %prep -%autosetup -n %{pypi_name}-%{pypi_version} +%autosetup -n %{pypi_name}-%{pypi_version} -p1 # Remove bundled egg-info rm -rf %{pypi_name}.egg-info @@ -51,5 +54,8 @@ rm -rf %{pypi_name}.egg-info %{python3_sitearch}/%{pypi_name}-%{pypi_version}-py%{python3_version}.egg-info %changelog +* Mon Mar 02 2026 tomcruiseqi - 1.86-1 +- Update to 1.86 and apply patch to fix CVE-2025-68463 + * Thu Jan 18 2024 Zhongling He - 1.83-1 -- Initial package. \ No newline at end of file +- Initial package.