Harden security
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user