feat(security): vault wall, path guard, and utils
- utils/paths.py: pyra_home(), ensure_dir(), safe_chmod(), expand() - security/boundaries.py: VaultAccessError, PyraSecurityError, assert_safe_path() (called before every file read), check_vault_lock() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
from pathlib import Path
|
||||
|
||||
from pyra.utils.paths import pyra_home
|
||||
|
||||
|
||||
class VaultAccessError(PermissionError):
|
||||
pass
|
||||
|
||||
|
||||
class PyraSecurityError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
VAULT_PATH: Path = pyra_home() / "vault"
|
||||
BLOCKED_PREFIXES: list[Path] = [VAULT_PATH]
|
||||
|
||||
|
||||
def assert_safe_path(path: Path) -> None:
|
||||
"""Raise VaultAccessError if path resolves into a blocked prefix."""
|
||||
resolved = path.resolve()
|
||||
for blocked in BLOCKED_PREFIXES:
|
||||
blocked_resolved = blocked.resolve()
|
||||
try:
|
||||
resolved.relative_to(blocked_resolved)
|
||||
raise VaultAccessError(
|
||||
f"Access denied: '{resolved}' is inside the protected vault. "
|
||||
"The vault is never accessible to the AI."
|
||||
)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
def check_vault_lock() -> None:
|
||||
"""Raise PyraSecurityError if the vault sentinel is missing."""
|
||||
lock = VAULT_PATH / ".vault_lock"
|
||||
if not lock.exists():
|
||||
raise PyraSecurityError(
|
||||
f"Vault sentinel missing: {lock}\n"
|
||||
"This may indicate tampering. Refusing to start.\n"
|
||||
"Run 'pyra setup' to reinitialise the vault."
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def pyra_home() -> Path:
|
||||
return Path.home() / ".pyra"
|
||||
|
||||
|
||||
def ensure_dir(path: Path, mode: int = 0o700) -> Path:
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
safe_chmod(path, mode)
|
||||
return path
|
||||
|
||||
|
||||
def safe_chmod(path: Path, mode: int) -> None:
|
||||
if os.name != "nt":
|
||||
path.chmod(mode)
|
||||
|
||||
|
||||
def expand(p: str) -> Path:
|
||||
return Path(p).expanduser().resolve()
|
||||
Reference in New Issue
Block a user