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.
This commit is contained in:
parent
f4953bc648
commit
5744b17579
@ -17,7 +17,11 @@ import logging
|
|||||||
import socket as _socket
|
import socket as _socket
|
||||||
import time
|
import time
|
||||||
from typing import Any, Dict, List, Optional
|
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:
|
try:
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|||||||
@ -324,11 +324,24 @@ function InlineContent({
|
|||||||
<HighlightedText text={node.content} terms={highlightTerms} />
|
<HighlightedText text={node.content} terms={highlightTerms} />
|
||||||
</em>
|
</em>
|
||||||
);
|
);
|
||||||
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 (
|
||||||
|
<HighlightedText
|
||||||
|
key={i}
|
||||||
|
text={node.text}
|
||||||
|
terms={highlightTerms}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
key={i}
|
key={i}
|
||||||
href={node.href}
|
href={href}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="text-primary underline underline-offset-2 decoration-primary/30 hover:decoration-primary/60 transition-colors"
|
className="text-primary underline underline-offset-2 decoration-primary/30 hover:decoration-primary/60 transition-colors"
|
||||||
@ -336,6 +349,7 @@ function InlineContent({
|
|||||||
{node.text}
|
{node.text}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
case "br":
|
case "br":
|
||||||
return <br key={i} />;
|
return <br key={i} />;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user