T05 · Unauthorized Access and Privilege Escalation
Warning
- Location
- scripts/obsidian_audit.py:25
- Finding
- Unrestricted Recursive Rename Target Allows Out-of-Scope File Modification<![CDATA[ ## Vulnerability Details **File Location**: `scripts/obsidian_audit.py:25-79` **Vulnerability Type**: Insufficient target-path validation and unrestricted recursive file modification **Risk Level**: Medium ### Vulnerable Code ```python def scan(vault: Path): issues = [] for p in vault.rglob("*"): if p.is_dir(): continue if any(d in p.parts for d in IGNORE_DIRS): continue if p.name in RESERVED: continue if p.suffix.lower() not in ALLOWED_EXT: continue exp = expected_name(p) if p.name != exp: issues.append((p, "rename", exp)) return issues def main(): ap = argparse.ArgumentParser(description="Audit Obsidian vault naming consistency") ap.add_argument("vault", help="Path to vault root") ap.add_argument("--apply", action="store_true", help="Apply safe renames") args = ap.parse_args() vault = Path(args.vault).expanduser().resolve() if not vault.exists(): raise SystemExit(f"Vault not found: {vault}") issues = scan(vault) if not issues: print("OK: no naming issues found") return print(f"Found {len(issues)} issue(s)") for p, kind, target in issues: rel = p.relative_to(vault) print(f"- {kind}: {rel} -> {target}") if not args.apply: print("\nDry-run only. Re-run with --apply to rename files.") return for p, kind, target in issues: if kind != "rename": continue new_path = p.with_name(target) if new_path.exists(): print(f"skip (exists): {new_path}") continue p.rename(new_path) print(f"renamed: {p.name} -> {new_path.name}") ``` ### Technical Analysis The positional `vault` argument is treated as a trusted Obsidian vault after only checking whether the resolved path exists. The implementation does not: - Require the target to be a directory. - Verify that it ...[truncated 2684 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Require a valid directory** ```python if not vault.is_dir(): raise SystemExit(f"Vault must be a directory: {vault}") ``` 2. **Enforce an explicit vault boundary** - Prefer an administrator- or user-configured allowlisted vault root. - Resolve both the configured root and requested path. - Reject targets outside the configured root using `Path.is_relative_to()` or an equivalent safe comparison. 3. **Verify vault identity** - At minimum, require an expected `.obsidian/` directory before permitting `--apply`. - If vaults without `.obsidian/` must be supported, require an explicit initialization or trust step that records the approved canonical path. 4. **Reject dangerous broad targets** - Reject filesystem roots, user home directories, the skill installation directory, and other predefined sensitive paths. - Do not rely only on path names; compare canonical resolved paths. 5. **Bind apply mode to the reviewed dry-run** - Generate a manifest containing each source path, destination path, and relevant file metadata. - Require apply mode to consume that exact approved manifest. - Refuse application if files or the target root have changed since the dry-run. 6. **Add an enforced confirmation gate** - Display the canonical vault root and exact rename count. - Require explicit confirmation before applying changes. - For bulk operations of ten or more files, require or strongly enforce a recent backup as specified by the skill documentation. 7. **Add regression tests** - Confirm that files, home directories, filesystem roots, and unrelated repositories are rejected. - Confirm that paths outside the approved root are rejected. - Confirm that symlinks cannot be used to escape the approved vault boundary. - Confirm that apply mode cannot proceed with a stale or altered dry-run manifest. ]]>
