T09 · Insecure Skill Coding Practices
- Location
scripts/astro-new-post.py:22- Finding
Path Traversal Allows Markdown Files to Be Written Outside the Content Directory
- Content
View full analysis
Vulnerability Details
File Location:
scripts/astro-new-post.py, lines 22–48
Vulnerability Type: Path traversal and arbitrary file creation
Risk Level: HighVulnerable Code
python # Create language directory lang_dir = content_dir / lang lang_dir.mkdir(parents=True, exist_ok=True) # Generate slug from title slug = title.lower().replace(" ", "-").replace("'", "") for char in [",", ".", "!", "?", ":", ";"]: slug = slug.replace(char, "") # Create filename filename = f"{slug}.md" filepath = lang_dir / filename # Check if file already exists if filepath.exists(): print(f"⚠️ File already exists: {filepath}") return filepath # Generate frontmatter today = datetime.now().strftime("%Y-%m-%d") tags_str = str(tags) if tags else "[]" frontmatter = f"""--- title: "{title}" description: "" pubDate: {today} author: "{author}" lang: "{lang}" tags: {tags_str} --- # {title} Write your content here... """ # Write file filepath.write_text(frontmatter, encoding="utf-8")Technical Analysis
The script directly uses the user-controlled
langvalue as a filesystem path component:python lang_dir = content_dir / langIt does not reject absolute paths,
..traversal components, path separators, or other values that resolve outside the configured content directory. Withpathlib, an absolute right-hand operand can also replace the preceding base path.The user-controlled title is converted into a slug using only limited punctuation removal. Path separators and traversal components are not removed or rejected. The resulting slug is then used to construct the destination filename.
Neither the language directory nor the final file path is resolved and checked for containment beneath
content_dir. Consequently, a caller able to supply command-line arguments can direct the script to create directories and a new Markdown fi ...[truncated 1801 chars]- Remediation
View remediation
Remediation Suggestions
-
Validate language identifiers against a strict allowlist or conservative locale pattern, for example:
python import re LOCALE_PATTERN = re.compile(r"^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})?$") if not LOCALE_PATTERN.fullmatch(lang): raise ValueError(f"Invalid language code: {lang}") -
Reject empty language values, absolute paths, path separators,
.components, and..components. -
Replace the current title transformation with a slug generator that permits only a restricted set of characters, such as lowercase ASCII letters, digits, and hyphens.
-
Resolve both the content root and destination and verify containment before creating directories or writing:
python content_root = content_dir.resolve() destination = (content_root / lang / f"{slug}.md").resolve() if destination.parent != content_root / lang: raise ValueError("Destination escapes the expected language directory") if content_root not in destination.parents: raise ValueError("Destination escapes the content directory") -
Create the file using exclusive creation, such as mode
x, to avoid race conditions between the existence check and write. -
If languages are known by the project, prefer an explicit configured allowlist such as
{"en", "es", "fr"}rather than accepting arbitrary directory names.
-
