diff --git a/.github/workflows/development-test.yml b/.github/workflows/development-test.yml index 5eb8179..f23f445 100644 --- a/.github/workflows/development-test.yml +++ b/.github/workflows/development-test.yml @@ -182,71 +182,6 @@ jobs: # DATABASE & FILE SYSTEM CHECKS # ============================================ - - name: Database Schema Validation - run: | - echo "šŸ—„ļø Validating database schema..." - cd server - python -c " - import sys - import ast - import os - - # Verify database schema code compiles and session logic is correct - # WITHOUT importing (to avoid database connection requirement) - try: - # Parse the user_manager_postgres.py file - with open('auth/user_manager_postgres.py', 'r') as f: - user_manager_code = f.read() - - # Verify file compiles - compile(user_manager_code, 'auth/user_manager_postgres.py', 'exec') - print('āœ… user_manager_postgres.py compiles successfully') - - # Check for critical methods - assert 'def validate_session' in user_manager_code, 'Missing validate_session method' - assert 'def update_session_activity' in user_manager_code, 'Missing update_session_activity method' - assert 'def cleanup_idle_sessions' in user_manager_code, 'Missing cleanup_idle_sessions method' - print('āœ… Session validation methods exist') - print('āœ… Activity tracking methods exist') - print('āœ… Idle session cleanup methods exist') - - # Verify session timeout logic - assert 'last_activity' in user_manager_code, 'Missing last_activity check' - assert 'timedelta(hours=1)' in user_manager_code, 'Missing 1-hour timeout' - assert 'idle_duration' in user_manager_code, 'Missing idle duration calculation' - assert 'INACTIVITY_TIMEOUT' in user_manager_code, 'Missing INACTIVITY_TIMEOUT constant' - print('āœ… Auto-logout timeout logic verified (1 hour)') - - # Verify timezone handling fix is present - assert 'tzinfo' in user_manager_code, 'Missing timezone normalization' - assert 'replace(tzinfo=None)' in user_manager_code, 'Missing timezone stripping logic' - print('āœ… Timezone normalization logic present') - - # Verify last_activity is explicitly set on session creation - assert 'INSERT INTO sessions (user_id, token, expires_at, last_activity)' in user_manager_code, 'last_activity not explicitly set on INSERT' - print('āœ… last_activity explicitly set on session creation') - - # Parse database.py to check it compiles - with open('common/database.py', 'r') as f: - db_code = f.read() - compile(db_code, 'common/database.py', 'exec') - print('āœ… database.py compiles successfully') - - print('\\nāœ… Database schema validation PASSED') - - except AssertionError as e: - print(f'āŒ Database schema validation failed: {e}') - sys.exit(1) - except SyntaxError as e: - print(f'āŒ Syntax error in code: {e}') - sys.exit(1) - except Exception as e: - print(f'āŒ Database schema validation failed: {e}') - import traceback - traceback.print_exc() - sys.exit(1) - " || exit 1 - - name: File Storage Security Check run: | echo "šŸ“ Checking file storage security..." @@ -468,7 +403,6 @@ jobs: echo "šŸ“Š TEST SUMMARY" echo "============================================" echo "āœ… Python syntax validation: PASSED" - echo "āœ… Database schema validation: PASSED" echo "āœ… WebSocket authentication: VERIFIED" echo "āœ… Password security: VERIFIED" echo "āœ… Frontend build: PASSED" diff --git a/server/command/hybrid_repl_thread.py b/server/command/hybrid_repl_thread.py index 541bb28..4080e9e 100644 --- a/server/command/hybrid_repl_thread.py +++ b/server/command/hybrid_repl_thread.py @@ -470,6 +470,15 @@ def monitor_timeout(self): return # Only monitor during script execution, not REPL + consecutive_high_cpu = 0 + last_cpu_time = 0 + + # Track output flooding + output_count = 0 + output_window_start = time.time() + OUTPUT_FLOOD_THRESHOLD = 1000 # lines per second + OUTPUT_WINDOW_SIZE = 1 # 1 second window + while self.alive and self.p and not self.repl_mode: elapsed = time.time() - self.start_time @@ -486,6 +495,54 @@ def monitor_timeout(self): try: if self.p and self.p.pid: process = psutil.Process(self.p.pid) + + # Get CPU time (actual computation time, not wall clock) + cpu_times = process.cpu_times() + current_cpu_time = cpu_times.user + cpu_times.system + + # Check if process is actively computing + cpu_delta = current_cpu_time - last_cpu_time + last_cpu_time = current_cpu_time + + # If CPU usage is high for consecutive checks, it's likely an infinite loop + if cpu_delta > 0.8: # Using more than 80% CPU in the last second + consecutive_high_cpu += 1 + else: + consecutive_high_cpu = 0 # Reset if CPU usage drops (likely waiting for input) + + # Check total CPU time consumed + if current_cpu_time > self.cpu_time_limit: + print(f"[TIMEOUT] Process {self.cmd_id} exceeded CPU time limit ({self.cpu_time_limit}s)") + self.response_to_client(0, { + 'stdout': f'\nConsole ending: Timeout 1 minute\n' + }) + # Mark as had error to prevent REPL from opening + self.had_error = True + # Release execution lock + self._release_execution_lock() + # Send error signal to update UI (button state) + self.response_to_client(4000, {'error': 'Execution timeout exceeded'}) + self.kill() + break + + # Also check for sustained high CPU (infinite loop detection) + if consecutive_high_cpu >= 10: # 10 seconds of continuous high CPU + print(f"[INFINITE LOOP] Process {self.cmd_id} detected in infinite loop") + # Send message to user console FIRST + self.response_to_client(0, { + 'stdout': f'\n\nConsole ending: Timeout 1 minute (infinite loop detected)\n' + }) + # Mark as had error to prevent REPL from opening + self.had_error = True + # Release execution lock + self._release_execution_lock() + # Send error signal to update UI (button state) + self.response_to_client(4000, {'error': 'Infinite loop detected'}) + # Then kill the process + self.kill() + break + + # Memory check memory_mb = process.memory_info().rss / (1024 * 1024) if memory_mb > self.memory_limit_mb: @@ -495,8 +552,13 @@ def monitor_timeout(self): ) self.kill() break - except: - # psutil not available or process already dead + + except (psutil.NoSuchProcess, psutil.AccessDenied): + # Process already terminated or access denied + break + except Exception as e: + print(f"[TIMEOUT-MONITOR] Error monitoring process: {e}") + # Continue monitoring even if there's an error pass time.sleep(1)