Merge pull request #9496 from makeplane/preview

v1.4.0-fixes
This commit is contained in:
Manish Gupta
2026-07-29 12:20:05 +05:30
committed by GitHub
2 changed files with 18 additions and 6 deletions

View File

@@ -10,6 +10,7 @@ from django.conf import settings
from django.http import HttpResponseRedirect
from django.utils import timezone
from django.db import IntegrityError
from django.db.models import Q
# Third party imports
from rest_framework import status
@@ -708,8 +709,19 @@ class ProjectBulkAssetEndpoint(BaseAPIView):
if not asset_ids:
return Response({"error": "No asset ids provided."}, status=status.HTTP_400_BAD_REQUEST)
# get the asset id — scope to the project to prevent cross-project IDOR
assets = FileAsset.objects.filter(id__in=asset_ids, workspace__slug=slug, project_id=project_id)
# Scope to the requester's own uploads in this workspace, limited to assets that are
# either unassociated or already in this project. This endpoint *associates*
# freshly-uploaded assets, which are not yet project-scoped (e.g. a cover uploaded
# during project creation has project_id=NULL until this call sets it) — so the
# earlier project_id=project_id filter 404'd that flow. created_by + the
# unassociated-or-same-project bound prevent cross-project/user IDOR (a caller can
# only touch their own uploads, cannot move an asset in from another project, and
# @allow_permission already scopes them to this project).
assets = FileAsset.objects.filter(
id__in=asset_ids,
workspace__slug=slug,
created_by=request.user,
).filter(Q(project_id=project_id) | Q(project_id__isnull=True))
# Get the first asset
asset = assets.first()

View File

@@ -15,8 +15,8 @@ def sanitize_filename(filename):
"""
Sanitize a filename to prevent path traversal attacks.
Strips directory components, path traversal sequences, and null bytes
from user-supplied filenames used in upload paths and S3 object keys.
Strips directory components, path traversal sequences, and control
characters from user-supplied filenames used in upload paths and S3 object keys.
Returns None for empty/missing input so callers can still validate
that a filename was provided.
@@ -24,8 +24,8 @@ def sanitize_filename(filename):
if not filename or not isinstance(filename, str):
return None
# Strip null bytes
filename = filename.replace("\x00", "")
# Strip ASCII control characters (0-31 and 127), including null bytes
filename = "".join(char for char in filename if not (ord(char) < 32 or ord(char) == 127))
# Normalize backslashes so os.path.basename handles Windows-style paths on POSIX
filename = filename.replace("\\", "/")