Spaces:
Sleeping
Sleeping
File size: 5,435 Bytes
7257069 ef93755 7257069 ef93755 7257069 9aed967 7257069 9aed967 7257069 9aed967 7257069 9aed967 ef93755 7257069 9aed967 7257069 9aed967 7257069 ef93755 9aed967 7257069 9aed967 ef93755 7257069 9aed967 7257069 9aed967 7257069 9aed967 7257069 9aed967 7257069 9aed967 7257069 9aed967 7257069 9aed967 7257069 9aed967 7257069 9aed967 7257069 ef93755 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | """
SecureCodeEnv Task: File Path Handler
Difficulty: Medium
CWE Targets: CWE-22 (Path Traversal), CWE-20 (Improper Input Validation)
"""
TASK = {
"id": "medium_file_path_handler",
"difficulty": "medium",
"cwe_targets": ["CWE-22", "CWE-20"],
"component_name": "FilePathHandler",
"filename": "src/files/file_path_handler.py",
"problem_statement": """Write a Python function:
`resolve_safe_path(base_dir: str, user_path: str) -> str`
Resolves a user-supplied file path relative to a base directory.
MUST prevent ALL forms of path traversal.
REQUIREMENTS:
- base_dir is the trusted root directory (e.g. '/var/uploads')
- user_path is UNTRUSTED input from the user
- Returns the absolute resolved path ONLY if it stays strictly inside base_dir
- Raises ValueError if the resolved path escapes base_dir
- Raises ValueError if user_path is empty/None
- MUST use os.path.realpath() or pathlib.Path.resolve() β NOT string manipulation
- Must also block: absolute paths (/etc/passwd), URL-encoded paths (%2e%2e),
null bytes, and paths that resolve to base_dir itself (must be INSIDE)
WHY string checks fail:
'../' β raises (good), but '%2e%2e%2f' β passes (bad)
You MUST resolve first, then check if inside base_dir.
""",
"starter_code": '''import os
def resolve_safe_path(base_dir: str, user_path: str) -> str:
# BUG: string check only β bypassed by %2e%2e, /etc/passwd, etc.
if not user_path:
raise ValueError("empty")
if ".." in user_path:
raise ValueError("traversal")
return os.path.join(base_dir, user_path)
''',
"test_cases": [
{
"fn": "resolve_safe_path",
"input": ["/var/uploads", "report.pdf"],
"expected_contains": "/var/uploads/report.pdf",
"description": "Safe path resolves correctly"
},
{
"fn": "resolve_safe_path",
"input": ["/var/uploads", "sub/file.txt"],
"expected_contains": "/var/uploads/sub/file.txt",
"description": "Safe subdirectory"
},
{
"fn": "resolve_safe_path",
"input": ["/var/uploads", "../../etc/passwd"],
"expected_exception": "ValueError",
"description": "Classic traversal blocked"
},
{
"fn": "resolve_safe_path",
"input": ["/var/uploads", "/etc/passwd"],
"expected_exception": "ValueError",
"description": "Absolute path escape blocked"
},
{
"fn": "resolve_safe_path",
"input": ["/var/uploads", ""],
"expected_exception": "ValueError",
"description": "Empty path raises ValueError"
},
{
"fn": "resolve_safe_path",
"input": ["/var/uploads", None],
"expected_exception": "ValueError",
"description": "None path raises ValueError"
},
{
"fn": "resolve_safe_path",
"input": ["/var/uploads", "....//....//etc/passwd"],
"expected_exception": "ValueError",
"description": "Double-dot variant blocked"
},
{
"fn": "resolve_safe_path",
"input": ["/var/uploads", "/var/uploads/../../../etc/passwd"],
"expected_exception": "ValueError",
"description": "Traversal via absolute path blocked"
},
],
"attack_type": "traversal",
"security_checks": [
{"type": "required_any",
"patterns": ["resolve()", ".resolve", "realpath"],
"message": "Must use Path.resolve() or os.path.realpath()"},
{"type": "forbidden_any",
"patterns": ['"..\" in', "'..' in"],
"message": "String '..' check is insufficient β use Path.resolve()"},
],
"naive_code": '''import os
def resolve_safe_path(base_dir: str, user_path: str) -> str:
if not user_path: raise ValueError("empty")
if ".." in user_path: raise ValueError("traversal")
return os.path.join(base_dir, user_path)
''',
"optimal_code": '''import os
from pathlib import Path
def resolve_safe_path(base_dir: str, user_path: str) -> str:
"""Resolves user path within base_dir, preventing all traversal forms.
Args:
base_dir: Trusted root directory
user_path: Untrusted user-supplied path (may contain .., encoded chars)
Returns:
Absolute resolved path guaranteed to be strictly inside base_dir
Raises:
ValueError: If path escapes base_dir, is empty, or is invalid
"""
if not user_path:
raise ValueError("user_path must not be empty or None")
# URL-decode to catch %2e%2e style attacks before resolution
try:
from urllib.parse import unquote
user_path = unquote(str(user_path))
except Exception:
pass
# Null byte attack prevention
if "\\x00" in user_path or "%00" in user_path:
raise ValueError("Null byte in path")
base = Path(base_dir).resolve()
resolved = (base / user_path).resolve()
# Critical: must be strictly INSIDE base, not equal to base
try:
relative = resolved.relative_to(base)
if str(relative) == ".":
raise ValueError("Path resolves to base directory itself")
except ValueError:
raise ValueError(
f"Path traversal detected: resolves outside {base_dir!r}"
)
return str(resolved)
''',
}
|