build(security): add migration and CI security gates

This commit is contained in:
2026-07-28 16:41:36 +08:00
parent 6b43b77c3b
commit 4bfe2900fb
15 changed files with 186 additions and 97 deletions
+10 -5
View File
@@ -1,9 +1,11 @@
import hashlib
import hmac
import struct
import base64
import uuid
import xml.etree.ElementTree as ET
from typing import Optional
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from defusedxml import ElementTree as ET
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from fastapi.responses import PlainTextResponse
from pydantic import BaseModel
@@ -38,17 +40,20 @@ class BindConfirmRequest(BaseModel):
def _verify_signature(token: str, timestamp: str, nonce: str, encrypted: str, signature: str) -> bool:
items = sorted([token, timestamp, nonce, encrypted])
raw = "".join(items)
return hashlib.sha1(raw.encode()).hexdigest() == signature
# The WeCom callback protocol mandates SHA-1; compare_digest avoids timing leaks.
expected = hashlib.sha1(raw.encode(), usedforsecurity=False).hexdigest()
return hmac.compare_digest(expected, signature)
def _decrypt_msg(encrypted: str) -> str:
"""Decrypt WeChat Work callback message. Returns plaintext XML."""
key = base64.b64decode(settings.WECOM_ENCODING_AES_KEY + "=")
ciphertext = base64.b64decode(encrypted)
from Crypto.Cipher import AES
cipher = AES.new(key, AES.MODE_CBC, iv=key[:16])
plaintext = cipher.decrypt(ciphertext)
decryptor = Cipher(algorithms.AES(key), modes.CBC(key[:16])).decryptor()
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
pad_len = plaintext[-1]
if pad_len < 1 or pad_len > 32 or plaintext[-pad_len:] != bytes([pad_len]) * pad_len:
raise ValueError("Invalid callback padding")
plaintext = plaintext[:-pad_len]
msg_len = struct.unpack(">I", plaintext[16:20])[0]
return plaintext[20:20 + msg_len].decode("utf-8")