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

@@ -17,6 +17,10 @@ from django.core.cache import cache
from core.clients import signalapi
from core.messaging import media_bridge
from core.security.attachments import (
validate_attachment_metadata,
validate_attachment_url,
)
from core.transports.capabilities import supports, unsupported_reason
from core.util import logs
@@ -665,17 +669,21 @@ async def _normalize_gateway_attachment(service: str, row: dict, session):
if isinstance(content, memoryview):
content = content.tobytes()
if isinstance(content, bytes):
filename, content_type = validate_attachment_metadata(
filename=normalized.get("filename") or "attachment.bin",
content_type=normalized.get("content_type") or "application/octet-stream",
size=normalized.get("size") or len(content),
)
blob_key = media_bridge.put_blob(
service=service,
content=content,
filename=normalized.get("filename") or "attachment.bin",
content_type=normalized.get("content_type") or "application/octet-stream",
filename=filename,
content_type=content_type,
)
return {
"blob_key": blob_key,
"filename": normalized.get("filename") or "attachment.bin",
"content_type": normalized.get("content_type")
or "application/octet-stream",
"filename": filename,
"content_type": content_type,
"size": normalized.get("size") or len(content),
}
@@ -685,33 +693,39 @@ async def _normalize_gateway_attachment(service: str, row: dict, session):
source_url = normalized.get("url")
if source_url:
try:
async with session.get(source_url) as response:
safe_url = validate_attachment_url(source_url)
async with session.get(safe_url) as response:
if response.status == 200:
payload = await response.read()
blob_key = media_bridge.put_blob(
service=service,
content=payload,
filename, content_type = validate_attachment_metadata(
filename=normalized.get("filename")
or source_url.rstrip("/").split("/")[-1]
or safe_url.rstrip("/").split("/")[-1]
or "attachment.bin",
content_type=normalized.get("content_type")
or response.headers.get(
"Content-Type", "application/octet-stream"
),
size=normalized.get("size") or len(payload),
)
blob_key = media_bridge.put_blob(
service=service,
content=payload,
filename=filename,
content_type=content_type,
)
return {
"blob_key": blob_key,
"filename": normalized.get("filename")
or source_url.rstrip("/").split("/")[-1]
or "attachment.bin",
"content_type": normalized.get("content_type")
or response.headers.get(
"Content-Type", "application/octet-stream"
),
"filename": filename,
"content_type": content_type,
"size": normalized.get("size") or len(payload),
}
except Exception:
log.warning("%s attachment fetch failed for %s", service, source_url)
except Exception as exc:
log.warning(
"%s attachment fetch failed for %s: %s",
service,
source_url,
exc,
)
return normalized
@@ -1074,21 +1088,27 @@ async def fetch_attachment(service: str, attachment_ref: dict):
if blob_key:
return media_bridge.get_blob(blob_key)
if direct_url:
safe_url = validate_attachment_url(direct_url)
timeout = aiohttp.ClientTimeout(total=20)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get(direct_url) as response:
async with session.get(safe_url) as response:
if response.status != 200:
return None
content = await response.read()
return {
"content": content,
"content_type": response.headers.get(
filename, content_type = validate_attachment_metadata(
filename=attachment_ref.get("filename")
or safe_url.rstrip("/").split("/")[-1]
or "attachment.bin",
content_type=response.headers.get(
"Content-Type",
attachment_ref.get("content_type", "application/octet-stream"),
),
"filename": attachment_ref.get("filename")
or direct_url.rstrip("/").split("/")[-1]
or "attachment.bin",
size=len(content),
)
return {
"content": content,
"content_type": content_type,
"filename": filename,
"size": len(content),
}
return None