fix(telegram): BUTTON_DATA_INVALID — nonce 超過 64 bytes 根因修復
Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled

根因:Telegram callback_data 上限 64 bytes。
5 個長 action 名(docker_restart/host_restart_service 等)+ UUID approval_id
= 71-77 bytes → BUTTON_DATA_INVALID。

修復:
1. security_interceptor.generate_callback_nonce:若 nonce > 63 bytes,
   改用 3-part 格式(捨棄 random)— timestamp 仍保時間唯一性。
2. security_interceptor.parse_callback_data:接受 3-part 或 4-part 格式。
3. telegram_gateway:移除 debug payload logging(診斷完成)。

影響 action:docker_restart / host_restart_service / host_clear_log /
reload_nginx / renew_cert(全部 > 7 chars + UUID = 64 bytes 以上)。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-04-21 21:17:34 +08:00
parent a2777aee04
commit bd735482f7
2 changed files with 9 additions and 9 deletions

View File

@@ -411,10 +411,17 @@ class TelegramSecurityInterceptor:
nonce = f"{action}:{approval_id}:{timestamp}:{random_part}"
# Telegram callback_data limit is 64 bytes.
# Long action names (e.g. docker_restart=14 chars) with UUID approval_id push nonce to 71+ bytes.
# Drop the random suffix when over limit — timestamp still guarantees temporal uniqueness.
if len(nonce.encode()) > 63:
nonce = f"{action}:{approval_id}:{timestamp}"
logger.debug(
"callback_nonce_generated",
approval_id=approval_id,
action=action,
nonce_len=len(nonce.encode()),
)
return nonce
@@ -447,7 +454,8 @@ class TelegramSecurityInterceptor:
"is_info_action": True,
}
if len(parts) != 4:
# 接受 3-part長 action 名截斷 random和 4-part標準兩種格式
if len(parts) not in (3, 4):
raise ValueError(f"Invalid callback_data format: {callback_data}")
return {

View File

@@ -1378,14 +1378,6 @@ class TelegramGateway:
) as span:
try:
response = await self._http_client.post(url, json=payload)
if response.status_code >= 400:
import json as _json
_payload_str = _json.dumps(payload, ensure_ascii=False)
logger.error("telegram_api_rejected", method=method,
status=response.status_code,
response_body=response.text[:300],
payload_len=len(_payload_str),
payload_preview=_payload_str[:1000])
response.raise_for_status()
result = response.json()