wu981526092 Claude Opus 4.5 commited on
Commit
ea856a6
·
1 Parent(s): 795b72e

Security & HF Spaces fixes: Enable CSRF, auth middleware, persistent storage

Browse files

- Re-enable CSRF state verification in OAuth callback (auth.py:165-177)
- Re-enable ConditionalAuthMiddleware in app.py
- Add /auth/oauth-callback to excluded paths in auth middleware
- Centralize DB_URI config to use /data persistent storage in HF Spaces
- Update database/__init__.py and init_db.py to use central config
- Add graceful error handling for /data directory creation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

backend/app.py CHANGED
@@ -90,8 +90,7 @@ else:
90
  )
91
 
92
  # Add conditional authentication middleware (after session)
93
- # TEMPORARILY DISABLED for debugging session issues
94
- # app.add_middleware(ConditionalAuthMiddleware)
95
 
96
  # Add usage tracking middleware (last, to track authenticated requests)
97
  app.add_middleware(UsageTrackingMiddleware)
 
90
  )
91
 
92
  # Add conditional authentication middleware (after session)
93
+ app.add_middleware(ConditionalAuthMiddleware)
 
94
 
95
  # Add usage tracking middleware (last, to track authenticated requests)
96
  app.add_middleware(UsageTrackingMiddleware)
backend/database/__init__.py CHANGED
@@ -4,20 +4,14 @@ This package provides database access and utilities for agent monitoring.
4
  """
5
 
6
  import os
7
- from dotenv import load_dotenv
8
  from sqlalchemy import create_engine
9
  from sqlalchemy.ext.declarative import declarative_base
10
  from sqlalchemy.orm import sessionmaker, scoped_session
11
 
12
- # Load environment variables
13
- load_dotenv()
14
 
15
- # Get the absolute path to the project root directory
16
- ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
17
-
18
- # Database URL - use DB_URI from env, fallback to default
19
- DEFAULT_DB_PATH = f"sqlite:///{os.path.join(ROOT_DIR, 'datasets/db/agent_monitoring.db')}"
20
- DATABASE_URL = os.getenv("DB_URI", DEFAULT_DB_PATH)
21
 
22
  # Create engine
23
  engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
 
4
  """
5
 
6
  import os
 
7
  from sqlalchemy import create_engine
8
  from sqlalchemy.ext.declarative import declarative_base
9
  from sqlalchemy.orm import sessionmaker, scoped_session
10
 
11
+ # Import DB_URI from central config (handles HF Spaces vs local dev)
12
+ from utils.config import DB_URI
13
 
14
+ DATABASE_URL = DB_URI
 
 
 
 
 
15
 
16
  # Create engine
17
  engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
backend/database/init_db.py CHANGED
@@ -37,14 +37,15 @@ logger = logging.getLogger(__name__)
37
  # Get the absolute path to the project root directory
38
  ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
39
 
40
- # Database path - use DB_URI from env, fallback to default
41
- DEFAULT_DB_PATH = os.path.join(ROOT_DIR, 'datasets/db/agent_monitoring.db')
42
- DB_URI = os.getenv("DB_URI", f"sqlite:///{DEFAULT_DB_PATH}")
43
  # Extract path from sqlite URI
44
  if DB_URI.startswith("sqlite:///"):
45
  DB_PATH = DB_URI.replace("sqlite:///", "")
46
  else:
47
- DB_PATH = DEFAULT_DB_PATH
 
48
 
49
  def confirm_reset():
50
  """Ask for user confirmation before force resetting the database."""
@@ -56,13 +57,23 @@ def confirm_reset():
56
  def init_database(reset=False, force=False):
57
  """
58
  Initialize the database with the required tables.
59
-
60
  Args:
61
  reset: If True, drop and recreate the tables
62
  force: If True, delete the database file completely
63
  """
64
  # Make sure the directory exists
65
- os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
 
 
 
 
 
 
 
 
 
 
66
 
67
  # Check if database exists
68
  db_exists = os.path.exists(DB_PATH) and os.path.getsize(DB_PATH) > 0
 
37
  # Get the absolute path to the project root directory
38
  ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
39
 
40
+ # Import DB_URI from central config
41
+ from utils.config import DB_URI
42
+
43
  # Extract path from sqlite URI
44
  if DB_URI.startswith("sqlite:///"):
45
  DB_PATH = DB_URI.replace("sqlite:///", "")
46
  else:
47
+ # Fallback for non-sqlite databases
48
+ DB_PATH = os.path.join(ROOT_DIR, 'datasets/db/agent_monitoring.db')
49
 
50
  def confirm_reset():
51
  """Ask for user confirmation before force resetting the database."""
 
57
  def init_database(reset=False, force=False):
