diff --git a/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-111749.patch b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-111749.patch
new file mode 100644
index 0000000000..d1a7c60fb5
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-111749.patch
@@ -0,0 +1,262 @@
+From 66405536d4410c7456a52d2b6780e827f4e56d44 Mon Sep 17 00:00:00 2001
+From: Sam Bull <git@sambull.org>
+Date: Wed, 9 Sep 2026 02:21:06 +0100
+Subject: [PATCH] Fix host-only state on cookie expiration (#13674) (#13676)
+
+(cherry picked from commit 9332972cfcb0f5a2b6677e7307819ab8f13b5d09)
+(cherry picked from commit 6a07c171ef9f3be583e2907dcd1931372efca8d6)
+
+CVE: CVE-2026-111749
+Upstream-Status: Backport [https://github.com/aio-libs/aiohttp/commit/6a07c171ef9f3be583e2907dcd1931372efca8d6]
+
+Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
+---
+ CHANGES/13674.bugfix.rst     |  1 +
+ aiohttp/abc.py               |  2 +-
+ aiohttp/cookiejar.py         | 29 ++++++++--------
+ docs/client_reference.rst    | 10 ++++--
+ tests/test_client_session.py |  2 +-
+ tests/test_cookiejar.py      | 64 ++++++++++++++++++++++++++++++++++--
+ 6 files changed, 89 insertions(+), 19 deletions(-)
+ create mode 100644 CHANGES/13674.bugfix.rst
+
+diff --git a/CHANGES/13674.bugfix.rst b/CHANGES/13674.bugfix.rst
+new file mode 100644
+index 000000000..5152bceae
+--- /dev/null
++++ b/CHANGES/13674.bugfix.rst
+@@ -0,0 +1 @@
++Fixed host-only cookie state being lost on expiration -- by :user:`Dreamsorcerer`.
+diff --git a/aiohttp/abc.py b/aiohttp/abc.py
+index b67287318..74ca7216e 100644
+--- a/aiohttp/abc.py
++++ b/aiohttp/abc.py
+@@ -181,7 +181,7 @@ class AbstractCookieJar(Sized, IterableBase):
+ 
+     @property
+     @abstractmethod
+-    def host_only_cookies(self) -> frozenset[tuple[str, str]]:
++    def host_only_cookies(self) -> frozenset[tuple[str, str, str]]:
+         """Return the host-only cookies stored in this jar."""
+ 
+     @abstractmethod
+diff --git a/aiohttp/cookiejar.py b/aiohttp/cookiejar.py
+index b949cc118..1af02144e 100644
+--- a/aiohttp/cookiejar.py
++++ b/aiohttp/cookiejar.py
+@@ -134,7 +134,8 @@ class CookieJar(AbstractCookieJar):
+         self._morsel_cache: defaultdict[tuple[str, str], dict[str, Morsel[str]]] = (
+             defaultdict(dict)
+         )
+-        self._host_only_cookies: set[tuple[str, str]] = set()
++        # Cookie identity is (domain, path, name).
++        self._host_only_cookies: set[tuple[str, str, str]] = set()
+         self._unsafe = unsafe
+         self._quote_cookie = quote_cookie
+         if treat_as_secure_origin is None:
+@@ -166,7 +167,7 @@ class CookieJar(AbstractCookieJar):
+         return MappingProxyType(self._cookies)
+ 
+     @property
+-    def host_only_cookies(self) -> frozenset[tuple[str, str]]:
++    def host_only_cookies(self) -> frozenset[tuple[str, str, str]]:
+         """Return the host-only cookies stored in this jar."""
+         return frozenset(self._host_only_cookies)
+ 
+@@ -195,7 +196,7 @@ class CookieJar(AbstractCookieJar):
+                     if attr_val:
+                         morsel_data[attr] = attr_val
+                 # Persist or it reloads as a domain cookie and leaks to subdomains.
+-                if (domain, name) in self._host_only_cookies:
++                if (domain, path, name) in self._host_only_cookies:
+                     morsel_data["host_only"] = True
+                 if (exp := self._expirations.get((domain, path, name))) is not None:
+                     morsel_data["expires_timestamp"] = exp
+@@ -358,7 +359,7 @@ class CookieJar(AbstractCookieJar):
+ 
+     def _delete_cookies(self, to_del: list[tuple[str, str, str]]) -> None:
+         for domain, path, name in to_del:
+-            self._host_only_cookies.discard((domain, name))
++            self._host_only_cookies.discard((domain, path, name))
+             self._cookies[(domain, path)].pop(name, None)
+             self._morsel_cache[(domain, path)].pop(name, None)
+             self._expirations.pop((domain, path, name), None)
+@@ -395,18 +396,12 @@ class CookieJar(AbstractCookieJar):
+                 domain = ""
+                 del cookie["domain"]
+ 
+-            if not domain and hostname is not None:
+-                # Set the cookie's domain to the response hostname
+-                # and set its host-only-flag
+-                self._host_only_cookies.add((hostname, name))
+-                domain = cookie["domain"] = hostname
+-
+             if domain and domain[0] == ".":
+                 # Remove leading dot
+                 domain = domain[1:]
+                 cookie["domain"] = domain
+ 
+-            if hostname and not self._is_domain_match(domain, hostname):
++            if domain and hostname and not self._is_domain_match(domain, hostname):
+                 # Setting cookies for different domains is not allowed
+                 continue
+ 
+@@ -422,6 +417,14 @@ class CookieJar(AbstractCookieJar):
+                 cookie["path"] = path
+             path = path.rstrip("/")
+ 
++            if not domain and hostname is not None:
++                self._host_only_cookies.add((hostname, path, name))
++                domain = cookie["domain"] = hostname
++            else:
++                # A cookie with an explicit Domain attribute replaces any
++                # host-only cookie with the same (domain, path, name) identity.
++                self._host_only_cookies.discard((domain, path, name))
++
+             if max_age := cookie["max-age"]:
+                 try:
+                     delta_seconds = int(max_age)
+@@ -522,7 +525,7 @@ class CookieJar(AbstractCookieJar):
+             for name, cookie in self._cookies[p].items():
+                 domain = cookie["domain"]
+ 
+-                if (domain, name) in self._host_only_cookies and domain != hostname:
++                if domain != hostname and p + (name,) in self._host_only_cookies:
+                     continue
+ 
+                 # Skip edge case when the cookie has a trailing slash but request doesn't.
+@@ -672,7 +675,7 @@ class DummyCookieJar(AbstractCookieJar):
+         return MappingProxyType({})
+ 
+     @property
+-    def host_only_cookies(self) -> frozenset[tuple[str, str]]:
++    def host_only_cookies(self) -> frozenset[tuple[str, str, str]]:
+         """Return an empty frozenset."""
+         return frozenset()
+ 
+diff --git a/docs/client_reference.rst b/docs/client_reference.rst
+index a237fb64b..476e6e81f 100644
+--- a/docs/client_reference.rst
++++ b/docs/client_reference.rst
+@@ -2611,11 +2611,17 @@ Utilities
+ 
+    .. attribute:: host_only_cookies
+ 
+-      A :class:`frozenset` of ``(domain, name)`` tuples indicating which
+-      cookies are host-only (not sent to subdomains).
++      A :class:`frozenset` of ``(domain, path, name)`` tuples indicating
++      which cookies are host-only (not sent to subdomains).
+ 
+       .. versionadded:: 3.14
+ 
++      .. versionchanged:: 3.14.4
++
++         The tuples gained the *path* element; host-only state is tracked
++         per ``(domain, path, name)`` cookie identity so that same-named
++         cookies on other paths cannot affect it.
++
+ 
+ .. class:: DummyCookieJar(*, loop=None)
+ 
+diff --git a/tests/test_client_session.py b/tests/test_client_session.py
+index 6b3516879..4481a4bf8 100644
+--- a/tests/test_client_session.py
++++ b/tests/test_client_session.py
+@@ -758,7 +758,7 @@ async def test_cookie_jar_usage(loop: Any, aiohttp_client: Any) -> None:
+             return MappingProxyType({})
+ 
+         @property
+-        def host_only_cookies(self) -> frozenset[tuple[str, str]]:
++        def host_only_cookies(self) -> frozenset[tuple[str, str, str]]:
+             return frozenset()
+ 
+         def clear(self, predicate: abc.ClearCookiePredicate | None = None) -> None:
+diff --git a/tests/test_cookiejar.py b/tests/test_cookiejar.py
+index be6da2a5f..4fd2959e1 100644
+--- a/tests/test_cookiejar.py
++++ b/tests/test_cookiejar.py
+@@ -878,7 +878,45 @@ async def test_cookie_jar_host_only_cookies_property() -> None:
+ 
+     host_only = jar.host_only_cookies
+     assert isinstance(host_only, frozenset)
+-    assert ("example.com", "hostonly") in host_only
++    assert ("example.com", "", "hostonly") in host_only
++
++
++async def test_host_only_marker_survives_same_name_expiry_on_other_path() -> None:
++    """Expiring a same-name cookie on another path must not clear host-only state."""
++    jar = CookieJar()
++    origin = URL("http://auth.example.com/")
++    subdomain = URL("http://evil.auth.example.com/")
++
++    jar.update_cookies_from_headers(["sid=secret; Path=/"], origin)
++    assert "sid" not in jar.filter_cookies(subdomain)
++
++    # Attacker-controlled descendant expires a same-name cookie on its own path.
++    jar.update_cookies_from_headers(
++        ["sid=gone; Domain=auth.example.com; Path=/attacker; Max-Age=0"],
++        subdomain,
++    )
++
++    assert ("auth.example.com", "", "sid") in jar.host_only_cookies
++    assert "sid" not in jar.filter_cookies(subdomain)
++    assert jar.filter_cookies(origin)["sid"].value == "secret"
++
++
++async def test_explicit_domain_replacement_clears_host_only_marker() -> None:
++    """A replacing cookie with an explicit Domain is a domain cookie."""
++    jar = CookieJar()
++    origin = URL("http://example.com/")
++    subdomain = URL("http://sub.example.com/")
++
++    jar.update_cookies_from_headers(["sid=hostonly; Path=/"], origin)
++    assert ("example.com", "", "sid") in jar.host_only_cookies
++    assert "sid" not in jar.filter_cookies(subdomain)
++
++    jar.update_cookies_from_headers(
++        ["sid=domainwide; Domain=example.com; Path=/"], origin
++    )
++
++    assert jar.host_only_cookies == frozenset()
++    assert jar.filter_cookies(subdomain)["sid"].value == "domainwide"
+ 
+ 
+ async def test_cookie_jar_cookies_property_immutable() -> None:
+@@ -1857,7 +1895,7 @@ async def test_save_load_json_preserves_host_only_scope(tmp_path: Path) -> None:
+     jar_load = CookieJar()
+     jar_load.load(file_path=file_path)
+ 
+-    assert jar_load.host_only_cookies == frozenset({("auth.example.com", "sid")})
++    assert jar_load.host_only_cookies == frozenset({("auth.example.com", "", "sid")})
+     assert "sid" not in jar_load.filter_cookies(subdomain)
+     assert "sid" in jar_load.filter_cookies(issuer)
+ 
+@@ -1882,6 +1920,28 @@ async def test_save_load_json_domain_cookie_still_matches_subdomain(
+     assert "sid" in jar_load.filter_cookies(subdomain)
+ 
+ 
++async def test_save_load_json_host_only_per_path(tmp_path: Path) -> None:
++    """Verify save/load keeps host-only state per (domain, path, name)."""
++    file_path = tmp_path / "per_path.json"
++    origin = URL("https://example.com/")
++    subdomain = URL("https://sub.example.com/")
++
++    jar_save = CookieJar()
++    jar_save.update_cookies_from_headers(
++        ["sid=hostonly; Path=/", "sid=domainwide; Domain=example.com; Path=/api"],
++        origin,
++    )
++    jar_save.save(file_path=file_path)
++
++    jar_load = CookieJar()
++    jar_load.load(file_path=file_path)
++
++    assert jar_load.host_only_cookies == frozenset({("example.com", "", "sid")})
++    assert "sid" not in jar_load.filter_cookies(subdomain)
++    filtered = jar_load.filter_cookies(URL("https://sub.example.com/api/x"))
++    assert filtered["sid"].value == "domainwide"
++
++
+ async def test_save_load_json_preserves_max_age_deadline(tmp_path: Path) -> None:
+     """Verify save/load restores the absolute deadline without resetting it."""
+     file_path = tmp_path / "max_age.json"
diff --git a/meta-python/recipes-devtools/python/python3-aiohttp_3.14.3.bb b/meta-python/recipes-devtools/python/python3-aiohttp_3.14.3.bb
index 3654f95532..78845ffb56 100644
--- a/meta-python/recipes-devtools/python/python3-aiohttp_3.14.3.bb
+++ b/meta-python/recipes-devtools/python/python3-aiohttp_3.14.3.bb
@@ -10,6 +10,7 @@ SRC_URI += "file://CVE-2026-111063.patch \
             file://CVE-2026-111065.patch \
             file://CVE-2026-111066.patch \
             file://CVE-2026-111748.patch \
+            file://CVE-2026-111749.patch \
 "
 
 CVE_PRODUCT = "aiohttp:aiohttp"
