diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 1e9e764..9ee92e8 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -12,3 +12,8 @@ **Vulnerability:** The `_collect_files` function in `scanner/cli/vibesec.py` used `os.scandir` without explicitly checking if entries were symbolic links before processing them as directories or files. This could allow for arbitrary file read or path traversal vulnerabilities by processing symlinks that point outside the expected directories. **Learning:** During static analysis, directory and file collection methods must be robust against maliciously crafted directory structures, specifically symbolic links pointing to sensitive system files. **Prevention:** Explicitly use `entry.is_symlink()` and check `follow_symlinks=False` on `is_file()` to prevent traversing external links or including them during scan operations. + +## 2026-06-11 - Fix Arbitrary File Write via symlink path traversal in `vibesec init` +**Vulnerability:** The `cmd_init` command in `scanner/cli/vibesec.py` created files and directories inside `.cursor/rules` or `VIBESEC_CHECKLIST.md` in the current project root. However, if the project directory contained malicious symlinks (e.g., `.cursor -> /etc` or `.cursor -> /tmp`), the CLI would unknowingly traverse the symlink and write files (e.g., `vibesec.md`) outside the intended directory. This leads to an Arbitrary File Write vulnerability, potentially allowing attackers to overwrite sensitive files or escalate privileges on the victim's machine. +**Learning:** Even simple CLI file operations (like initializing project configurations) are susceptible to Path Traversal via symlinks. When dealing with directory structures that could be maliciously crafted, we cannot trust that `Path(".").resolve() / ".cursor"` stays within the bounds of `Path(".").resolve()`. +**Prevention:** Before performing any filesystem mutation operations (e.g. `mkdir` or `write_text`), ensure the fully resolved path resides strictly within the expected parent boundary. Use `target_file.resolve().is_relative_to(project_root)` as a security check to detect and abort if the path escapes the intended directory. diff --git a/scanner/cli/vibesec.py b/scanner/cli/vibesec.py index 2931698..ef9fa7c 100644 --- a/scanner/cli/vibesec.py +++ b/scanner/cli/vibesec.py @@ -313,6 +313,12 @@ def cmd_init(args): config = tool_configs[tool] if not config.get("shared_only"): target_file = project_root / config["path"] + + # SECURITY: Prevent Arbitrary File Write via symlink path traversal + if not target_file.resolve().is_relative_to(project_root): + print(f"Error: Target path {target_file} escapes the project root. Aborting.", file=sys.stderr) + sys.exit(1) + target_file.parent.mkdir(parents=True, exist_ok=True) if target_file.is_symlink(): target_file.unlink() @@ -333,6 +339,12 @@ def cmd_init(args): installed.append(str(config["path"])) # Always create the checklist checklist_file = project_root / "VIBESEC_CHECKLIST.md" + + # SECURITY: Prevent Arbitrary File Write via symlink path traversal + if not checklist_file.resolve().is_relative_to(project_root): + print(f"Error: Checklist path {checklist_file} escapes the project root. Aborting.", file=sys.stderr) + sys.exit(1) + if checklist_file.is_symlink(): checklist_file.unlink() if not checklist_file.exists():