58
  """
59
  Initialize the database with the required tables.
60
+
61
  Args:
62
  reset: If True, drop and recreate the tables
63
  force: If True, delete the database file completely
64
  """
65
  # Make sure the directory exists
66
+ db_dir = os.path.dirname(DB_PATH)
67
+ if db_dir: # Only try to create if there's a directory path
68
+ try:
69
+ os.makedirs(db_dir, exist_ok=True)
70
+ logger.info(f"Database directory ensured at: {db_dir}")
71
+ except OSError as e:
72
+ logger.warning(f"Could not create database directory {db_dir}: {e}")
73
+ # In HF Spaces, /data might not be available until Persistent Storage is enabled
74
+ if "/data" in db_dir:
75
+ logger.warning("HF Spaces Persistent Storage may not be enabled. "
76
+ "Database will be stored in ephemeral storage.")
77
 
78
  # Check if database exists
79
  db_exists = os.path.exists(DB_PATH) and os.path.getsize(DB_PATH) > 0
backend/middleware/auth.py CHANGED
@@ -30,12 +30,13 @@ class ConditionalAuthMiddleware:
30
  # Paths that don't require authentication even in HF Spaces
31
  self.excluded_paths = excluded_paths or [
32
  "/docs",
33
- "/redoc",
34
  "/openapi.json",
35
  "/api/observability/health-check",
36
  "/api/observability/environment",
37
  "/auth/login",
38
  "/auth/callback",
 
39
  "/auth/logout",
40
  "/auth/login-page",
41
  "/auth/status",
 
30
  # Paths that don't require authentication even in HF Spaces
31
  self.excluded_paths = excluded_paths or [
32
  "/docs",
33
+ "/redoc",
34
  "/openapi.json",
35
  "/api/observability/health-check",
36
  "/api/observability/environment",
37
  "/auth/login",
38
  "/auth/callback",
39
+ "/auth/oauth-callback", # OAuth callback from HF
40
  "/auth/logout",
41
  "/auth/login-page",
42
  "/auth/status",
backend/routers/auth.py CHANGED
@@ -163,13 +163,17 @@ async def handle_oauth_callback(request: Request, code: str, state: str):
163
  stored_state = None
164
 
165
  if not stored_state:
166
- logger.warning(f"🚫 No stored OAuth state found - proceeding without state verification for debugging")
167
- # Temporarily skip state verification for debugging
168
- # raise HTTPException(status_code=400, detail="No stored state found")
 
 
169
  elif stored_state != state:
170
- logger.error(f"🚫 OAuth state mismatch - stored: {stored_state}, received: {state}")
171
- # Temporarily skip state verification for debugging
172
- # raise HTTPException(status_code=400, detail="State parameter mismatch")
 
 
173
  else:
174
  logger.info("✅ OAuth state verification successful")
175
 
 
163
  stored_state = None
164
 
165
  if not stored_state:
166
+ logger.error("🚫 No stored OAuth state found - CSRF protection triggered")
167
+ raise HTTPException(
168
+ status_code=400,
169
+ detail="No stored state found. Your session may have expired. Please try logging in again."
170
+ )
171
  elif stored_state != state:
172
+ logger.error(f"🚫 OAuth state mismatch - CSRF protection triggered")
173
+ raise HTTPException(
174
+ status_code=400,
175
+ detail="State parameter mismatch. Please try logging in again."
176
+ )
177
  else:
178
  logger.info("✅ OAuth state verification successful")
179
 
utils/config.py CHANGED
@@ -53,7 +53,23 @@ if LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY:
53
  ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
54
 
55
  # Database Configuration
56
- DB_URI = os.getenv("DB_URI", "sqlite:///agent_monitoring.db")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
  # Function to validate configuration
59
  def validate_config():
 
53
  ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
54
 
55
  # Database Configuration
56
+ # For HF Spaces, use /data persistent storage directory
57
+ # For local development, use datasets/db directory
58
+ def _get_default_db_uri():
59
+ """Get default database URI based on environment."""
60
+ if os.getenv("SPACE_ID"): # HF Spaces
61
+ # Use HF Persistent Storage at /data
62
+ # Note: /data directory is created by HF when Persistent Storage is enabled
63
+ # The directory creation is attempted at database init time, not config load time
64
+ return "sqlite:////data/agent_monitoring.db"
65
+ else:
66
+ # Local development - use datasets/db relative to project root
67
+ project_root = Path(__file__).parent.parent.resolve()
68
+ db_dir = project_root / "datasets" / "db"
69
+ os.makedirs(db_dir, exist_ok=True)
70
+ return f"sqlite:///{db_dir}/agent_monitoring.db"
71
+
72
+ DB_URI = os.getenv("DB_URI", _get_default_db_uri())
73
 
74
  # Function to validate configuration
75
  def validate_config():