From 164792a13d2aeb7f7bc0cef250b278a091332d04 Mon Sep 17 00:00:00 2001 From: Aidan Butler Date: Mon, 20 Jul 2026 16:53:15 +0100 Subject: [PATCH 01/11] Fix redis connection leak Signed-off-by: Aidan Butler --- mcpgateway/plugins/__init__.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/mcpgateway/plugins/__init__.py b/mcpgateway/plugins/__init__.py index 3f0f5808ff..c0aba6ed29 100644 --- a/mcpgateway/plugins/__init__.py +++ b/mcpgateway/plugins/__init__.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- """Location: ./mcpgateway/plugins/__init__.py -Copyright contributors to the MCP-CONTEXT-FORGE project +Copyright 2026 SPDX-License-Identifier: Apache-2.0 +Authors: Fred Araujo Gateway plugin integration. @@ -498,14 +499,14 @@ async def _plugin_invalidation_listener() -> None: await asyncio.sleep(10) continue - pubsub = client.pubsub() - await pubsub.subscribe(_REDIS_INVALIDATION_CHANNEL) - _logger.info("Plugin invalidation listener subscribed to %s", _REDIS_INVALIDATION_CHANNEL) - backoff = 1.0 - consecutive_failures = 0 + async with client.pubsub() as pubsub: + await pubsub.subscribe(_REDIS_INVALIDATION_CHANNEL) + _logger.info("Plugin invalidation listener subscribed to %s", _REDIS_INVALIDATION_CHANNEL) + backoff = 1.0 + consecutive_failures = 0 - async for message in pubsub.listen(): - await _handle_invalidation_message(message) + async for message in pubsub.listen(): + await _handle_invalidation_message(message) except asyncio.CancelledError: _logger.info("Plugin invalidation listener cancelled") From c19c88e1d1e5987f23a4d1e47d5688d378c491b5 Mon Sep 17 00:00:00 2001 From: Aidan Butler Date: Mon, 20 Jul 2026 17:05:08 +0100 Subject: [PATCH 02/11] Fix redis connection leak Signed-off-by: Aidan Butler --- mcpgateway/plugins/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mcpgateway/plugins/__init__.py b/mcpgateway/plugins/__init__.py index c0aba6ed29..d4f3dd9232 100644 --- a/mcpgateway/plugins/__init__.py +++ b/mcpgateway/plugins/__init__.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- """Location: ./mcpgateway/plugins/__init__.py -Copyright 2026 +Copyright contributors to the MCP-CONTEXT-FORGE project SPDX-License-Identifier: Apache-2.0 -Authors: Fred Araujo Gateway plugin integration. From edf493392ce2ff8fe6804d76193e9caedccb99eb Mon Sep 17 00:00:00 2001 From: Aidan Butler Date: Tue, 21 Jul 2026 12:18:04 +0100 Subject: [PATCH 03/11] Reworked fix to avoid test execution hanging Signed-off-by: Aidan Butler --- mcpgateway/plugins/__init__.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/mcpgateway/plugins/__init__.py b/mcpgateway/plugins/__init__.py index d4f3dd9232..0f79c0ecea 100644 --- a/mcpgateway/plugins/__init__.py +++ b/mcpgateway/plugins/__init__.py @@ -23,7 +23,7 @@ import logging import random import time -from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Union +from typing import Any, Callable, Literal, Optional, TYPE_CHECKING, Union # Third-Party from cpex.framework import ObservabilityProvider, TenantPluginManager @@ -102,9 +102,10 @@ class _TeamBindingChangeMsg(BaseModel): def _get_invalidation_hmac_key() -> Optional[bytes]: """Return the HMAC signing key for pub/sub messages, or None if unconfigured.""" - from mcpgateway.config import settings # pylint: disable=import-outside-toplevel from pydantic import SecretStr # pylint: disable=import-outside-toplevel + from mcpgateway.config import settings # pylint: disable=import-outside-toplevel + secret = settings.jwt_secret_key if not secret: return None @@ -491,6 +492,7 @@ async def _plugin_invalidation_listener() -> None: consecutive_failures = 0 while True: + pubsub = None try: client = await _redis() if not client: @@ -498,14 +500,14 @@ async def _plugin_invalidation_listener() -> None: await asyncio.sleep(10) continue - async with client.pubsub() as pubsub: - await pubsub.subscribe(_REDIS_INVALIDATION_CHANNEL) - _logger.info("Plugin invalidation listener subscribed to %s", _REDIS_INVALIDATION_CHANNEL) - backoff = 1.0 - consecutive_failures = 0 + pubsub = client.pubsub() + await pubsub.subscribe(_REDIS_INVALIDATION_CHANNEL) + _logger.info("Plugin invalidation listener subscribed to %s", _REDIS_INVALIDATION_CHANNEL) + backoff = 1.0 + consecutive_failures = 0 - async for message in pubsub.listen(): - await _handle_invalidation_message(message) + async for message in pubsub.listen(): + await _handle_invalidation_message(message) except asyncio.CancelledError: _logger.info("Plugin invalidation listener cancelled") @@ -524,6 +526,14 @@ async def _plugin_invalidation_listener() -> None: ) await asyncio.sleep(delay) backoff = min(max_backoff, backoff * 2) + finally: + # Always release the pubsub connection back to the pool. + # Using wait_for shields aclose() from an in-flight CancelledError: + if pubsub is not None: + try: + await asyncio.wait_for(pubsub.aclose(), timeout=2.0) + except Exception: + pass async def start_plugin_invalidation_listener() -> None: From 82fc122f3a8d3935f10507990b950e2f4fc44f83 Mon Sep 17 00:00:00 2001 From: Aidan Butler Date: Thu, 23 Jul 2026 11:04:22 +0100 Subject: [PATCH 04/11] Added log message and unit tests to cover new code branches Signed-off-by: Aidan Butler --- mcpgateway/plugins/__init__.py | 4 +- .../plugins/test_plugin_runtime_management.py | 92 +++++++++++++++++++ 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/mcpgateway/plugins/__init__.py b/mcpgateway/plugins/__init__.py index 0f79c0ecea..f703a44671 100644 --- a/mcpgateway/plugins/__init__.py +++ b/mcpgateway/plugins/__init__.py @@ -532,8 +532,8 @@ async def _plugin_invalidation_listener() -> None: if pubsub is not None: try: await asyncio.wait_for(pubsub.aclose(), timeout=2.0) - except Exception: - pass + except Exception as exc: + _logger.error("Plugin invalidation listener: pubsub aclose failed (%s)", exc) async def start_plugin_invalidation_listener() -> None: diff --git a/tests/unit/mcpgateway/plugins/test_plugin_runtime_management.py b/tests/unit/mcpgateway/plugins/test_plugin_runtime_management.py index 4a86b52b7e..0120fbe66c 100644 --- a/tests/unit/mcpgateway/plugins/test_plugin_runtime_management.py +++ b/tests/unit/mcpgateway/plugins/test_plugin_runtime_management.py @@ -1705,6 +1705,7 @@ async def _fake_handle(msg): pubsub = MagicMock() pubsub.subscribe = AsyncMock() + pubsub.aclose = AsyncMock() class _Listen: def __aiter__(self): @@ -1724,6 +1725,97 @@ async def __anext__(self): pubsub.subscribe.assert_awaited_once() assert len(seen) == 1 + @pytest.mark.asyncio + async def test_pubsub_aclose_called_on_cancellation(self, monkeypatch): + """``pubsub.aclose()`` must be called when CancelledError propagates out of the listen loop. + + Regression guard: if the ``finally: await pubsub.aclose()`` block is ever + removed, the pubsub connection would be leaked on every cancellation. + """ + import mcpgateway.plugins as framework + + async def _fake_handle(msg): + raise asyncio.CancelledError() + + monkeypatch.setattr(framework, "_handle_invalidation_message", _fake_handle) + + pubsub = MagicMock() + pubsub.subscribe = AsyncMock() + pubsub.aclose = AsyncMock() + + class _Listen: + def __aiter__(self): + return self + + async def __anext__(self): + return {"type": "message", "data": "{}"} + + pubsub.listen = MagicMock(return_value=_Listen()) + + client = MagicMock() + client.pubsub = MagicMock(return_value=pubsub) + monkeypatch.setattr(framework, "_redis", AsyncMock(return_value=client)) + + await framework._plugin_invalidation_listener() + + # aclose() must have been awaited exactly once — proving the connection + # was released via the finally block before the listener returned. + pubsub.aclose.assert_awaited_once() + + @pytest.mark.asyncio + async def test_pubsub_aclose_called_on_reconnect_after_exception(self, monkeypatch): + """``pubsub.aclose()`` must be called when a non-cancellation exception escapes the listen loop. + + A transient Redis error must not leak the pubsub handle — the finally + block must close it before the backoff sleep and reconnection attempt. + """ + import mcpgateway.plugins as framework + + iterations: list[int] = [] + + async def _fake_handle(msg): + iterations.append(1) + raise RuntimeError("transient broker error") + + monkeypatch.setattr(framework, "_handle_invalidation_message", _fake_handle) + + pubsub = MagicMock() + pubsub.subscribe = AsyncMock() + pubsub.aclose = AsyncMock() + + class _Listen: + def __aiter__(self): + return self + + async def __anext__(self): + return {"type": "message", "data": "{}"} + + pubsub.listen = MagicMock(return_value=_Listen()) + + client = MagicMock() + client.pubsub = MagicMock(return_value=pubsub) + monkeypatch.setattr(framework, "_redis", AsyncMock(return_value=client)) + + # Stop the reconnection loop after the first backoff sleep. + async def _fake_sleep(_delay): + raise asyncio.CancelledError() + + monkeypatch.setattr(framework.asyncio, "sleep", _fake_sleep) + + # CancelledError raised inside _fake_sleep propagates out of the + # except-Exception handler, through the finally block, and escapes the + # coroutine — that is the intended termination path when cancellation + # races with the backoff sleep. + try: + await framework._plugin_invalidation_listener() + except asyncio.CancelledError: + pass + + # One dispatch happened, then an exception fired. + assert len(iterations) == 1 + # aclose() must have been awaited once before the backoff sleep. + pubsub.aclose.assert_awaited_once() + # --------------------------------------------------------------------------- # HMAC signing and verification tests From d9803dee3821dd3fd11b5e142f6b69b86242906e Mon Sep 17 00:00:00 2001 From: Aidan Butler Date: Mon, 27 Jul 2026 11:44:40 +0100 Subject: [PATCH 05/11] Updates to address comments Signed-off-by: Aidan Butler --- CHANGELOG.md | 2 + mcpgateway/plugins/__init__.py | 5 +- .../plugins/test_plugin_runtime_management.py | 50 +++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bc20bc326..a30828a5c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ - **Missing Server and Gateway Deletes** - `DELETE /servers/{id}` and `DELETE /gateways/{id}` now return `404 Not Found` when the target no longer exists, instead of incorrectly returning `403 Forbidden`. - **Custom Auth Headers on Tools** ([#5314](https://github.com/IBM/mcp-context-forge/pull/5314), [#5201](https://github.com/IBM/mcp-context-forge/issues/5201)) - `POST /tools` and `PUT /tools/{tool_id}` now persist the `auth_headers` array instead of silently storing `auth_value: null`. Invalid header keys/values are rejected with a 422 rather than an unhandled 500. +- **Redis Connection Leak** ([#5711](https://github.com/IBM/mcp-context-forge/pull/5711)) - The `mcpgateway/plugins/__init__.py` had a connection leak in `_plugin_invalidation_listener`. The pubsub client is unsubscribed but the connection was never closed, eventually leading to connection pool exhaustion. + ### Changed - **Stricter `authheaders` Key Validation (Gateways, Tools, A2A Agents)** ([#5314](https://github.com/IBM/mcp-context-forge/pull/5314)) - Header-key validation is now shared across all create/update schemas and the admin form. Keys with embedded whitespace (e.g. `X Api Key`) were previously accepted and stored as invalid HTTP header names that failed at invocation time; they are now rejected with a 422 at config time, and surrounding whitespace is trimmed before storage. Gateway or A2A configs relying on the old behavior will need their header keys corrected on the next update. diff --git a/mcpgateway/plugins/__init__.py b/mcpgateway/plugins/__init__.py index f703a44671..9198b5c323 100644 --- a/mcpgateway/plugins/__init__.py +++ b/mcpgateway/plugins/__init__.py @@ -528,10 +528,13 @@ async def _plugin_invalidation_listener() -> None: backoff = min(max_backoff, backoff * 2) finally: # Always release the pubsub connection back to the pool. - # Using wait_for shields aclose() from an in-flight CancelledError: + # wait_for bounds aclose() so a stalled redis-py connection cannot + # block the finally clause indefinitely. if pubsub is not None: try: await asyncio.wait_for(pubsub.aclose(), timeout=2.0) + except asyncio.TimeoutError: + _logger.debug("Plugin invalidation listener: pubsub aclose timed out") except Exception as exc: _logger.error("Plugin invalidation listener: pubsub aclose failed (%s)", exc) diff --git a/tests/unit/mcpgateway/plugins/test_plugin_runtime_management.py b/tests/unit/mcpgateway/plugins/test_plugin_runtime_management.py index 0120fbe66c..2c2610b8a9 100644 --- a/tests/unit/mcpgateway/plugins/test_plugin_runtime_management.py +++ b/tests/unit/mcpgateway/plugins/test_plugin_runtime_management.py @@ -1816,6 +1816,56 @@ async def _fake_sleep(_delay): # aclose() must have been awaited once before the backoff sleep. pubsub.aclose.assert_awaited_once() + @pytest.mark.asyncio + async def test_pubsub_aclose_timeout_guard_prevents_hang(self, monkeypatch): + """``wait_for(aclose(), timeout=2.0)`` lets the listener exit even when aclose() hangs. + + Regression guard for the timeout introduced to bound a hanging aclose(): + if the ``timeout=2.0`` parameter were removed (or the wait_for dropped), + the finally block would suspend indefinitely and the listener task would + never return. + + The test replaces aclose() with a coroutine that sleeps for 999 s and + asserts the entire listener finishes well inside that window. + """ + import time + + import mcpgateway.plugins as framework + + async def _fake_handle(msg): + raise asyncio.CancelledError() + + monkeypatch.setattr(framework, "_handle_invalidation_message", _fake_handle) + + async def _hanging_aclose(): + await asyncio.sleep(999) + + pubsub = MagicMock() + pubsub.subscribe = AsyncMock() + pubsub.aclose = _hanging_aclose # not an AsyncMock — returns a real coroutine + + class _Listen: + def __aiter__(self): + return self + + async def __anext__(self): + return {"type": "message", "data": "{}"} + + pubsub.listen = MagicMock(return_value=_Listen()) + + client = MagicMock() + client.pubsub = MagicMock(return_value=pubsub) + monkeypatch.setattr(framework, "_redis", AsyncMock(return_value=client)) + + start = time.monotonic() + await framework._plugin_invalidation_listener() + elapsed = time.monotonic() - start + + # The listener must complete well before aclose()'s 999 s sleep expires. + # A generous upper bound of 5 s covers slow CI environments while still + # being far shorter than the hang duration. + assert elapsed < 5.0, f"Listener took {elapsed:.2f}s — timeout guard may be missing" + # --------------------------------------------------------------------------- # HMAC signing and verification tests From 9282120ac236f070fd066677f8ba6cd9527d9516 Mon Sep 17 00:00:00 2001 From: Aidan Butler Date: Mon, 27 Jul 2026 11:46:23 +0100 Subject: [PATCH 06/11] Updates to address comments Signed-off-by: Aidan Butler --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a30828a5c5..c0b75a75da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ - **Missing Server and Gateway Deletes** - `DELETE /servers/{id}` and `DELETE /gateways/{id}` now return `404 Not Found` when the target no longer exists, instead of incorrectly returning `403 Forbidden`. - **Custom Auth Headers on Tools** ([#5314](https://github.com/IBM/mcp-context-forge/pull/5314), [#5201](https://github.com/IBM/mcp-context-forge/issues/5201)) - `POST /tools` and `PUT /tools/{tool_id}` now persist the `auth_headers` array instead of silently storing `auth_value: null`. Invalid header keys/values are rejected with a 422 rather than an unhandled 500. -- **Redis Connection Leak** ([#5711](https://github.com/IBM/mcp-context-forge/pull/5711)) - The `mcpgateway/plugins/__init__.py` had a connection leak in `_plugin_invalidation_listener`. The pubsub client is unsubscribed but the connection was never closed, eventually leading to connection pool exhaustion. +- **Redis Connection Leak** ([#5711](https://github.com/IBM/mcp-context-forge/pull/5711)) - The `mcpgateway/plugins/__init__.py` had a connection leak in `_plugin_invalidation_listener`. The pubsub client was unsubscribed but the connection was never closed, eventually leading to connection pool exhaustion. ### Changed From ca39a83162c705a003c925b34d0323f239a096b9 Mon Sep 17 00:00:00 2001 From: Aidan Butler Date: Mon, 27 Jul 2026 12:05:58 +0100 Subject: [PATCH 07/11] Updated detect secrets file Signed-off-by: Aidan Butler --- .secrets.baseline | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index 2e354636f8..65cb3e84af 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "(?x)( package-lock\\.json$ |Cargo\\.lock$ |uv\\.lock$ |go\\.sum$ |mcpgateway/sri_hashes\\.json$ )|^.secrets.baseline$", "lines": null }, - "generated_at": "2026-07-20T09:47:57Z", + "generated_at": "2026-07-27T11:04:41Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -266,7 +266,7 @@ "hashed_secret": "b4673e578b9b30fe8bba1b555b7b59883444c697", "is_secret": false, "is_verified": false, - "line_number": 1747, + "line_number": 1749, "type": "Secret Keyword", "verified_result": null }, @@ -274,7 +274,7 @@ "hashed_secret": "4a0a2df96d4c9a13a282268cab33ac4b8cbb2c72", "is_secret": false, "is_verified": false, - "line_number": 1835, + "line_number": 1837, "type": "Secret Keyword", "verified_result": null }, @@ -282,7 +282,7 @@ "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "is_secret": false, "is_verified": false, - "line_number": 2185, + "line_number": 2187, "type": "Basic Auth Credentials", "verified_result": null }, @@ -290,7 +290,7 @@ "hashed_secret": "fa9beb99e4029ad5a6615399e7bbae21356086b3", "is_secret": false, "is_verified": false, - "line_number": 3550, + "line_number": 3552, "type": "Basic Auth Credentials", "verified_result": null }, @@ -298,7 +298,7 @@ "hashed_secret": "fa9beb99e4029ad5a6615399e7bbae21356086b3", "is_secret": false, "is_verified": false, - "line_number": 3641, + "line_number": 3643, "type": "Secret Keyword", "verified_result": null }, @@ -306,7 +306,7 @@ "hashed_secret": "ac371b6dcce28a86c90d12bc57d946a800eebf17", "is_secret": false, "is_verified": false, - "line_number": 3684, + "line_number": 3686, "type": "Secret Keyword", "verified_result": null }, @@ -314,7 +314,7 @@ "hashed_secret": "0b6ec68df700dec4dcd64babd0eda1edccddace1", "is_secret": false, "is_verified": false, - "line_number": 3689, + "line_number": 3691, "type": "Secret Keyword", "verified_result": null }, @@ -322,7 +322,7 @@ "hashed_secret": "4ad6f0082ee224001beb3ca5c3e81c8ceea5ed86", "is_secret": false, "is_verified": false, - "line_number": 3694, + "line_number": 3696, "type": "Secret Keyword", "verified_result": null } From eba6cc9fe0354358785acc8e101e9cfb39e66ff0 Mon Sep 17 00:00:00 2001 From: Aidan Butler Date: Mon, 27 Jul 2026 12:11:26 +0100 Subject: [PATCH 08/11] Update secrets baseline Signed-off-by: Aidan Butler --- .secrets.baseline | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index 793c7a674c..ba2723825b 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "(?x)( package-lock\\.json$ |Cargo\\.lock$ |uv\\.lock$ |go\\.sum$ |mcpgateway/sri_hashes\\.json$ )|^\\.secrets\\.baseline$", "lines": null }, - "generated_at": "2026-07-27T11:04:41Z", + "generated_at": "2026-07-27T11:10:12Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -258,7 +258,7 @@ "hashed_secret": "b4673e578b9b30fe8bba1b555b7b59883444c697", "is_secret": false, "is_verified": false, - "line_number": 1749, + "line_number": 1922, "type": "Secret Keyword", "verified_result": null }, @@ -266,7 +266,7 @@ "hashed_secret": "4a0a2df96d4c9a13a282268cab33ac4b8cbb2c72", "is_secret": false, "is_verified": false, - "line_number": 1837, + "line_number": 2010, "type": "Secret Keyword", "verified_result": null }, @@ -274,7 +274,7 @@ "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "is_secret": false, "is_verified": false, - "line_number": 2187, + "line_number": 2360, "type": "Basic Auth Credentials", "verified_result": null }, @@ -282,7 +282,7 @@ "hashed_secret": "fa9beb99e4029ad5a6615399e7bbae21356086b3", "is_secret": false, "is_verified": false, - "line_number": 3552, + "line_number": 3725, "type": "Basic Auth Credentials", "verified_result": null }, @@ -290,7 +290,7 @@ "hashed_secret": "fa9beb99e4029ad5a6615399e7bbae21356086b3", "is_secret": false, "is_verified": false, - "line_number": 3643, + "line_number": 3816, "type": "Secret Keyword", "verified_result": null }, @@ -298,7 +298,7 @@ "hashed_secret": "ac371b6dcce28a86c90d12bc57d946a800eebf17", "is_secret": false, "is_verified": false, - "line_number": 3686, + "line_number": 3859, "type": "Secret Keyword", "verified_result": null }, @@ -306,7 +306,7 @@ "hashed_secret": "0b6ec68df700dec4dcd64babd0eda1edccddace1", "is_secret": false, "is_verified": false, - "line_number": 3691, + "line_number": 3864, "type": "Secret Keyword", "verified_result": null }, @@ -314,7 +314,7 @@ "hashed_secret": "4ad6f0082ee224001beb3ca5c3e81c8ceea5ed86", "is_secret": false, "is_verified": false, - "line_number": 3696, + "line_number": 3869, "type": "Secret Keyword", "verified_result": null } From 20ed493ffbde66ecb208a13a6cefe4ff46f3bccf Mon Sep 17 00:00:00 2001 From: Aidan Butler Date: Mon, 27 Jul 2026 13:03:07 +0100 Subject: [PATCH 09/11] updated secrets baseline Signed-off-by: Aidan Butler --- .secrets.baseline | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.secrets.baseline b/.secrets.baseline index ba2723825b..6f44b00864 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "(?x)( package-lock\\.json$ |Cargo\\.lock$ |uv\\.lock$ |go\\.sum$ |mcpgateway/sri_hashes\\.json$ )|^\\.secrets\\.baseline$", "lines": null }, - "generated_at": "2026-07-27T11:10:12Z", + "generated_at": "2026-07-27T12:02:05Z", "plugins_used": [ { "name": "AWSKeyDetector" From e3e32e512ab0959bbca94e9d5762198459db6e25 Mon Sep 17 00:00:00 2001 From: Suresh Kumar Moharajan Date: Mon, 27 Jul 2026 15:07:36 +0100 Subject: [PATCH 10/11] chore: remove changelog entry for redis connection leak fix Signed-off-by: Suresh Kumar Moharajan --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d118f14831..5444ba4ed8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,8 +89,6 @@ Release 1.0.6 consolidates **61 PRs** focused on **OAuth RFC 8693 token exchange rotate long-lived tokens; enabling `DERIVE_KEY_PER_ENVIRONMENT` invalidates tokens issued before it was turned on. RS*/ES* deployments must use distinct key pairs per environment. -- **Redis Connection Leak** ([#5711](https://github.com/IBM/mcp-context-forge/pull/5711)) - The `mcpgateway/plugins/__init__.py` had a connection leak in `_plugin_invalidation_listener`. The pubsub client was unsubscribed but the connection was never closed, eventually leading to connection pool exhaustion. - ### Changed #### **API** From 69b828450c296ba75cc97c300221a896d6b519bd Mon Sep 17 00:00:00 2001 From: Suresh Kumar Moharajan Date: Mon, 27 Jul 2026 15:40:26 +0100 Subject: [PATCH 11/11] chore: fix secrets baseline line numbers after changelog edit Signed-off-by: Suresh Kumar Moharajan --- .secrets.baseline | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index 6f44b00864..945633fbe3 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "(?x)( package-lock\\.json$ |Cargo\\.lock$ |uv\\.lock$ |go\\.sum$ |mcpgateway/sri_hashes\\.json$ )|^\\.secrets\\.baseline$", "lines": null }, - "generated_at": "2026-07-27T12:02:05Z", + "generated_at": "2026-07-27T14:40:12Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -258,7 +258,7 @@ "hashed_secret": "b4673e578b9b30fe8bba1b555b7b59883444c697", "is_secret": false, "is_verified": false, - "line_number": 1922, + "line_number": 1920, "type": "Secret Keyword", "verified_result": null }, @@ -266,7 +266,7 @@ "hashed_secret": "4a0a2df96d4c9a13a282268cab33ac4b8cbb2c72", "is_secret": false, "is_verified": false, - "line_number": 2010, + "line_number": 2008, "type": "Secret Keyword", "verified_result": null }, @@ -274,7 +274,7 @@ "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "is_secret": false, "is_verified": false, - "line_number": 2360, + "line_number": 2358, "type": "Basic Auth Credentials", "verified_result": null }, @@ -282,7 +282,7 @@ "hashed_secret": "fa9beb99e4029ad5a6615399e7bbae21356086b3", "is_secret": false, "is_verified": false, - "line_number": 3725, + "line_number": 3723, "type": "Basic Auth Credentials", "verified_result": null }, @@ -290,7 +290,7 @@ "hashed_secret": "fa9beb99e4029ad5a6615399e7bbae21356086b3", "is_secret": false, "is_verified": false, - "line_number": 3816, + "line_number": 3814, "type": "Secret Keyword", "verified_result": null }, @@ -298,7 +298,7 @@ "hashed_secret": "ac371b6dcce28a86c90d12bc57d946a800eebf17", "is_secret": false, "is_verified": false, - "line_number": 3859, + "line_number": 3857, "type": "Secret Keyword", "verified_result": null }, @@ -306,7 +306,7 @@ "hashed_secret": "0b6ec68df700dec4dcd64babd0eda1edccddace1", "is_secret": false, "is_verified": false, - "line_number": 3864, + "line_number": 3862, "type": "Secret Keyword", "verified_result": null }, @@ -314,7 +314,7 @@ "hashed_secret": "4ad6f0082ee224001beb3ca5c3e81c8ceea5ed86", "is_secret": false, "is_verified": false, - "line_number": 3869, + "line_number": 3867, "type": "Secret Keyword", "verified_result": null }