{"record":{"id":"c178d3b24edfb33d","repo":"harry0703/MoneyPrinterTurbo","slug":"empty-path-is-not-allowed","errorCode":null,"errorMessage":"empty path is not allowed","messagePattern":"empty path is not allowed","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"warning","filePath":"app/utils/file_security.py","lineNumber":15,"sourceCode":"import os\n\n\ndef resolve_path_within_directory(\n    base_dir: str,\n    unsafe_path: str,\n    *,\n    require_file: bool = True,\n) -> str:\n    # 用户传入的路径可能是文件名、相对路径、绝对路径，也可能夹带 `../`。\n    # 这里统一解析成真实路径，并用 commonpath 判断它是否仍在允许目录内。\n    # 这样比简单判断字符串前缀可靠，可以覆盖符号链接、重复分隔符、相对路径\n    # 等场景，适用于上传目录、素材目录、任务产物目录这类白名单目录。\n    if not unsafe_path:\n        raise ValueError(\"empty path is not allowed\")\n\n    base_dir_real = os.path.realpath(base_dir)\n    candidate_path = unsafe_path\n    if not os.path.isabs(candidate_path):\n        candidate_path = os.path.join(base_dir_real, candidate_path)\n\n    resolved_path = os.path.realpath(candidate_path)\n    try:\n        common_path = os.path.commonpath([base_dir_real, resolved_path])\n    except ValueError as exc:\n        # Windows 下不同盘符会触发 ValueError，这类路径一定不属于允许目录。\n        raise ValueError(\"path is outside the allowed directory\") from exc\n\n    if common_path != base_dir_real:\n        raise ValueError(\"path is outside the allowed directory\")\n\n    if require_file and not os.path.isfile(resolved_path):\n        raise ValueError(\"file does not exist\")","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/harry0703/MoneyPrinterTurbo/blob/1f9f19c2021a68d04df228f33e9099a0c947f6f8/app/utils/file_security.py#L1-L33","documentation":"Path-security guard used for whitelisted directories (uploads, materials, task artifacts): resolve_path_within_directory rejects an empty/blank unsafe_path with ValueError before doing any filesystem work. It is the first checkpoint before containment and existence checks.","triggerScenarios":"Calling resolve_path_within_directory(base_dir, '') or with None-like blank string; API request where the client omitted the path field but the handler still called the resolver.","commonSituations":"Frontend sends an empty file parameter; task record has a missing file name after a partial save; query param like ?file= present but blank.","solutions":["Ensure the caller supplies a non-empty path; check required form/query fields before invoking the resolver.","Return a 400-style validation message to the client instead of letting the ValueError propagate as a 500."],"exampleFix":"# before\nresolved = resolve_path_within_directory(task_dir, request.args.get(\"file\") or \"\")\n\n# after\nraw = (request.args.get(\"file\") or \"\").strip()\nif not raw:\n    raise BadRequest(\"file parameter is required\")\nresolved = resolve_path_within_directory(task_dir, raw)","handlingStrategy":"validation","validationCode":"if not (unsafe_path or \"\").strip():\n    raise ValueError(\"file parameter is required\")  # fail before the resolver","typeGuard":"def has_nonempty_path(value: str | None) -> bool:\n    return isinstance(value, str) and bool(value.strip())","tryCatchPattern":null,"preventionTips":["Validate required path fields at the API boundary (400 responses) instead of deep in services.","Make clients use server-returned filenames rather than constructing paths."],"tags":["path-traversal","security","validation"],"backgroundTag":null,"analyzedSha":"1f9f19c2021a68d04df228f33e9099a0c947f6f8","analyzedAt":"2026-08-14T19:41:05.568Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}