Harden security

This commit is contained in:
2026-03-05 05:42:19 +00:00
parent 06735bdfb1
commit 438e561da0
75 changed files with 6260 additions and 278 deletions

View File

@@ -30,6 +30,10 @@ from core.models import (
User,
WorkspaceConversation,
)
from core.security.attachments import (
validate_attachment_metadata,
validate_attachment_url,
)
from core.util import logs
URL_PATTERN = re.compile(r"https?://[^\s<>'\"\\]+")
@@ -49,9 +53,8 @@ def _filename_from_url(url_value):
def _content_type_from_filename_or_url(url_value, default="application/octet-stream"):
filename = _filename_from_url(url_value)
guessed, _ = mimetypes.guess_type(filename)
return guessed or default
_ = url_value
return str(default or "application/octet-stream")
def _extract_xml_attachment_urls(message_stanza):
@@ -1013,13 +1016,21 @@ class XMPPComponent(ComponentXMPP):
url_value = _clean_url(att.attrib.get("url"))
if not url_value:
continue
try:
safe_url = validate_attachment_url(url_value)
filename, content_type = validate_attachment_metadata(
filename=att.attrib.get("filename") or _filename_from_url(safe_url),
content_type=att.attrib.get("content_type")
or "application/octet-stream",
)
except Exception as exc:
self.log.warning("xmpp dropped unsafe attachment url=%s: %s", url_value, exc)
continue
attachments.append(
{
"url": url_value,
"filename": att.attrib.get("filename")
or _filename_from_url(url_value),
"content_type": att.attrib.get("content_type")
or "application/octet-stream",
"url": safe_url,
"filename": filename,
"content_type": content_type,
}
)
@@ -1028,11 +1039,19 @@ class XMPPComponent(ComponentXMPP):
url_value = _clean_url(oob.text)
if not url_value:
continue
guessed_content_type = _content_type_from_filename_or_url(url_value)
try:
safe_url = validate_attachment_url(url_value)
filename, guessed_content_type = validate_attachment_metadata(
filename=_filename_from_url(safe_url),
content_type=_content_type_from_filename_or_url(safe_url),
)
except Exception as exc:
self.log.warning("xmpp dropped unsafe oob url=%s: %s", url_value, exc)
continue
attachments.append(
{
"url": url_value,
"filename": _filename_from_url(url_value),
"url": safe_url,
"filename": filename,
"content_type": guessed_content_type,
}
)
@@ -1043,11 +1062,19 @@ class XMPPComponent(ComponentXMPP):
for url_value in extracted_urls:
if url_value in existing_urls:
continue
guessed_content_type = _content_type_from_filename_or_url(url_value)
try:
safe_url = validate_attachment_url(url_value)
filename, guessed_content_type = validate_attachment_metadata(
filename=_filename_from_url(safe_url),
content_type=_content_type_from_filename_or_url(safe_url),
)
except Exception as exc:
self.log.warning("xmpp dropped extracted unsafe url=%s: %s", url_value, exc)
continue
attachments.append(
{
"url": url_value,
"filename": _filename_from_url(url_value),
"url": safe_url,
"filename": filename,
"content_type": guessed_content_type,
}
)
@@ -1397,7 +1424,16 @@ class XMPPComponent(ComponentXMPP):
async def upload_and_send(self, att, upload_slot, recipient_jid, sender_jid):
"""Uploads a file and immediately sends the corresponding XMPP message."""
upload_url, put_url, auth_header = upload_slot
headers = {"Content-Type": att["content_type"]}
try:
filename, content_type = validate_attachment_metadata(
filename=att.get("filename"),
content_type=att.get("content_type"),
size=att.get("size"),
)
except Exception as exc:
self.log.warning("xmpp blocked outbound attachment: %s", exc)
return None
headers = {"Content-Type": content_type}
if auth_header:
headers["Authorization"] = auth_header
@@ -1412,7 +1448,7 @@ class XMPPComponent(ComponentXMPP):
)
return None
self.log.debug(
"Successfully uploaded %s to %s", att["filename"], upload_url
"Successfully uploaded %s to %s", filename, upload_url
)
# Send XMPP message immediately after successful upload