T09 · Insecure Skill Coding Practices
- Location
scripts/main.py:17- Finding
Workspace Boundary Bypass Through Incomplete Path Validation
- Content
View full analysis
Vulnerability Details
File Location:
scripts/main.py:17-21, with file access sinks atscripts/main.py:122andscripts/main.py:158-167
Vulnerability Type: Improper path validation and insufficient workspace containment
Risk Level: MediumVulnerable Code
python def check_path_traversal(path: str) -> None: """Reject paths containing directory traversal sequences.""" if "../" in path or path.startswith(".."): print(f"Error: path traversal detected in '{path}'. Absolute or traversal paths are not allowed.", file=sys.stderr) sys.exit(1)The validation is applied before the input and output paths are used:
python # Security: reject path traversal check_path_traversal(args.input) check_path_traversal(args.output) if not os.path.isfile(args.input): print(f"Error: input file not found: {args.input}", file=sys.stderr) sys.exit(1)The accepted paths subsequently reach Pillow file operations without canonicalization or workspace-containment checks:
python image = Image.open(input_path).convert("RGBA")python try: img_probe = Image.open(args.input) pixels_per_unit = read_tiff_pixels_per_unit(img_probe) img_probe.close() except Exception: pixels_per_unit = Nonepython image.save(output_path)Technical Analysis
The
check_path_traversalfunction only searches for textual../patterns or paths beginning with... It does not canonicalize paths, reject absolute paths, establish an approved workspace root, or verify that the resolved path remains beneath that root.As a result, paths such as
/tmp/external/image.pngcontain no prohibited substring and pass validation. A relative path pointing to an in-workspace symbolic link can also pass the textual check even when the link resolves outside the workspace.This behavior conflicts with the security boundary documented in
SKILL.md, which states that absolute paths outside the workspace must be rejected ...[truncated 1998 chars]- Remediation
View remediation
Remediation Suggestions
Replace substring-based validation with canonical path containment checks:
-
Define the trusted workspace root explicitly and resolve it:
python from pathlib import Path WORKSPACE = Path.cwd().resolve() -
Resolve each supplied path and verify that it remains beneath the workspace:
python def resolve_workspace_path(raw_path: str) -> Path: candidate = Path(raw_path) if candidate.is_absolute(): raise ValueError(f"Absolute path is not allowed: {raw_path}") resolved = (WORKSPACE / candidate).resolve() if not resolved.is_relative_to(WORKSPACE): raise ValueError(f"Path escapes workspace: {raw_path}") return resolved -
On Python versions without
Path.is_relative_to, useos.path.commonpathcarefully:python if os.path.commonpath([str(WORKSPACE), str(resolved)]) != str(WORKSPACE): raise ValueError(f"Path escapes workspace: {raw_path}") -
Apply the resolved path returned by the validator to every subsequent operation. Do not validate one string and later use the original unvalidated string.
-
Decide on and enforce a symbolic-link policy. If symbolic links are unnecessary, reject any path component that is a symbolic link. Otherwise, always perform containment checks on the fully resolved target.
-
Validate the output parent directory after resolution, require it to exist beneath the workspace, and avoid overwriting existing files unless overwrite behavior is explicitly authorized. Exclusive creation or an explicit
--overwriteoption should be considered. -
Add regression tests covering:
- Absolute input and output paths.
../traversal.- Nested traversal sequences.
- Symbolic links resolving outside the workspace.
- Valid files in nested workspace directories.
- Attempts to overwrite existing output files.
-
