T09 · Insecure Skill Coding Practices
- Location
scripts/draft_to_toutiao.example.py:16- Finding
Path Containment Check Can Be Bypassed
- Content
View full analysis
Vulnerability Details
File Location:
scripts/draft_to_toutiao.example.py, lines 16-23
Vulnerability Type: Improper path validation
Risk Level: Mediumpython def load_local_article(filepath): """Only files under the current project output directory may be read.""" base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) expected_path = os.path.abspath(filepath) # Path traversal security check if not expected_path.startswith(base_dir): raise SecurityError("Reading files outside the project directory is prohibited!")Technical Analysis
The function uses a string-prefix comparison to determine whether
expected_pathis insidebase_dir. A matching string prefix does not prove filesystem containment.For example, if the project directory is
/opt/project, a path such as/opt/project-backup/secret.txtstarts with/opt/projectand therefore passes the check despite being outside the project. The validation also does not resolve symlinks before enforcing the boundary. A symlink inside the project can consequently refer to a file outside it.In addition, the function's documentation claims that only files in the output directory are permitted, but the implemented boundary is the entire project root. Any project file is therefore readable if its path is supplied.
The current program invokes this function with a fixed article path, so exploitation through the supplied entry point is limited. The flaw becomes exploitable if the function is reused with a user-controlled path, the article path becomes configurable, or an attacker can place a symlink at an accepted location.
Attack Path
- The attacker gains control over the
filepathargument or can create a symlink in an accepted project location. - The attacker supplies either:
- A sibling path whose textual prefix matches the project directory; or
- An in-project symlink that resolves to an external file.
- `os.path.a ...[truncated 833 chars]
- The attacker gains control over the
- Remediation
View remediation
Remediation Suggestions
- Define the precise permitted directory rather than using the project root:
python from pathlib import Path ALLOWED_DIR = ( Path(__file__).resolve().parent.parent / "examples" / "output" / "toutiao" ).resolve()- Resolve the candidate path before checking containment:
python def load_local_article(filepath): candidate = Path(filepath).resolve(strict=True) if not candidate.is_relative_to(ALLOWED_DIR): raise SecurityError("The requested file is outside the allowed output directory.") if not candidate.is_file(): raise SecurityError("The requested path is not a regular file.") with candidate.open("r", encoding="utf-8") as file: lines = file.readlines()- For older Python versions, use
os.path.commonpath()rather thanstartswith():
python if os.path.commonpath([str(ALLOWED_DIR), str(candidate)]) != str(ALLOWED_DIR): raise SecurityError("The requested file is outside the allowed output directory.")- If symlinks are not required, explicitly reject them before opening the file.
- Prefer a fixed, internally constructed article path instead of accepting arbitrary path input.
- Add tests for sibling-prefix paths,
..traversal, absolute external paths, and symlinks targeting external files.
