From 5744b17579492f48d1436418509da084bd4e7fd7 Mon Sep 17 00:00:00 2001 From: TheOnlyMika Date: Tue, 26 May 2026 00:23:19 +0800 Subject: [PATCH] harden: restrict markdown link schemes; parse untrusted XML with defusedxml Two small defensive-hardening changes: - web/src/components/Markdown.tsx: render links only for http(s)/mailto schemes; other schemes (javascript:, data:, vbscript:) are dropped to plain text so a crafted link in rendered content can't execute on click. - gateway/platforms/wecom_callback.py: parse the untrusted, pre-auth WeCom callback request body with defusedxml instead of xml.etree, blocking entity-expansion / billion-laughs (and XXE) on the parse path. defusedxml is already a dependency (uv.lock); response-building XML in wecom_crypto.py is unchanged (it is not parsed from untrusted input). Verified: dashboard typechecks and builds; defusedxml blocks an entity-expansion payload while valid WeCom envelopes still parse. --- gateway/platforms/wecom_callback.py | 6 +++++- web/src/components/Markdown.tsx | 18 ++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/gateway/platforms/wecom_callback.py b/gateway/platforms/wecom_callback.py index e08bc039742..b656d2ecf96 100644 --- a/gateway/platforms/wecom_callback.py +++ b/gateway/platforms/wecom_callback.py @@ -17,7 +17,11 @@ import logging import socket as _socket import time from typing import Any, Dict, List, Optional -from xml.etree import ElementTree as ET +# Security: parse untrusted, pre-auth request bodies (WeCom callbacks) with +# defusedxml to block billion-laughs / entity-expansion (and XXE) DoS. The +# parsing API (fromstring) is a drop-in for the stdlib calls used below; +# response-building XML lives in wecom_crypto.py and is not parsed here. +import defusedxml.ElementTree as ET try: from aiohttp import web diff --git a/web/src/components/Markdown.tsx b/web/src/components/Markdown.tsx index bef0804e7c4..a78c4430c34 100644 --- a/web/src/components/Markdown.tsx +++ b/web/src/components/Markdown.tsx @@ -324,11 +324,24 @@ function InlineContent({ ); - case "link": + case "link": { + // Security: only render http(s)/mailto links. Other schemes + // (javascript:, data:, vbscript:) are dropped to plain text so a + // crafted link in agent/message content can't execute on click. + const href = node.href.trim(); + if (!/^(https?:|mailto:)/i.test(href)) { + return ( + + ); + } return ( ); + } case "br": return
; }