mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-17 11:39:28 +00:00
Validate encryption paths and block traversal
This commit is contained in:
@@ -92,6 +92,15 @@ def _require_password(password: str | None) -> None:
|
||||
raise HTTPException(status_code=401, detail="Invalid password")
|
||||
|
||||
|
||||
def _validate_encryption_path(path: Path) -> None:
|
||||
"""Validate that ``path`` stays within the active fingerprint directory."""
|
||||
assert _pm is not None
|
||||
try:
|
||||
_pm.encryption_manager.resolve_relative_path(path)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/api/v1/entry")
|
||||
def search_entry(query: str, authorization: str | None = Header(None)) -> List[Any]:
|
||||
_check_token(authorization)
|
||||
@@ -578,6 +587,7 @@ def backup_parent_seed(
|
||||
if not path_str:
|
||||
raise HTTPException(status_code=400, detail="Missing path")
|
||||
path = Path(path_str)
|
||||
_validate_encryption_path(path)
|
||||
_pm.encryption_manager.encrypt_and_save_file(_pm.parent_seed.encode("utf-8"), path)
|
||||
return {"status": "saved", "path": str(path)}
|
||||
|
||||
|
@@ -120,6 +120,30 @@ class EncryptionManager:
|
||||
|
||||
# --- All functions below this point now use the smart `decrypt_data` method ---
|
||||
|
||||
def resolve_relative_path(self, relative_path: Path) -> Path:
|
||||
"""Resolve ``relative_path`` within ``fingerprint_dir`` and validate it.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
relative_path:
|
||||
The user-supplied path relative to ``fingerprint_dir``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Path
|
||||
The normalized absolute path inside ``fingerprint_dir``.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If the resulting path is absolute or escapes ``fingerprint_dir``.
|
||||
"""
|
||||
|
||||
candidate = (self.fingerprint_dir / relative_path).resolve()
|
||||
if not candidate.is_relative_to(self.fingerprint_dir.resolve()):
|
||||
raise ValueError("Invalid path outside fingerprint directory")
|
||||
return candidate
|
||||
|
||||
def encrypt_parent_seed(self, parent_seed: str) -> None:
|
||||
"""Encrypts and saves the parent seed to 'parent_seed.enc'."""
|
||||
data = parent_seed.encode("utf-8")
|
||||
@@ -147,7 +171,7 @@ class EncryptionManager:
|
||||
return decrypted_data.decode("utf-8").strip()
|
||||
|
||||
def encrypt_and_save_file(self, data: bytes, relative_path: Path) -> None:
|
||||
file_path = self.fingerprint_dir / relative_path
|
||||
file_path = self.resolve_relative_path(relative_path)
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
encrypted_data = self.encrypt_data(data)
|
||||
with exclusive_lock(file_path) as fh:
|
||||
@@ -159,7 +183,7 @@ class EncryptionManager:
|
||||
os.chmod(file_path, 0o600)
|
||||
|
||||
def decrypt_file(self, relative_path: Path) -> bytes:
|
||||
file_path = self.fingerprint_dir / relative_path
|
||||
file_path = self.resolve_relative_path(relative_path)
|
||||
with exclusive_lock(file_path) as fh:
|
||||
fh.seek(0)
|
||||
encrypted_data = fh.read()
|
||||
@@ -182,8 +206,7 @@ class EncryptionManager:
|
||||
"""
|
||||
if relative_path is None:
|
||||
relative_path = Path("seedpass_entries_db.json.enc")
|
||||
|
||||
file_path = self.fingerprint_dir / relative_path
|
||||
file_path = self.resolve_relative_path(relative_path)
|
||||
if not file_path.exists():
|
||||
return {"entries": {}}
|
||||
|
||||
@@ -216,7 +239,7 @@ class EncryptionManager:
|
||||
|
||||
def get_encrypted_index(self) -> Optional[bytes]:
|
||||
relative_path = Path("seedpass_entries_db.json.enc")
|
||||
file_path = self.fingerprint_dir / relative_path
|
||||
file_path = self.resolve_relative_path(relative_path)
|
||||
if not file_path.exists():
|
||||
return None
|
||||
with exclusive_lock(file_path) as fh:
|
||||
@@ -251,7 +274,8 @@ class EncryptionManager:
|
||||
data = json_lib.loads(decrypted_data)
|
||||
else:
|
||||
data = json_lib.loads(decrypted_data.decode("utf-8"))
|
||||
if merge and (self.fingerprint_dir / relative_path).exists():
|
||||
existing_file = self.resolve_relative_path(relative_path)
|
||||
if merge and existing_file.exists():
|
||||
current = self.load_json_data(relative_path)
|
||||
current_entries = current.get("entries", {})
|
||||
for idx, entry in data.get("entries", {}).items():
|
||||
@@ -290,8 +314,7 @@ class EncryptionManager:
|
||||
"""Updates the checksum file for the specified file."""
|
||||
if relative_path is None:
|
||||
relative_path = Path("seedpass_entries_db.json.enc")
|
||||
|
||||
file_path = self.fingerprint_dir / relative_path
|
||||
file_path = self.resolve_relative_path(relative_path)
|
||||
if not file_path.exists():
|
||||
return
|
||||
|
||||
|
Reference in New Issue
Block a user