68 lines
2.2 KiB
Python
68 lines
2.2 KiB
Python
"""Hermes shared-fabric profile isolation primitives."""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from .tenant_context import ValidationError, require_ident
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class HermesProfile:
|
|
tenant_id: str
|
|
cell_id: str
|
|
profile_id: str
|
|
hermes_home: Path
|
|
workspace: Path
|
|
|
|
def __post_init__(self) -> None:
|
|
require_ident("tenant_id", self.tenant_id, tenant=True)
|
|
for name in ("cell_id", "profile_id"):
|
|
require_ident(name, getattr(self, name))
|
|
if (
|
|
not isinstance(self.hermes_home, Path)
|
|
or not isinstance(self.workspace, Path)
|
|
or not self.hermes_home.is_absolute()
|
|
or not self.workspace.is_absolute()
|
|
):
|
|
raise ValidationError("profile paths must be absolute")
|
|
|
|
@property
|
|
def environment(self) -> dict[str, str]:
|
|
return {
|
|
"HERMES_HOME": str(self.hermes_home),
|
|
"HOME": str(self.hermes_home),
|
|
"AIOS_TENANT_ID": self.tenant_id,
|
|
"AIOS_CELL_ID": self.cell_id,
|
|
"AIOS_PROFILE_ID": self.profile_id,
|
|
}
|
|
|
|
@staticmethod
|
|
def _reject_symlink_components(root: Path) -> None:
|
|
current = Path(root.anchor)
|
|
for part in root.parts[1:]:
|
|
current = current / part
|
|
if current.is_symlink():
|
|
raise ValidationError("profile root contains symlink")
|
|
|
|
def _validate_roots(self) -> None:
|
|
for root in (self.hermes_home, self.workspace):
|
|
self._reject_symlink_components(root)
|
|
|
|
def prepare(self) -> None:
|
|
self._validate_roots()
|
|
for root in (self.hermes_home, self.workspace):
|
|
root.mkdir(parents=True, exist_ok=True)
|
|
self._validate_roots()
|
|
for root in (self.hermes_home, self.workspace):
|
|
os.chmod(root, 0o700)
|
|
|
|
def assert_path_allowed(self, path: str | Path) -> None:
|
|
self._validate_roots()
|
|
target = Path(path).resolve()
|
|
for root in (self.hermes_home.resolve(), self.workspace.resolve()):
|
|
if target == root or root in target.parents:
|
|
return
|
|
raise ValidationError("path escapes profile isolation boundary")
|