T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/hygiene.py:191
- Finding
- Workspace Symlink Traversal Allows Reads and Writes Outside the Audited Workspace## Vulnerability Details **File Location**: `scripts/hygiene.py`, lines 191-211 and 440-450 **Vulnerability Type**: Unrestricted symlink following and insufficient filesystem boundary validation **Risk Level**: High ### Vulnerable Code ```python def append_memory_content(target_path: Path, source_path: Path) -> bool: source_text = load_text(source_path) if not source_text.strip(): return False target_text = load_text(target_path) if source_text.strip() in target_text: return False if target_text and not target_text.endswith("\n"): target_text += "\n" if target_text.strip(): merged = target_text.rstrip() + "\n\n" + source_text.strip() + "\n" else: merged = source_text.rstrip() + "\n" target_path.parent.mkdir(parents=True, exist_ok=True) target_path.write_text(merged, encoding="utf-8") return True ``` ```python def write_report(result: AuditResult, report_date: dt.date, report_only: bool) -> Path: report_dir = result.workspace / "projects" / "system" if not report_dir.exists() and not report_only: report_dir.mkdir(parents=True, exist_ok=True) result.fixes.append("Created report directory: projects/system") elif not report_dir.exists(): report_dir.mkdir(parents=True, exist_ok=True) report_path = report_dir / "hygiene-{0}.md".format(report_date.isoformat()) report_path.write_text(render_report(result, report_date), encoding="utf-8") result.report_path = report_path return report_path ``` ### Technical Analysis The skill treats the audited workspace as trusted and performs filesystem reads and writes without verifying whether path components or files are symbolic links. Python operations including `Path.exists()`, `Path.is_file()`, `Path.read_text()`, and `Path.write_text()` ordinarily follow symlinks. Two exploitable paths result: 1. `write_report ...[truncated 2968 chars]
- Remediation
- ## Remediation Suggestions 1. Treat every audited workspace and all of its contents as untrusted input. 2. Resolve each source, destination, and parent directory before access, then verify containment beneath the resolved workspace root: ```python workspace_root = result.workspace.resolve(strict=True) candidate = target_path.resolve(strict=False) candidate.relative_to(workspace_root) ``` Reject the operation if `relative_to()` raises `ValueError`. 3. Explicitly reject symlinks for memory source files, daily target files, `projects`, `projects/system`, and report files. Check every existing path component rather than only the final path. 4. For write operations, use descriptor-based APIs with no-follow semantics such as `os.open()` with `O_NOFOLLOW` where supported. Use safe create or replace behavior to reduce time-of-check/time-of-use races. 5. Open and validate trusted parent-directory descriptors, then perform relative operations through those descriptors where platform support permits. 6. Do not overwrite an existing report without an explicit option. Consider exclusive file creation or a securely generated filename. 7. Before `--fix` reads a memory file, require that it is a regular, non-symlinked file located directly inside the validated `memory` directory. 8. Before writing a daily memory file, reject an existing symlink or non-regular file and verify that the resolved parent remains inside the workspace. 9. Make `--report-only` genuinely non-mutating, or rename and document the option to clarify that it still writes a report. 10. Add regression tests using symlinked source files, target files, and parent directories to confirm that all attempted workspace escapes are rejected.
