From d6fe487ff235f03468e18366bbd83be983d8ab6c Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Thu, 13 Nov 2025 14:14:00 +0100 Subject: [PATCH 01/12] Re-enable attribute-defined-outside-init --- .pylintrc | 1 - 1 file changed, 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 1f888f88..dd0f88aa 100644 --- a/.pylintrc +++ b/.pylintrc @@ -30,5 +30,4 @@ disable = unexpected-keyword-arg, inconsistent-return-statements, duplicate-code, - attribute-defined-outside-init, super-init-not-called, From ff068d9c04d7b1949caa2aa2b1144594ab7fa12d Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Thu, 13 Nov 2025 14:14:25 +0100 Subject: [PATCH 02/12] Create a static new() function on the service config class --- aikido_zen/background_process/service_config.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/aikido_zen/background_process/service_config.py b/aikido_zen/background_process/service_config.py index c4eaa232..b3300327 100644 --- a/aikido_zen/background_process/service_config.py +++ b/aikido_zen/background_process/service_config.py @@ -9,7 +9,6 @@ # noinspection PyAttributeOutsideInit class ServiceConfig: """Class holding the config of the connection_manager""" - def __init__( self, endpoints, @@ -23,6 +22,16 @@ def __init__( endpoints, last_updated_at, blocked_uids, bypassed_ips, received_any_stats ) + @staticmethod + def new(): + return ServiceConfig( + endpoints=[], + blocked_uids=set(), + bypassed_ips=[], + last_updated_at=-1, + received_any_stats=False, + ) + def update( self, endpoints, From 0e9e975b3432765a790afca0dfcb7aabd2b70de1 Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Thu, 13 Nov 2025 14:14:37 +0100 Subject: [PATCH 03/12] thread cache: define all attribtues in init --- aikido_zen/thread/thread_cache.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/aikido_zen/thread/thread_cache.py b/aikido_zen/thread/thread_cache.py index 0072bac3..683f0420 100644 --- a/aikido_zen/thread/thread_cache.py +++ b/aikido_zen/thread/thread_cache.py @@ -17,11 +17,13 @@ class ThreadCache: """ def __init__(self): + self.config = ServiceConfig.new() + self.middleware_installed = False + self.routes = Routes(max_size=1000) self.hostnames = Hostnames(200) self.users = Users(1000) self.stats = Statistics() self.ai_stats = AIStatistics() - self.reset() # Initialize values def is_bypassed_ip(self, ip): """Checks the given IP against the list of bypassed ips""" @@ -36,15 +38,8 @@ def get_endpoints(self): def reset(self): """Empties out all values of the cache""" - self.routes = Routes(max_size=1000) - self.config = ServiceConfig( - endpoints=[], - blocked_uids=set(), - bypassed_ips=[], - last_updated_at=-1, - received_any_stats=False, - ) - self.middleware_installed = False + self.config = ServiceConfig.new() + self.routes.clear() self.hostnames.clear() self.users.clear() self.stats.clear() From bae79cbc8ea594cafabfc014effa9a51908bf3e6 Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Thu, 13 Nov 2025 14:41:32 +0100 Subject: [PATCH 04/12] service_config.py cleanup --- .../background_process/service_config.py | 56 ++++++------------- 1 file changed, 16 insertions(+), 40 deletions(-) diff --git a/aikido_zen/background_process/service_config.py b/aikido_zen/background_process/service_config.py index b3300327..b635d24b 100644 --- a/aikido_zen/background_process/service_config.py +++ b/aikido_zen/background_process/service_config.py @@ -6,49 +6,16 @@ from aikido_zen.helpers.match_endpoints import match_endpoints -# noinspection PyAttributeOutsideInit class ServiceConfig: - """Class holding the config of the connection_manager""" - def __init__( - self, - endpoints, - last_updated_at: int, - blocked_uids, - bypassed_ips, - received_any_stats: bool, - ): - # Init the class using update function : - self.update( - endpoints, last_updated_at, blocked_uids, bypassed_ips, received_any_stats - ) - @staticmethod - def new(): - return ServiceConfig( - endpoints=[], - blocked_uids=set(), - bypassed_ips=[], - last_updated_at=-1, - received_any_stats=False, - ) - - def update( - self, - endpoints, - last_updated_at: int, - blocked_uids, - bypassed_ips, - received_any_stats: bool, - ): - self.last_updated_at = last_updated_at - self.received_any_stats = bool(received_any_stats) - self.blocked_uids = set(blocked_uids) - self.set_endpoints(endpoints) - self.set_bypassed_ips(bypassed_ips) + def __init__(self): + self.endpoints = [] + self.bypassed_ips = IPMatcher() + self.blocked_uids = set() + self.last_updated_at = -1 + self.received_any_stats = False def set_endpoints(self, endpoints): - """Sets non-graphql endpoints""" - self.endpoints = [ endpoint for endpoint in endpoints if not endpoint.get("graphql") ] @@ -75,7 +42,7 @@ def get_endpoints(self, route_metadata): return match_endpoints(route_metadata, self.endpoints) def set_bypassed_ips(self, bypassed_ips): - """Creates an IPMatcher from the given bypassed ip set""" + """Creates a new IPMatcher from the given bypassed ip set""" self.bypassed_ips = IPMatcher() for ip in bypassed_ips: self.bypassed_ips.add(ip) @@ -83,3 +50,12 @@ def set_bypassed_ips(self, bypassed_ips): def is_bypassed_ip(self, ip): """Checks if the IP is on the bypass list""" return self.bypassed_ips.has(ip) + + def set_blocked_user_ids(self, blocked_user_ids): + self.blocked_uids = set(blocked_user_ids) + + def enable_received_any_stats(self): + self.received_any_stats = True + + def set_last_updated_at(self, last_updated_at: int): + self.last_updated_at = last_updated_at From bd9a33ff0eebd9df4bdc6c8a334bc59e344c0d6f Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Thu, 13 Nov 2025 14:42:04 +0100 Subject: [PATCH 05/12] Update thread_cache & it's tests to use new ServiceCOnfig init --- aikido_zen/thread/thread_cache.py | 5 ++-- aikido_zen/thread/thread_cache_test.py | 35 +++++++++++++------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/aikido_zen/thread/thread_cache.py b/aikido_zen/thread/thread_cache.py index 683f0420..5547e59b 100644 --- a/aikido_zen/thread/thread_cache.py +++ b/aikido_zen/thread/thread_cache.py @@ -17,7 +17,7 @@ class ThreadCache: """ def __init__(self): - self.config = ServiceConfig.new() + self.config = ServiceConfig() self.middleware_installed = False self.routes = Routes(max_size=1000) self.hostnames = Hostnames(200) @@ -37,8 +37,7 @@ def get_endpoints(self): return self.config.endpoints def reset(self): - """Empties out all values of the cache""" - self.config = ServiceConfig.new() + self.config = ServiceConfig() self.routes.clear() self.hostnames.clear() self.users.clear() diff --git a/aikido_zen/thread/thread_cache_test.py b/aikido_zen/thread/thread_cache_test.py index 83b48464..325d34d2 100644 --- a/aikido_zen/thread/thread_cache_test.py +++ b/aikido_zen/thread/thread_cache_test.py @@ -150,26 +150,27 @@ def increment_in_thread(): def test_parses_routes_correctly(mock_get_comms, thread_cache: ThreadCache): """Test renewing the cache multiple times if TTL has expired.""" mock_get_comms.return_value = MagicMock() + service_config = ServiceConfig() + service_config.set_endpoints( + [ + { + "graphql": False, + "method": "POST", + "route": "/v2", + "rate_limiting": { + "enabled": False, + }, + "force_protection_off": False, + } + ] + ) + service_config.set_bypassed_ips(["192.168.1.1"]) + service_config.set_blocked_user_ids({"user123"}) + service_config.enable_received_any_stats() mock_get_comms.return_value.send_data_to_bg_process.return_value = { "success": True, "data": { - "config": ServiceConfig( - endpoints=[ - { - "graphql": False, - "method": "POST", - "route": "/v2", - "rate_limiting": { - "enabled": False, - }, - "force_protection_off": False, - } - ], - bypassed_ips=["192.168.1.1"], - blocked_uids={"user123"}, - last_updated_at=-1, - received_any_stats=True, - ), + "config": service_config, "routes": { "POST:/body": { "method": "POST", From 141e4793ff96b1b95463e8689fcb98174e676e51 Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Thu, 13 Nov 2025 14:42:27 +0100 Subject: [PATCH 06/12] Update request_handler test cases --- aikido_zen/sources/functions/request_handler_test.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/aikido_zen/sources/functions/request_handler_test.py b/aikido_zen/sources/functions/request_handler_test.py index db76717f..fac92b65 100644 --- a/aikido_zen/sources/functions/request_handler_test.py +++ b/aikido_zen/sources/functions/request_handler_test.py @@ -146,19 +146,16 @@ def set_context(remote_address, user_agent=""): def create_service_config(): - config = ServiceConfig( - endpoints=[ + config = ServiceConfig() + config.set_endpoints( + [ { "method": "POST", "route": "/posts/:number", "graphql": False, "allowedIPAddresses": ["1.1.1.1", "2.2.2.2", "3.3.3.3"], } - ], - last_updated_at=None, - blocked_uids=set(), - bypassed_ips=[], - received_any_stats=False, + ] ) get_cache().config = config return config From 9913bc8d5a788f53e7191754594c770b83bc4a4f Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Thu, 13 Nov 2025 14:42:49 +0100 Subject: [PATCH 07/12] update rate-limiting test cases with new service config --- aikido_zen/ratelimiting/init_test.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/aikido_zen/ratelimiting/init_test.py b/aikido_zen/ratelimiting/init_test.py index 966d8c7a..017db812 100644 --- a/aikido_zen/ratelimiting/init_test.py +++ b/aikido_zen/ratelimiting/init_test.py @@ -17,13 +17,10 @@ def user(): def create_connection_manager(endpoints=[], bypassed_ips=[]): cm = MagicMock() - cm.conf = ServiceConfig( - endpoints=endpoints, - last_updated_at=1, - blocked_uids=[], - bypassed_ips=bypassed_ips, - received_any_stats=True, - ) + cm.conf = ServiceConfig() + cm.conf.set_endpoints(endpoints) + cm.conf.enable_received_any_stats() + cm.conf.set_bypassed_ips(bypassed_ips) cm.rate_limiter = RateLimiter( max_items=5000, time_to_live_in_ms=120 * 60 * 1000 # 120 minutes ) From 48b2bfdbb08c86972ffd2877a07967a53f76abd9 Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Thu, 13 Nov 2025 14:42:58 +0100 Subject: [PATCH 08/12] Update service config test cases themselves --- .../background_process/service_config_test.py | 123 +++++++----------- 1 file changed, 50 insertions(+), 73 deletions(-) diff --git a/aikido_zen/background_process/service_config_test.py b/aikido_zen/background_process/service_config_test.py index ce7a4421..5437c3a9 100644 --- a/aikido_zen/background_process/service_config_test.py +++ b/aikido_zen/background_process/service_config_test.py @@ -4,6 +4,7 @@ def test_service_config_initialization(): + service_config = ServiceConfig() endpoints = [ { "graphql": False, @@ -51,26 +52,33 @@ def test_service_config_initialization(): "force_protection_off": False, }, ] - last_updated_at = "2023-10-01" - service_config = ServiceConfig( - endpoints, - last_updated_at, - ["0", "0", "1", "5"], - ["127.0.0.1", "123.1.2.0/24", "132.1.0.0/16"], - True, - ) - # Check that non-GraphQL endpoints are correctly filtered - assert len(service_config.endpoints) == 3 + assert len(service_config.endpoints) == 0 + service_config.set_endpoints(endpoints) + assert ( + len(service_config.endpoints) == 3 + ) # Check that non-GraphQL endpoints are correctly filtered assert service_config.endpoints[0]["route"] == "/v1" assert service_config.endpoints[1]["route"] == "/v3" assert service_config.endpoints[2]["route"] == "/admin" - assert service_config.last_updated_at == last_updated_at + + service_config.set_last_updated_at(37982562953) + assert service_config.last_updated_at == 37982562953 + + assert isinstance(service_config.bypassed_ips, IPMatcher) + service_config.set_bypassed_ips(["127.0.0.1", "123.1.2.0/24", "132.1.0.0/16"]) assert isinstance(service_config.bypassed_ips, IPMatcher) assert service_config.bypassed_ips.has("127.0.0.1") assert service_config.bypassed_ips.has("123.1.2.2") assert not service_config.bypassed_ips.has("1.1.1.1") - assert service_config.blocked_uids == set(["1", "0", "5"]) + + assert len(service_config.blocked_uids) == 0 + service_config.set_blocked_user_ids({"0", "0", "1", "5"}) + assert service_config.blocked_uids == {"1", "0", "5"} + + assert not service_config.received_any_stats + service_config.enable_received_any_stats() + assert service_config.received_any_stats == True v1_endpoint = service_config.get_endpoints( { @@ -96,41 +104,9 @@ def test_service_config_initialization(): assert not admin_endpoint["allowedIPAddresses"].has("192.168.0.1") -# Sample data for testing -sample_endpoints = [ - {"url": "http://example.com/api/v1", "graphql": False, "context": "user"}, - {"url": "http://example.com/api/v2", "graphql": True, "context": "admin"}, - {"url": "http://example.com/api/v3", "graphql": False, "context": "guest"}, -] - - -@pytest.fixture -def service_config(): - return ServiceConfig( - endpoints=sample_endpoints, - last_updated_at="2023-10-01T00:00:00Z", - blocked_uids=["user1", "user2"], - bypassed_ips=["192.168.1.1", "10.0.0.1"], - received_any_stats=True, - ) - - -def test_initialization(service_config): - assert len(service_config.endpoints) == 2 # Only non-graphql endpoints - assert service_config.last_updated_at == "2023-10-01T00:00:00Z" - assert isinstance(service_config.bypassed_ips, IPMatcher) - assert service_config.blocked_uids == {"user1", "user2"} - - def test_ip_blocking(): - config = ServiceConfig( - endpoints=sample_endpoints, - last_updated_at="2023-10-01T00:00:00Z", - blocked_uids=["user1", "user2"], - bypassed_ips=["192.168.1.1", "10.0.0.0/16", "::1/128"], - received_any_stats=True, - ) - + config = ServiceConfig() + config.set_bypassed_ips(["192.168.1.1", "10.0.0.0/16", "::1/128"]) assert config.is_bypassed_ip("192.168.1.1") assert config.is_bypassed_ip("10.0.0.1") assert config.is_bypassed_ip("10.0.1.2") @@ -142,38 +118,39 @@ def test_ip_blocking(): def test_service_config_with_empty_allowlist(): - endpoints = [ - { - "graphql": False, - "method": "GET", - "route": "/admin", - "rate_limiting": { - "enabled": False, - "max_requests": 10, - "window_size_in_ms": 1000, - }, - "allowedIPAddresses": [], - "force_protection_off": False, - }, - ] - last_updated_at = "2023-10-01" - service_config = ServiceConfig( - endpoints, - last_updated_at, - ["0", "0", "1", "5"], - ["127.0.0.1", "123.1.2.0/24", "132.1.0.0/16"], - True, - ) + service_config = ServiceConfig() # Check that non-GraphQL endpoints are correctly filtered + service_config.set_endpoints( + [ + { + "graphql": False, + "method": "GET", + "route": "/admin", + "rate_limiting": { + "enabled": False, + "max_requests": 10, + "window_size_in_ms": 1000, + }, + "allowedIPAddresses": [], + "force_protection_off": False, + }, + ] + ) assert len(service_config.endpoints) == 1 assert service_config.endpoints[0]["route"] == "/admin" - assert service_config.last_updated_at == last_updated_at + + service_config.set_last_updated_at(29839537) + assert service_config.last_updated_at == 29839537 + + service_config.set_blocked_user_ids({"0", "0", "1", "5"}) + assert service_config.blocked_uids == {"1", "0", "5"} + + service_config.set_bypassed_ips(["127.0.0.1", "123.1.2.0/24", "132.1.0.0/16"]) assert isinstance(service_config.bypassed_ips, IPMatcher) - assert service_config.bypassed_ips.has("127.0.0.1") - assert service_config.bypassed_ips.has("123.1.2.2") - assert not service_config.bypassed_ips.has("1.1.1.1") - assert service_config.blocked_uids == set(["1", "0", "5"]) + assert service_config.is_bypassed_ip("127.0.0.1") + assert service_config.is_bypassed_ip("123.1.2.2") + assert not service_config.is_bypassed_ip("1.1.1.1") admin_endpoint = service_config.get_endpoints( { From 0c2e7fb902e5d318a763f3bd52690cb378cb718d Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Thu, 13 Nov 2025 14:46:40 +0100 Subject: [PATCH 09/12] cloud connection manager, fix service config update --- .../cloud_connection_manager/__init__.py | 8 +------- .../update_service_config.py | 14 ++++++-------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/aikido_zen/background_process/cloud_connection_manager/__init__.py b/aikido_zen/background_process/cloud_connection_manager/__init__.py index 579fa0ef..e4fd38f5 100644 --- a/aikido_zen/background_process/cloud_connection_manager/__init__.py +++ b/aikido_zen/background_process/cloud_connection_manager/__init__.py @@ -35,13 +35,7 @@ def __init__(self, block, api, token, serverless): self.token = token # Should be instance of the Token class! self.routes = Routes(200) self.hostnames = Hostnames(200) - self.conf = ServiceConfig( - endpoints=[], - last_updated_at=-1, # Has not been updated yet - blocked_uids=[], - bypassed_ips=[], - received_any_stats=True, - ) + self.conf = ServiceConfig() self.firewall_lists = FirewallLists() self.rate_limiter = RateLimiter( max_items=5000, time_to_live_in_ms=120 * 60 * 1000 # 120 minutes diff --git a/aikido_zen/background_process/cloud_connection_manager/update_service_config.py b/aikido_zen/background_process/cloud_connection_manager/update_service_config.py index 7d7f729e..192a8d12 100644 --- a/aikido_zen/background_process/cloud_connection_manager/update_service_config.py +++ b/aikido_zen/background_process/cloud_connection_manager/update_service_config.py @@ -1,5 +1,4 @@ """Mainly exports update_service_config function""" - from aikido_zen.helpers.logging import logger from aikido_zen.helpers.get_current_unixtime_ms import get_unixtime_ms @@ -15,10 +14,9 @@ def update_service_config(connection_manager, res): logger.debug("Updating blocking, setting blocking to : %s", res["block"]) connection_manager.block = bool(res["block"]) - connection_manager.conf.update( - endpoints=res.get("endpoints", []), - last_updated_at=res.get("configUpdatedAt", get_unixtime_ms()), - blocked_uids=res.get("blockedUserIds", []), - bypassed_ips=res.get("allowedIPAddresses", []), - received_any_stats=res.get("receivedAnyStats", True), - ) + connection_manager.conf.set_endpoints(res.get("endpoints", [])) + connection_manager.conf.set_last_updated_at(res.get("configUpdatedAt", get_unixtime_ms())) + connection_manager.conf.set_blocked_user_ids(res.get("blockedUserIds", [])) + connection_manager.conf.set_bypassed_ips(res.get("allowedIPAddresses", [])) + if res.get("receivedAnyStats", True): + connection_manager.conf.enable_received_stats() From 7020ed27828f87eeccba5f3abc7dfb14928c8a01 Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Thu, 13 Nov 2025 14:46:53 +0100 Subject: [PATCH 10/12] lint --- .../cloud_connection_manager/update_service_config.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/aikido_zen/background_process/cloud_connection_manager/update_service_config.py b/aikido_zen/background_process/cloud_connection_manager/update_service_config.py index 192a8d12..fa0d855a 100644 --- a/aikido_zen/background_process/cloud_connection_manager/update_service_config.py +++ b/aikido_zen/background_process/cloud_connection_manager/update_service_config.py @@ -1,4 +1,5 @@ """Mainly exports update_service_config function""" + from aikido_zen.helpers.logging import logger from aikido_zen.helpers.get_current_unixtime_ms import get_unixtime_ms @@ -15,7 +16,9 @@ def update_service_config(connection_manager, res): connection_manager.block = bool(res["block"]) connection_manager.conf.set_endpoints(res.get("endpoints", [])) - connection_manager.conf.set_last_updated_at(res.get("configUpdatedAt", get_unixtime_ms())) + connection_manager.conf.set_last_updated_at( + res.get("configUpdatedAt", get_unixtime_ms()) + ) connection_manager.conf.set_blocked_user_ids(res.get("blockedUserIds", [])) connection_manager.conf.set_bypassed_ips(res.get("allowedIPAddresses", [])) if res.get("receivedAnyStats", True): From 9b94bb6634ee0aa3e4711420711ef2e3a7f512be Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Thu, 13 Nov 2025 14:53:05 +0100 Subject: [PATCH 11/12] Fix the set_body_internal mess --- aikido_zen/context/__init__.py | 40 ++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/aikido_zen/context/__init__.py b/aikido_zen/context/__init__.py index bb8eeb3b..3bbe6273 100644 --- a/aikido_zen/context/__init__.py +++ b/aikido_zen/context/__init__.py @@ -50,7 +50,7 @@ def __init__(self, context_obj=None, body=None, req=None, source=None): self.parsed_userinput = {} self.xml = {} self.outgoing_req_redirects = [] - self.set_body(body) + self.body = Context.parse_body_object(body) self.headers: Headers = Headers() self.cookies = {} self.query = {} @@ -107,26 +107,28 @@ def set_cookies(self, cookies): self.cookies = cookies def set_body(self, body): - try: - self.set_body_internal(body) - except Exception as e: - logger.debug("Exception occurred whilst setting body: %s", e) + self.body = Context.parse_body_object(body) - def set_body_internal(self, body): + @staticmethod + def parse_body_object(body): """Sets the body and checks if it's possibly JSON""" - self.body = body - if isinstance(self.body, (str, bytes)) and len(body) == 0: - # Make sure that empty bodies like b"" don't get sent. - self.body = None - if isinstance(self.body, bytes): - self.body = self.body.decode("utf-8") # Decode byte input to string. - if not isinstance(self.body, str): - return - if self.body.strip()[0] in ["{", "[", '"']: - # Might be JSON, but might not have been parsed correctly by server because of wrong headers - parsed_body = json.loads(self.body) - if parsed_body: - self.body = parsed_body + try: + if isinstance(body, (str, bytes)) and len(body) == 0: + # Make sure that empty bodies like b"" don't get sent. + return None + if isinstance(body, bytes): + body = body.decode("utf-8") # Decode byte input to string. + if not isinstance(body, str): + return body + if body.strip()[0] in ["{", "[", '"']: + # Might be JSON, but might not have been parsed correctly by server because of wrong headers + parsed_body = json.loads(body) + if parsed_body: + return parsed_body + return body + except Exception as e: + logger.debug("Exception occurred whilst parsing body: %s", e) + return body def get_route_metadata(self): """Returns a route_metadata object""" From 95a11eeab3447a5297d979836b742073372afdbd Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Thu, 13 Nov 2025 15:24:27 +0100 Subject: [PATCH 12/12] thread cache reset - also reset middleware_installed --- aikido_zen/thread/thread_cache.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aikido_zen/thread/thread_cache.py b/aikido_zen/thread/thread_cache.py index 5547e59b..10f3f12e 100644 --- a/aikido_zen/thread/thread_cache.py +++ b/aikido_zen/thread/thread_cache.py @@ -37,6 +37,7 @@ def get_endpoints(self): return self.config.endpoints def reset(self): + self.middleware_installed = False self.config = ServiceConfig() self.routes.clear() self.hostnames.clear()