Repository: slackapi/python-slack-sdk
Pull request #1519 (2024-06-26 21:31:09)

fix: redact `bot_access_tokens` from the debug logs of socket mode
## Summary

This PR modified the behavior of `socket mode` in order to redact `bot_access_tokens` out of the `debug` logs

Since the raw payload message is printed, I implemented a `regex` based solution, this feels less then idea for maintainability purposes, let me know if there are other better alternatives to this 

### Category (place an `x` in each of the `[ ]`)

- [ ] **slack_sdk.web.WebClient (sync/async)** (Web API client)
- [ ] **slack_sdk.webhook.WebhookClient (sync/async)** (Incoming Webhook, response_url sender)
- [x] **slack_sdk.socket_mode** (Socket Mode client)
- [ ] **slack_sdk.signature** (Request Signature Verifier)
- [ ] **slack_sdk.oauth** (OAuth Flow Utilities)
- [ ] **slack_sdk.models** (UI component builders)
- [ ] **slack_sdk.scim** (SCIM API client)
- [ ] **slack_sdk.audit_logs** (Audit Logs API client)
- [ ] **slack_sdk.rtm_v2** (RTM client)
- [ ] `/docs-src` (Documents, have you run `./scripts/docs.sh`?)
- [ ] `/docs-src-v2` (Documents, have you run `./scripts/docs-v2.sh`?)
- [ ] `/tutorial` (PythOnBoardingBot tutorial)
- [ ] `tests`/`integration_tests` (Automated tests for this library)

## Requirements (place an `x` in each `[ ]`)

- [x] I've read and understood the [Contributing Guidelines](https://github.com/slackapi/python-slack-sdk/blob/main/.github/contributing.md) and have done my best effort to follow them.
- [x] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct).
- [x] I've run `python3 -m venv .venv && source .venv/bin/activate && ./scripts/run_validation.sh` after making the changes.


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/slackapi/python-slack-sdk/pull/1519?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slackapi) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 84.89%. Comparing base [(`aa3c792`)](https://app.codecov.io/gh/slackapi/python-slack-sdk/commit/aa3c79220b93a5f579794024cd2c5afa6befd208?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slackapi) to head [(`47ad0dd`)](https://app.codecov.io/gh/slackapi/python-slack-sdk/commit/47ad0ddcb7d085e3e651a82d28bfe9aefd05693a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slackapi).

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main    #1519   +/-   ##
=======================================
  Coverage   84.89%   84.89%           
=======================================
  Files         112      113    +1     
  Lines       12473    12480    +7     
=======================================
+ Hits        10589    10595    +6     
- Misses       1884     1885    +1     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/slackapi/python-slack-sdk/pull/1519?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slackapi).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slackapi).


Patch:
diff --git a/slack_sdk/socket_mode/builtin/client.py b/slack_sdk/socket_mode/builtin/client.py
index 79f875d..15e93af 100644
--- a/slack_sdk/socket_mode/builtin/client.py
+++ b/slack_sdk/socket_mode/builtin/client.py
@@ -20,6 +20,7 @@ from slack_sdk.socket_mode.request import SocketModeRequest
 from slack_sdk.web import WebClient
 from .connection import Connection, ConnectionState
 from ..interval_runner import IntervalRunner
+from ..logger.messages import debug_redacted_message_string
 from ...errors import SlackClientConfigurationError, SlackClientNotConnectedError
 from ...proxy_env_variable_loader import load_http_proxy_from_env
 
@@ -231,7 +232,7 @@ class SocketModeClient(BaseSocketModeClient):
 
     def _on_message(self, message: str):
         if self.logger.level <= logging.DEBUG:
-            self.logger.debug(f"on_message invoked: (message: {message})")
+            self.logger.debug(f"on_message invoked: (message: {debug_redacted_message_string(message)})")
         self.enqueue_message(message)
         for listener in self.on_message_listeners:
             listener(message)
