From f0c4d13ddc44c4ade7dfa696a4913fbe76f2afea Mon Sep 17 00:00:00 2001 From: Sahil Jagtap Date: Thu, 8 Oct 2026 17:42:04 -0400 Subject: [PATCH] fix: SslCheck rejected requests with a matching verification token When an App was built with verification_token, the ssl_check middleware returned 401 for requests whose token matched and 200 for requests whose token did not. A request without a token field raised KeyError. The helper returned True on a match while the caller treated True as an error. - Return the error response only when the token is missing or wrong - Use body.get() so a missing token is rejected instead of crashing - Add sync and async tests covering matching, wrong, and missing tokens --- .../middleware/ssl_check/async_ssl_check.py | 2 +- slack_bolt/middleware/ssl_check/ssl_check.py | 10 ++++--- tests/scenario_tests/test_ssl_check.py | 26 ++++++++++++++++++ tests/scenario_tests_async/test_ssl_check.py | 27 +++++++++++++++++++ 4 files changed, 61 insertions(+), 4 deletions(-) diff --git a/slack_bolt/middleware/ssl_check/async_ssl_check.py b/slack_bolt/middleware/ssl_check/async_ssl_check.py index 5b806a4bc..62712aa67 100644 --- a/slack_bolt/middleware/ssl_check/async_ssl_check.py +++ b/slack_bolt/middleware/ssl_check/async_ssl_check.py @@ -18,7 +18,7 @@ async def async_process( next: Callable[[], Awaitable[BoltResponse]], ) -> BoltResponse: if self._is_ssl_check_request(req.body): - if self._verify_token_if_needed(req.body): + if not self._verify_token_if_needed(req.body): return self._build_error_response() return self._build_success_response() else: diff --git a/slack_bolt/middleware/ssl_check/ssl_check.py b/slack_bolt/middleware/ssl_check/ssl_check.py index 5e5d9ad9c..d7f6a759d 100644 --- a/slack_bolt/middleware/ssl_check/ssl_check.py +++ b/slack_bolt/middleware/ssl_check/ssl_check.py @@ -39,7 +39,7 @@ def process( next: Callable[[], BoltResponse], ) -> BoltResponse: if self._is_ssl_check_request(req.body): - if self._verify_token_if_needed(req.body): + if not self._verify_token_if_needed(req.body): return self._build_error_response() return self._build_success_response() else: @@ -51,8 +51,12 @@ def process( def _is_ssl_check_request(body: dict): return "ssl_check" in body and body["ssl_check"] == "1" - def _verify_token_if_needed(self, body: dict): - return self.verification_token and self.verification_token == body["token"] + def _verify_token_if_needed(self, body: dict) -> bool: + # Returns True when the request is acceptable: either no token check is configured, + # or the token in the body matches the configured one + if self.verification_token is None: + return True + return self.verification_token == body.get("token") @staticmethod def _build_success_response() -> BoltResponse: diff --git a/tests/scenario_tests/test_ssl_check.py b/tests/scenario_tests/test_ssl_check.py index 937f9d7e4..6cd146598 100644 --- a/tests/scenario_tests/test_ssl_check.py +++ b/tests/scenario_tests/test_ssl_check.py @@ -56,6 +56,32 @@ def test_ssl_check(self): assert response.status == 200 assert response.body == "" + def test_ssl_check_with_verification_token(self): + app = App(client=self.web_client, signing_secret=self.signing_secret, verification_token="expected") + + def dispatch(body: str): + timestamp = str(int(time())) + request = BoltRequest( + body=body, + query={}, + headers={ + "content-type": ["application/x-www-form-urlencoded"], + "x-slack-signature": [self.generate_signature(body, timestamp)], + "x-slack-request-timestamp": [timestamp], + }, + ) + return app.dispatch(request) + + response = dispatch("token=expected&ssl_check=1") + assert response.status == 200 + assert response.body == "" + + response = dispatch("token=wrong&ssl_check=1") + assert response.status == 401 + + response = dispatch("ssl_check=1") + assert response.status == 401 + def test_ssl_check_disabled(self): app = App( client=self.web_client, diff --git a/tests/scenario_tests_async/test_ssl_check.py b/tests/scenario_tests_async/test_ssl_check.py index ef32bc0dd..f1967163b 100644 --- a/tests/scenario_tests_async/test_ssl_check.py +++ b/tests/scenario_tests_async/test_ssl_check.py @@ -61,3 +61,30 @@ async def test_ssl_check(self): response = await app.async_dispatch(request) assert response.status == 200 assert response.body == "" + + @pytest.mark.asyncio + async def test_ssl_check_with_verification_token(self): + app = AsyncApp(client=self.web_client, signing_secret=self.signing_secret, verification_token="expected") + + async def dispatch(body: str): + timestamp = str(int(time())) + request = AsyncBoltRequest( + body=body, + query={}, + headers={ + "content-type": ["application/x-www-form-urlencoded"], + "x-slack-signature": [self.generate_signature(body, timestamp)], + "x-slack-request-timestamp": [timestamp], + }, + ) + return await app.async_dispatch(request) + + response = await dispatch("token=expected&ssl_check=1") + assert response.status == 200 + assert response.body == "" + + response = await dispatch("token=wrong&ssl_check=1") + assert response.status == 401 + + response = await dispatch("ssl_check=1") + assert response.status == 401