T09 · Insecure Skill Coding Practices
- Location
scripts/create_note.py:176- Finding
Workspace Escape Allows Arbitrary Markdown File Creation
- Content
View full analysis
Vulnerability Details
File Location:
scripts/create_note.py, lines 176–183 and 277–278
Vulnerability Type: Path traversal and missing workspace containment validation
Risk Level: MediumVulnerable Code
python # Determine output directory if output_dir is None: output_dir = foam_root elif not output_dir.is_absolute(): output_dir = foam_root / output_dir output_dir.mkdir(parents=True, exist_ok=True) # Generate filename slug = slugify(title) date_str = datetime.now().strftime("%Y-%m-%d") filename = f"{slug}.md" filepath = output_dir / filename # Check for existing file counter = 1 original_filepath = filepath while filepath.exists(): filename = f"{slug}-{counter}.md" filepath = output_dir / filename counter += 1 # Write the file filepath.write_text(content) print(f"Created: {filepath.relative_to(foam_root)}")Technical Analysis
The
--dirargument is documented as relative to the Foam workspace, but the implementation accepts both absolute paths and relative paths containing... Relative paths are joined tofoam_rootwithout normalization or a subsequent containment check. Absolute paths bypass the workspace join entirely.The script creates the destination directory and writes the file before calling
relative_to(foam_root). Consequently, the final display operation may raise an exception for an escaped path, but the external file has already been created.The generated filename is constrained by
slugify()and receives a.mdextension. Existing files are not overwritten because a numeric suffix is selected. Nevertheless, an attacker can create Markdown files and parent directories at arbitrary writable locations.Attack Path
- Identify a directory writable by the account executing the Skill.
- Invoke the script with an absolute or traversal-based output directory, for example:
bash python3 scripts/create_n
...[truncated 729 chars]
- Remediation
View remediation
Remediation Suggestions
- Reject absolute values for
--dirbecause the option is documented as workspace-relative. - Resolve both the workspace and destination before creating directories:
python root = foam_root.resolve(strict=True) destination = (root / output_dir).resolve() if not destination.is_relative_to(root): raise ValueError("Output directory must remain inside the Foam workspace") - Repeat containment validation on the final file path immediately before writing.
- Consider rejecting path components equal to
..for clearer user-facing validation. - Avoid following symlinks that lead outside the workspace. Validate the resolved destination after its parent exists.
- Perform the validation before
mkdir()so escaped directories are not created.
- Reject absolute values for
