# Copyright 2026 Guildhouse Dev # SPDX-License-Identifier: Apache-2.0 import os import stat from sqlmodel import SQLModel from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine from sqlmodel.ext.asyncio.session import AsyncSession from gsap_broker.settings import settings engine: AsyncEngine = create_async_engine(settings.database_url, echo=False) def _restrict_db_permissions() -> None: """Fix H-6: restrict SQLite file permissions to owner-only (0o600). Also restricts WAL and SHM files if they exist. """ db_url = settings.database_url if "sqlite" not in db_url: return # Extract path from sqlite URL path = db_url.split("///")[-1] if "///" in db_url else "" if not path or not os.path.exists(path): return mode = stat.S_IRUSR | stat.S_IWUSR # 0o600 try: os.chmod(path, mode) for suffix in ("-wal", "-shm"): extra = path + suffix if os.path.exists(extra): os.chmod(extra, mode) except OSError: pass # may fail on Windows or read-only filesystem async def init_db(): # Set restrictive umask before creating the database old_umask = os.umask(0o077) try: async with engine.begin() as conn: await conn.run_sync(SQLModel.metadata.create_all) # Schema migrations for existing DBs for migration in [ "ALTER TABLE authorization_contexts ADD COLUMN session_mode BOOLEAN DEFAULT 0", "ALTER TABLE delegations ADD COLUMN depth INTEGER DEFAULT 0", "ALTER TABLE delegations ADD COLUMN parent_delegation_id TEXT DEFAULT NULL", ]: try: await conn.execute(__import__("sqlalchemy").text(migration)) except Exception: pass # Column already exists finally: os.umask(old_umask) # Fix H-6: restrict file permissions after creation _restrict_db_permissions() async def get_session(): async with AsyncSession(engine) as session: yield session