fix(13): CR-02 apply PurePosixPath.name guard in WebDAV upload_file and rename to prevent path traversal

This commit is contained in:
curo1305
2026-06-23 00:27:52 +02:00
parent d4b2697109
commit a1d1c3ba2e
+23 -8
View File
@@ -41,7 +41,7 @@ import io
import uuid
import urllib.parse
from datetime import datetime, timezone
from pathlib import Path
from pathlib import Path, PurePosixPath
from typing import Optional
from webdav3.client import Client
@@ -505,13 +505,20 @@ class WebDAVBackend(StorageBackend, MutableCloudResourceAdapter):
{'kind': 'updated', 'reason': 'renamed', 'provider_item_id': str, 'name': str}
"""
# Compute new path: same parent directory, new name
# T-13-04 path-traversal guard: strip all directory components from the
# caller-supplied new_name. A value like "../../evil" would otherwise
# escape the item's parent directory when the new path is computed.
safe_new_name = PurePosixPath(new_name).name
if not safe_new_name or safe_new_name in (".", ".."):
safe_new_name = new_name # caller-validated; keep original on empty result
# Compute new path: same parent directory, safe new name
parts = provider_item_id.rstrip("/").rsplit("/", 1)
if len(parts) == 2:
parent_path = parts[0]
new_path = f"{parent_path}/{new_name}"
new_path = f"{parent_path}/{safe_new_name}"
else:
new_path = new_name
new_path = safe_new_name
def _move_rename() -> dict:
try:
@@ -520,7 +527,7 @@ class WebDAVBackend(StorageBackend, MutableCloudResourceAdapter):
"kind": MUT_KIND_UPDATED,
"reason": MUT_REASON_RENAMED,
"provider_item_id": new_path,
"name": new_name,
"name": safe_new_name,
}
except Exception as exc:
return self._normalize_error(exc)
@@ -627,10 +634,18 @@ class WebDAVBackend(StorageBackend, MutableCloudResourceAdapter):
{'kind': 'uploaded', 'reason': 'created', 'provider_item_id': str, 'name': str, 'parent_ref': str | None, 'size': int}
"""
# T-13-04 path-traversal guard: strip all directory components from the
# caller-supplied filename so a value like "../../evil" cannot escape
# the intended parent_ref. PurePosixPath.name returns only the final
# segment. Fall back to "upload" for empty or dot-only results.
safe_filename = PurePosixPath(filename).name
if not safe_filename or safe_filename in (".", ".."):
safe_filename = "upload"
if parent_ref:
object_path = f"{parent_ref.rstrip('/')}/{filename}"
object_path = f"{parent_ref.rstrip('/')}/{safe_filename}"
else:
object_path = filename
object_path = safe_filename
def _upload() -> dict:
try:
@@ -640,7 +655,7 @@ class WebDAVBackend(StorageBackend, MutableCloudResourceAdapter):
"kind": MUT_KIND_UPLOADED,
"reason": MUT_REASON_CREATED,
"provider_item_id": object_path,
"name": filename,
"name": safe_filename,
"parent_ref": parent_ref,
"size": len(content),
}