mirror of
https://github.com/makeplane/plane.git
synced 2026-09-01 19:48:42 +02:00
@@ -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()
|
||||
|
||||
@@ -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("\\", "/")
|
||||
|
||||
Reference in New Issue
Block a user