Security decisions, implementation details, and known limitations for chat-sdk-python.
Every adapter verifies incoming webhook requests before processing. The verification method depends on the platform.
- Method: HMAC-SHA256 signature verification
- Header:
X-Slack-Signature(v0 format:v0=<hex-digest>) - Timestamp:
X-Slack-Request-Timestamp(rejected if > 5 minutes old to prevent replay attacks) - Signing key:
signing_secretfrom the Slack app configuration - Base string:
v0:{timestamp}:{body} - Comparison: Timing-safe via
hmac.compare_digest()
# Simplified verification flow in Slack adapter
base_string = f"v0:{timestamp}:{body}"
expected = "v0=" + hmac.new(
signing_secret.encode(), base_string.encode(), hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, signature_header):
raise AuthenticationError("slack", "Invalid signature")- Method: Ed25519 signature verification (using PyNaCl)
- Headers:
X-Signature-Ed25519,X-Signature-Timestamp - Public key:
public_keyfrom the Discord application - Verified message:
{timestamp}{body} - Library:
nacl.signing.VerifyKey
- Method: JWT token validation (RS256)
- Header:
Authorization: Bearer <jwt> - Issuer:
https://api.botframework.comorhttps://sts.windows.net/{tenant-id}/ - Audience: The app's
app_id - JWKS endpoint:
https://login.botframework.com/v1/.well-known/openidconfiguration - Key rotation: JWKS keys are cached with a configurable TTL. See "Known Limitations" below.
The Teams adapter implements its own JWT validation rather than using the Microsoft Bot Framework SDK because there is no maintained async Python equivalent. The validation includes issuer checking, audience verification, expiry checking, and signature verification against the JWKS-provided RSA public keys.
- Method: Secret token comparison
- Header:
X-Telegram-Bot-Api-Secret-Token - Comparison: Timing-safe via
hmac.compare_digest() - Caveat: If
secret_tokenis not configured, webhook verification is silently skipped. See "Known Limitations" below.
- Method: HMAC-SHA256 signature verification
- Header:
X-Hub-Signature-256(format:sha256=<hex-digest>) - Signing key:
app_secretfrom the Meta app - Comparison: Timing-safe via
hmac.compare_digest()
- Method: Google-issued JWT token verification
- Header:
Authorization: Bearer <jwt> - Verification:
google.oauth2.id_token.verify_token()from thegoogle-authlibrary - Audience: The Google Cloud project number
- Fallback: If
use_application_default_credentialsis enabled, the adapter trusts Google Cloud's internal authentication.
- Method: HMAC-SHA256 signature verification
- Header:
X-Hub-Signature-256(format:sha256=<hex-digest>) - Signing key:
webhook_secretfrom the GitHub app - Comparison: Timing-safe via
hmac.compare_digest()
- Method: HMAC-SHA256 signature verification
- Header:
Linear-Signature - Signing key:
webhook_secretfrom the Linear app - Comparison: Timing-safe via
hmac.compare_digest()
The Teams adapter receives a serviceUrl in every activity payload. This URL is used to send replies back to Teams. The adapter validates that the URL matches the expected Microsoft domains:
https://*.botframework.com/https://smba.trafficmanager.net/
Requests to arbitrary URLs (which could target internal services) are rejected.
When downloading media attachments from WhatsApp messages, the adapter validates that media URLs point to Meta's CDN domains before fetching them:
https://*.whatsapp.net/https://scontent*.xx.fbcdn.net/
Slack's response_url (used for responding to slash commands and interactive messages) is validated to ensure it points to Slack's domains:
https://hooks.slack.com/
The Slack adapter supports multi-workspace OAuth installations. Bot tokens for each workspace can be encrypted at rest using AES-256-GCM.
def encrypt_token(plaintext: str, key: bytes) -> EncryptedTokenData:
iv = os.urandom(12) # 96-bit random IV
aesgcm = AESGCM(key)
ct_with_tag = aesgcm.encrypt(iv, plaintext.encode(), None)
ciphertext = ct_with_tag[:-16] # everything except last 16 bytes
tag = ct_with_tag[-16:] # 128-bit auth tag
return EncryptedTokenData(
iv=base64.b64encode(iv),
data=base64.b64encode(ciphertext),
tag=base64.b64encode(tag),
)- Algorithm: AES-256-GCM (authenticated encryption)
- IV: 12 bytes from
os.urandom()(CSPRNG) - Auth tag: 16 bytes (128-bit)
- Key format: 32-byte key, accepted as 64-char hex or 44-char base64
- Library:
cryptography(via thecryptoextra) - Lazy import:
cryptographyis imported inside the encrypt/decrypt functions, not at module level
The encryption key is provided via the encryption_key config option on the Slack adapter. It is the deployer's responsibility to:
- Generate a strong 256-bit key (
python -c "import secrets; print(secrets.token_hex(32))") - Store it securely (environment variable, secrets manager)
- Rotate it by re-encrypting stored tokens with the new key
All webhook signature verifications use hmac.compare_digest() for constant-time comparison. This prevents timing side-channel attacks where an attacker could determine the correct signature byte-by-byte by measuring response times.
This applies to:
- Slack signing secret verification
- GitHub webhook signature verification
- WhatsApp webhook signature verification
- Linear webhook signature verification
- Telegram secret token verification
- Lock token comparison in state adapters
Lock tokens serve as proof of ownership. A holder must present the correct token to release or extend a lock.
- Generator:
secrets.token_hex(16)-- 128 bits of cryptographic randomness - Format:
{backend}_{timestamp_ms}_{hex}(e.g.,mem_1700000000000_a1b2c3d4...) - Why CSPRNG: If tokens were predictable, a malicious actor (or a bug in a concurrent process) could forge a token and release someone else's lock, causing data races.
If the TelegramAdapterConfig does not include a secret_token, the Telegram adapter processes webhooks without any authentication. This is because Telegram does not require webhook verification -- it is optional.
Risk: Anyone who discovers the webhook URL can send fake updates.
Mitigation: Always configure secret_token in production. The adapter should log a warning when operating without verification, but this is not currently implemented.
The Teams adapter caches JWKS (JSON Web Key Set) public keys to avoid fetching them on every request. When Microsoft rotates its signing keys, there is a window where:
- The old key is still in the cache
- Microsoft starts signing with the new key
- Verification fails until the cache expires and new keys are fetched
Risk: Legitimate webhooks may be rejected during key rotation (~minutes).
Mitigation: The cache TTL is set to 24 hours by default. If verification fails with a cached key, the adapter should (but does not currently) attempt a cache refresh before rejecting the request. This is a known improvement area.
Card element content (titles, text, button labels) is passed through to platform APIs without HTML escaping. Each platform's API is responsible for sanitizing output. This is intentional -- double-escaping would produce visible escape sequences.
Risk: If a platform API has an XSS vulnerability, malicious card content could exploit it.
Mitigation: Trust platform APIs to handle their own output encoding. Do not pass user-controlled input directly into card content without application-level sanitization.
-
Webhook secrets are configured for every adapter in use. Never deploy with empty or default secrets.
-
Encryption key is set for Slack multi-workspace OAuth if bot tokens are persisted (Redis/Postgres state backends).
-
Telegram
secret_tokenis set. Without it, anyone can submit fake updates to your webhook endpoint. -
State backend is production-grade.
MemoryStateAdapteremits a warning in production environments but does not prevent usage. Use Redis or PostgreSQL. -
HTTPS is enforced on all webhook endpoints. Webhook signatures are useless if the request body can be observed and replayed over HTTP.
-
Rate limiting is configured at the HTTP layer (reverse proxy / load balancer). The SDK raises
RateLimitError/AdapterRateLimitErrorwhen platform rate limits are hit, but does not implement client-side rate limiting. -
Dependency audit: Run
pip auditor equivalent to check for known vulnerabilities in dependencies (cryptography,slack-sdk,pynacl,aiohttp, etc.). -
Log level: Set to
infoorwarnin production.debuglevel logs message content and adapter payloads, which may contain sensitive data.