From 85fbd2bd8cc88f4b381d284efcfcfd941d8b29aa Mon Sep 17 00:00:00 2001 From: Jon Saad-Falcon <41205309+jonsaadfalcon@users.noreply.github.com> Date: Sat, 28 Mar 2026 21:20:01 -0700 Subject: [PATCH] fix: enforce 0o600/0o700 permissions on all database and data files Co-Authored-By: Claude Sonnet 4.6 --- src/openjarvis/cli/log_config.py | 4 +++- src/openjarvis/connectors/attachment_store.py | 7 ++++++- src/openjarvis/connectors/store.py | 4 +++- src/openjarvis/core/config.py | 8 ++++++++ src/openjarvis/security/audit.py | 4 +++- src/openjarvis/server/session_store.py | 4 +++- src/openjarvis/traces/store.py | 4 ++++ 7 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/openjarvis/cli/log_config.py b/src/openjarvis/cli/log_config.py index 751a58fd..17174423 100644 --- a/src/openjarvis/cli/log_config.py +++ b/src/openjarvis/cli/log_config.py @@ -54,8 +54,10 @@ def setup_logging( # File handler (verbose or explicit path) if verbose or log_file is not None: if log_file is None: + from openjarvis.security.file_utils import secure_mkdir + log_dir = Path.home() / ".openjarvis" - log_dir.mkdir(parents=True, exist_ok=True) + secure_mkdir(log_dir) log_file = log_dir / "cli.log" file_handler = RotatingFileHandler( str(log_file), diff --git a/src/openjarvis/connectors/attachment_store.py b/src/openjarvis/connectors/attachment_store.py index 7b653562..770a6961 100644 --- a/src/openjarvis/connectors/attachment_store.py +++ b/src/openjarvis/connectors/attachment_store.py @@ -54,7 +54,9 @@ class AttachmentStore: base_dir = str(DEFAULT_CONFIG_DIR / "blobs") self._base_dir = Path(base_dir) - self._base_dir.mkdir(parents=True, exist_ok=True) + from openjarvis.security.file_utils import secure_mkdir + + secure_mkdir(self._base_dir) db_path = self._base_dir / "attachments.db" self._conn = sqlite3.connect(str(db_path), check_same_thread=False) @@ -95,6 +97,9 @@ class AttachmentStore: blob_path = blob_dir / sha if not blob_path.exists(): blob_path.write_bytes(content) + import os + + os.chmod(blob_path, 0o600) # Upsert metadata row existing = self._conn.execute( diff --git a/src/openjarvis/connectors/store.py b/src/openjarvis/connectors/store.py index 3d10312e..2e0cfbc8 100644 --- a/src/openjarvis/connectors/store.py +++ b/src/openjarvis/connectors/store.py @@ -124,7 +124,9 @@ class KnowledgeStore(MemoryBackend): self._db_path = str(db_path) # Ensure the parent directory exists (skip for :memory:) if self._db_path != ":memory:": - Path(self._db_path).parent.mkdir(parents=True, exist_ok=True) + from openjarvis.security.file_utils import secure_create + + secure_create(Path(self._db_path)) self._conn = sqlite3.connect(self._db_path, check_same_thread=False) self._conn.row_factory = sqlite3.Row diff --git a/src/openjarvis/core/config.py b/src/openjarvis/core/config.py index 290906c6..23f41635 100644 --- a/src/openjarvis/core/config.py +++ b/src/openjarvis/core/config.py @@ -29,6 +29,13 @@ DEFAULT_CONFIG_DIR = Path.home() / ".openjarvis" DEFAULT_CONFIG_PATH = DEFAULT_CONFIG_DIR / "config.toml" +def _ensure_config_dir() -> Path: + """Ensure the config directory exists with restrictive permissions.""" + from openjarvis.security.file_utils import secure_mkdir + + return secure_mkdir(DEFAULT_CONFIG_DIR) + + @dataclass(slots=True) class GpuInfo: """Detected GPU metadata.""" @@ -1327,6 +1334,7 @@ def load_config(path: Optional[Path] = None) -> JarvisConfig: Explicit config file. If not set, uses ``OPENJARVIS_CONFIG`` when set, otherwise ``~/.openjarvis/config.toml``. """ + _ensure_config_dir() hw = detect_hardware() cfg = JarvisConfig(hardware=hw) cfg.engine.default = recommend_engine(hw) diff --git a/src/openjarvis/security/audit.py b/src/openjarvis/security/audit.py index dcb842ad..06a68d30 100644 --- a/src/openjarvis/security/audit.py +++ b/src/openjarvis/security/audit.py @@ -36,7 +36,9 @@ class AuditLogger: bus: Optional[EventBus] = None, ) -> None: self._db_path = Path(db_path) - self._db_path.parent.mkdir(parents=True, exist_ok=True) + from openjarvis.security.file_utils import secure_create + + secure_create(self._db_path) self._conn = sqlite3.connect(str(self._db_path)) self._conn.execute( """ diff --git a/src/openjarvis/server/session_store.py b/src/openjarvis/server/session_store.py index fcea2601..892b1692 100644 --- a/src/openjarvis/server/session_store.py +++ b/src/openjarvis/server/session_store.py @@ -23,7 +23,9 @@ class SessionStore: def __init__(self, db_path: str = "") -> None: if not db_path: db_path = str(Path.home() / ".openjarvis" / "sessions.db") - Path(db_path).parent.mkdir(parents=True, exist_ok=True) + from openjarvis.security.file_utils import secure_create + + secure_create(Path(db_path)) self._db = sqlite3.connect(db_path, check_same_thread=False) self._db.row_factory = sqlite3.Row self._create_tables() diff --git a/src/openjarvis/traces/store.py b/src/openjarvis/traces/store.py index 9c4ceb66..72a26a2e 100644 --- a/src/openjarvis/traces/store.py +++ b/src/openjarvis/traces/store.py @@ -81,6 +81,10 @@ class TraceStore: def __init__(self, db_path: str | Path) -> None: self._db_path = str(db_path) + if self._db_path != ":memory:": + from openjarvis.security.file_utils import secure_create + + secure_create(Path(self._db_path)) # check_same_thread=False is safe with WAL mode. The # AgenticRunner dispatches agent work to a ThreadPoolExecutor # (for Playwright compat), so trace writes may originate from