T09 · Insecure Skill Coding Practices
- Location
scripts/project_tracker.py:42- Finding
Path Containment Bypass Through Unsafe String-Prefix Validation
- Content
View full analysis
Vulnerability Details
File Location:
scripts/project_tracker.py, lines 42–54
Vulnerability Type: Improper path containment validation and symlink handling
Risk Level: MediumVulnerable Code
python def sanitize_name(name): """Sanitize project name to prevent path traversal. Only allow safe characters.""" name = name.strip().strip("/\\") sanitized = re.sub(r'[^a-zA-Z0-9._-]', '-', name) sanitized = re.sub(r'[-.]{2,}', '-', sanitized) if not sanitized or sanitized.startswith('.'): print("Error: Invalid project name. Use only letters, numbers, hyphens, and underscores.") sys.exit(1) return sanitized def project_path(name): """Return safe path for project data. Validates name stays within DATA_DIR.""" name = sanitize_name(name) p = (DATA_DIR / f"{name}.json").resolve() if not str(p).startswith(str(DATA_DIR.resolve())): print("Error: Invalid project name.") sys.exit(1) return pTechnical Analysis
The code attempts to ensure that the resolved project path remains beneath
DATA_DIRby comparing path strings withstartswith. A string prefix is not equivalent to filesystem ancestry. For example,/tmp/projects-backup/targetbegins with the string/tmp/projects, although it is not inside/tmp/projects.Because
Path.resolve()follows an existing symbolic link, a project file symlink can resolve into a prefix-sharing sibling directory and still pass this test. The sanitized project name prevents direct../traversal, but it does not prevent this symlink-based containment bypass.Both reads and writes are affected because
load_project()andsave_project()rely onproject_path(). Exploitation requires local ability to create or replace an entry in the configured project data directory.Attack Path
- Determine or influence
PROJECTPILOT_DATA, such as setting it to/tmp/projects. - Create a sibling directory whose path shares that textu ...[truncated 1114 chars]
- Determine or influence
- Remediation
View remediation
Remediation Suggestions
Use filesystem-aware ancestry validation rather than string comparison:
python def project_path(name): name = sanitize_name(name) root = DATA_DIR.resolve() candidate = root / f"{name}.json" resolved = candidate.resolve() if not resolved.is_relative_to(root): raise ValueError("Project path escapes the data directory") return candidateFor compatibility with Python versions lacking
Path.is_relative_to(), useos.path.commonpath():python if os.path.commonpath([str(root), str(resolved)]) != str(root): raise ValueError("Project path escapes the data directory")Additional hardening should include:
- Reject project-file symbolic links explicitly with
candidate.is_symlink(). - Ensure the data directory is owned by the expected user and is not writable by untrusted users.
- Open files with no-follow semantics where the operating system supports them.
- Use atomic writes through a securely created temporary file in the same verified directory, followed by
os.replace(). - Revalidate immediately before file access to reduce time-of-check-to-time-of-use exposure.
- Reject project-file symbolic links explicitly with
