"""Tests for obsidian_rag.security — path traversal, sanitization, sensitive detection.""" from __future__ import annotations from pathlib import Path import tempfile from unittest.mock import MagicMock import pytest from obsidian_rag.security import ( detect_sensitive, filter_tags, is_symlink_outside_vault, sanitize_text, should_index_dir, validate_path, ) # ---------------------------------------------------------------------- # validate_path # ---------------------------------------------------------------------- def test_validate_path_normal_file(tmp_path: Path): vault = tmp_path / "vault" vault.mkdir() target = vault / "subdir" / "note.md" target.parent.mkdir() target.touch() result = validate_path(Path("subdir/note.md"), vault) assert result == target.resolve() def test_validate_path_traversal_attempt(tmp_path: Path): vault = tmp_path / "vault" vault.mkdir() with pytest.raises(ValueError, match="traversal"): validate_path(Path("../etc/passwd"), vault) def test_validate_path_deep_traversal(tmp_path: Path): vault = tmp_path / "vault" vault.mkdir() with pytest.raises(ValueError, match="traversal"): validate_path(Path("subdir/../../../etc/passwd"), vault) def test_validate_path_absolute_path(tmp_path: Path): vault = tmp_path / "vault" vault.mkdir() with pytest.raises(ValueError): validate_path(Path("/etc/passwd"), vault) def test_validate_path_path_with_dotdot_in_resolve(tmp_path: Path): """Path that resolves inside vault but has .. in parts should be caught.""" vault = tmp_path / "vault" vault.mkdir() sub = vault / "subdir" sub.mkdir() # validate_path checks parts for ".." with pytest.raises(ValueError, match="traversal"): validate_path(Path("subdir/../subdir/../note.md"), vault) # ---------------------------------------------------------------------- # is_symlink_outside_vault # ---------------------------------------------------------------------- def test_is_symlink_outside_vault_internal(tmp_path: Path): vault = tmp_path / "vault" vault.mkdir() note = vault / "note.md" note.touch() link = vault / "link.md" link.symlink_to(note) assert is_symlink_outside_vault(link, vault) is False def test_is_symlink_outside_vault_external(tmp_path: Path): vault = tmp_path / "vault" vault.mkdir() outside = tmp_path / "outside.md" outside.touch() link = vault / "link.md" link.symlink_to(outside) assert is_symlink_outside_vault(link, vault) is True # ---------------------------------------------------------------------- # sanitize_text # ---------------------------------------------------------------------- def test_sanitize_text_strips_html(): raw = "Hello #world" result = sanitize_text(raw) assert "