diff --git a/slack_sdk/socket_mode/logger/__init__.py b/slack_sdk/socket_mode/logger/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/slack_sdk/socket_mode/logger/messages.py b/slack_sdk/socket_mode/logger/messages.py
new file mode 100644
index 0000000..d10f7ee
--- /dev/null
+++ b/slack_sdk/socket_mode/logger/messages.py
@@ -0,0 +1,6 @@
+import re
+
+
+def debug_redacted_message_string(message: str) -> str:
+    xwfp_token_pattern = re.compile(r"\"xwfp-[A-Za-z0-9\-]+\"")  # ex: "xwfp-abc-ABC-1234"
+    return re.sub(xwfp_token_pattern, "[[REDACTED]]", message)
diff --git a/slack_sdk/socket_mode/websocket_client/__init__.py b/slack_sdk/socket_mode/websocket_client/__init__.py
index faa7fdd..c6d1b9b 100644
--- a/slack_sdk/socket_mode/websocket_client/__init__.py
+++ b/slack_sdk/socket_mode/websocket_client/__init__.py
@@ -24,6 +24,8 @@ from slack_sdk.socket_mode.listeners import (
 from slack_sdk.socket_mode.request import SocketModeRequest
 from slack_sdk.web import WebClient
 
+from ..logger.messages import debug_redacted_message_string
+
 
 class SocketModeClient(BaseSocketModeClient):
     logger: Logger
@@ -147,7 +149,7 @@ class SocketModeClient(BaseSocketModeClient):
 
         def on_message(ws: WebSocketApp, message: str):
             if self.logger.level <= logging.DEBUG:
-                self.logger.debug(f"on_message invoked: (message: {message})")
+                self.logger.debug(f"on_message invoked: (message: {debug_redacted_message_string(message)})")
             self.enqueue_message(message)
             for listener in self.on_message_listeners:
                 listener(ws, message)
diff --git a/slack_sdk/socket_mode/websockets/__init__.py b/slack_sdk/socket_mode/websockets/__init__.py
index 39ed6b3..dd8d9fd 100644
--- a/slack_sdk/socket_mode/websockets/__init__.py
+++ b/slack_sdk/socket_mode/websockets/__init__.py
@@ -26,6 +26,8 @@ from slack_sdk.socket_mode.async_listeners import (
 from slack_sdk.socket_mode.request import SocketModeRequest
 from slack_sdk.web.async_client import AsyncWebClient
 
+from ..logger.messages import debug_redacted_message_string
+
 
 class SocketModeClient(AsyncBaseSocketModeClient):
     logger: Logger
@@ -149,7 +151,9 @@ class SocketModeClient(AsyncBaseSocketModeClient):
                         if isinstance(message, bytes):
                             message = message.decode("utf-8")
                         if self.logger.level <= logging.DEBUG:
-                            self.logger.debug(f"Received message: {message}, session: {session_id}")
+                            self.logger.debug(
+                                f"Received message: {debug_redacted_message_string(message)}, session: {session_id}"
+                            )
                         await self.enqueue_message(message)
                     consecutive_error_count = 0
                 except Exception as e:


Test patch:
diff --git a/tests/slack_sdk/socket_mode/logger/__init__.py b/tests/slack_sdk/socket_mode/logger/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/slack_sdk/socket_mode/logger/test_messages.py b/tests/slack_sdk/socket_mode/logger/test_messages.py
new file mode 100644
index 0000000..e241f9b
--- /dev/null
+++ b/tests/slack_sdk/socket_mode/logger/test_messages.py
@@ -0,0 +1,26 @@
+import unittest
+
+from slack_sdk.socket_mode.logger.messages import debug_redacted_message_string
+
+
+class TestRequest(unittest.TestCase):
+    def setUp(self):
+        pass
+
+    def tearDown(self):
+        pass
+
+    def test_debug_redacted_message_string(self):
+        message = """{"envelope_id":"abc-123","payload":{"token":"xxx","team_id":"T123","api_app_id":"A123","event":{"type":"function_executed","function":{"id":"Fn123","callback_id":"sample_function","title":"Sample function","description":"","type":"app","input_parameters":[],"output_parameters":[],"app_id":"A123","date_created":1719416102,"date_released":0,"date_updated":1719426759,"date_deleted":0,"form_enabled":false},"inputs":{"user_id":"U123"},"function_execution_id":"Fx123","workflow_execution_id":"Wx079QN9CT8E","event_ts":"1719427571.129426","bot_access_token":"xwfp-123-abc"},"type":"event_callback","event_id":"Ev123","event_time":1719427571},"type":"events_api","accepts_response_payload":false,"retry_attempt":0,"retry_reason":""}"""
+        redacted_message = debug_redacted_message_string(message)
+        self.assertEqual(redacted_message.count('"bot_access_token":[[REDACTED]]'), 1)
+
+    def test_debug_redacted_message_string_no_changes(self):
+        message = """{"envelope_id":"abc-123","payload":{"token":"xxx","team_id":"T123","api_app_id":"A123","event":{"type":"function_executed","function":{"id":"Fn123","callback_id":"sample_function","title":"Sample function","description":"","type":"app","input_parameters":[],"output_parameters":[],"app_id":"A123","date_created":1719416102,"date_released":0,"date_updated":1719426759,"date_deleted":0,"form_enabled":false},"inputs":{"user_id":"U123"},"function_execution_id":"Fx123","workflow_execution_id":"Wx079QN9CT8E","event_ts":"1719427571.129426"},"type":"event_callback","event_id":"Ev123","event_time":1719427571},"type":"events_api","accepts_response_payload":false,"retry_attempt":0,"retry_reason":""}"""
+        redacted_message = debug_redacted_message_string(message)
+        self.assertEqual(redacted_message.count('"bot_access_token":[[REDACTED]]'), 0)
+
+    def test_debug_redacted_message_string_simple(self):
+        message = '"bot_access_token": "xwfp-123-abc"'
+        redacted_message = debug_redacted_message_string(message)
+        self.assertEqual(redacted_message.count('"bot_access_token": [[REDACTED]]'), 1)