Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions gateway/platforms/feishu.py
Original file line number Diff line number Diff line change
Expand Up @@ -3289,11 +3289,6 @@ async def _handle_webhook_request(self, request: Any) -> Any:
self._record_webhook_anomaly(remote_ip, "400")
return web.json_response({"code": 400, "msg": "invalid json"}, status=400)

# URL verification challenge — respond before other checks so that Feishu's
# subscription setup works even before encrypt_key is wired.
if payload.get("type") == "url_verification":
return web.json_response({"challenge": payload.get("challenge", "")})

# Verification token check — second layer of defence beyond signature (matches openclaw).
if self._verification_token:
header = payload.get("header") or {}
Expand All @@ -3303,6 +3298,13 @@ async def _handle_webhook_request(self, request: Any) -> Any:
self._record_webhook_anomaly(remote_ip, "401-token")
return web.Response(status=401, text="Invalid verification token")

# URL verification challenge — Feishu includes the verification token in
# challenge requests. Validate the token (above) before reflecting the
# challenge so an unauthenticated remote request cannot prove endpoint
# control by getting attacker-supplied challenge data echoed back.
if payload.get("type") == "url_verification":
return web.json_response({"challenge": payload.get("challenge", "")})

# Timing-safe signature verification (only enforced when encrypt_key is set).
if self._encrypt_key and not self._is_webhook_signature_valid(request.headers, body_bytes):
logger.warning("[Feishu] Webhook rejected: invalid signature from %s", remote_ip)
Expand Down
29 changes: 29 additions & 0 deletions tests/gateway/test_feishu.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ class TestFeishuAdapterMessaging(unittest.TestCase):
"FEISHU_WEBHOOK_HOST": "127.0.0.1",
"FEISHU_WEBHOOK_PORT": "9001",
"FEISHU_WEBHOOK_PATH": "/hook",
"FEISHU_VERIFICATION_TOKEN": "vtok",
}, clear=True)
def test_connect_webhook_mode_starts_local_server(self):
from gateway.config import PlatformConfig
Expand Down Expand Up @@ -1538,6 +1539,34 @@ def test_webhook_request_uses_same_message_dispatch_path(self):
self.assertEqual(response.status, 200)
adapter._on_message_event.assert_called_once()

@patch.dict(os.environ, {"FEISHU_VERIFICATION_TOKEN": "expected-token"}, clear=True)
def test_url_verification_requires_configured_verification_token(self):
"""url_verification must be rejected when token is set but mismatched.

Regression: previously the challenge was reflected before the token
check, so an unauthenticated remote could prove endpoint control by
sending an attacker-controlled challenge string.
"""
from gateway.config import PlatformConfig
from gateway.platforms.feishu import FeishuAdapter

adapter = FeishuAdapter(PlatformConfig())
body = json.dumps({
"type": "url_verification",
"token": "wrong-token",
"challenge": "attacker-controlled-challenge",
}).encode("utf-8")
request = SimpleNamespace(
remote="203.0.113.10",
content_length=None,
headers={},
read=AsyncMock(return_value=body),
)

response = asyncio.run(adapter._handle_webhook_request(request))

self.assertEqual(response.status, 401)

@patch.dict(os.environ, {}, clear=True)
def test_process_inbound_message_uses_event_sender_identity_only(self):
from gateway.config import PlatformConfig
Expand Down
2 changes: 1 addition & 1 deletion website/docs/user-guide/messaging/feishu.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ FEISHU_WEBHOOK_PORT=8765 # default: 8765
FEISHU_WEBHOOK_PATH=/feishu/webhook # default: /feishu/webhook
```

When Feishu sends a URL verification challenge (`type: url_verification`), the webhook responds automatically so you can complete the subscription setup in the Feishu developer console.
When Feishu sends a URL verification challenge (`type: url_verification`), the webhook responds automatically so you can complete the subscription setup in the Feishu developer console. The challenge response is gated on `FEISHU_VERIFICATION_TOKEN` when set — challenge requests with a missing or mismatched token are rejected so an unauthenticated remote cannot prove endpoint control by echoing attacker-controlled challenge data.

## Step 3: Configure Hermes

Expand Down
Loading