"""Full codebase audit scanner""" import sys, os, re sys.path.insert(0, '.') # === 1. Backend API endpoints === print('=' * 60) print('1. BACKEND ROUTE REGISTRY') print('=' * 60) api_files = [f for f in os.listdir('server/api') if f.endswith('.py') and f not in ('__init__.py', 'dependencies.py', 'schemas.py')] for f in sorted(api_files): try: with open(f'server/api/{f}', 'r', encoding='utf-8') as fh: content = fh.read() prefix_match = re.search(r'prefix=["\'](/api/[^"\']+)["\']', content) prefix = prefix_match.group(1) if prefix_match else '???' routes = re.findall(r'@router\.(get|post|put|delete|websocket)\(["\'](.*?)["\']', content) if routes: print(f' {f}: prefix={prefix}') for method, path in routes: print(f' {method.upper():4s} {prefix}{path}') except Exception as e: print(f' {f}: ERROR {e}') # === 2. Frontend API calls === print() print('=' * 60) print('2. FRONTEND API CALLS') print('=' * 60) html_files = [f for f in os.listdir('web/app') if f.endswith('.html')] for f in sorted(html_files): try: with open(f'web/app/{f}', 'r', encoding='utf-8') as fh: content = fh.read() pattern = r"API\s*\+\s*['\"](/[^'\"]+)['\"]" calls = re.findall(pattern, content) if calls: print(f' {f}:') for call in sorted(set(calls)): print(f' {call}') except Exception as e: print(f' {f}: ERROR {e}') # === 3. Missing imports (AuditLog used but not imported) === print() print('=' * 60) print('3. POTENTIAL MISSING IMPORTS (AuditLog used without import)') print('=' * 60) for f in sorted(api_files): try: with open(f'server/api/{f}', 'r', encoding='utf-8') as fh: content = fh.read() uses_audit = 'AuditLog(' in content imports_audit = 'from server.domain.models import' in content and 'AuditLog' in content.split('from server.domain.models import')[1].split('\n')[0] if 'from server.domain.models import' in content else False # Also check local imports local_imports_audit = 'from server.domain.models import AuditLog' in content or ('AuditLog' in content and 'import AuditLog' in content) if uses_audit and not imports_audit and not local_imports_audit: # Check if it's imported via local import inside function has_local = 'import AuditLog' in content or 'from server.domain.models import' in content if not has_local: print(f' [BUG] {f}: Uses AuditLog() but never imported!') else: print(f' [WARN] {f}: Uses AuditLog() via local import only') except Exception as e: pass # === 4. SSH pool usage (acquire without release) === print() print('=' * 60) print('4. SSH POOL: acquire() without release()') print('=' * 60) for root, dirs, files in os.walk('server'): for f in files: if not f.endswith('.py'): continue path = os.path.join(root, f) try: with open(path, 'r', encoding='utf-8') as fh: content = fh.read() acquires = len(re.findall(r'ssh_pool\.acquire', content)) releases = len(re.findall(r'ssh_pool\.release', content)) if acquires > 0 and acquires != releases: print(f' [WARN] {path}: acquire={acquires} release={releases}') except: pass # === 5. AsyncSessionLocal without commit === print() print('=' * 60) print('5. DB writes without commit()') print('=' * 60) for root, dirs, files in os.walk('server'): for f in files: if not f.endswith('.py'): continue path = os.path.join(root, f) try: with open(path, 'r', encoding='utf-8') as fh: content = fh.read() has_create = 'repo.create(' in content or 'session.add(' in content or 'session.execute(update' in content has_commit = 'session.commit()' in content if has_create and not has_commit and 'api/' in path: print(f' [WARN] {path}: has DB writes but no session.commit()') except: pass # === 6. Hardcoded secrets or IPs === print() print('=' * 60) print('6. HARDCODED SECRETS / IPs') print('=' * 60) for root, dirs, files in os.walk('server'): for f in files: if not f.endswith('.py'): continue path = os.path.join(root, f) try: with open(path, 'r', encoding='utf-8') as fh: lines = fh.readlines() for i, line in enumerate(lines, 1): if re.search(r'password\s*=\s*["\'][^"\']{3,}["\']', line) and 'test' not in line.lower() and 'hash' not in line.lower() and 'Column' not in line: print(f' [WARN] {path}:{i}: possible hardcoded password') if re.search(r'\b10\.0\.\d+\.\d+\b|\b192\.168\.\d+\.\d+\b|\b172\.(1[6-9]|2\d|3[01])\.\d+\.\d+\b', line) and 'example' not in line.lower() and 'comment' not in line.lower(): print(f' [INFO] {path}:{i}: hardcoded private IP') except: pass print() print('=' * 60) print('SCAN COMPLETE') print('=' * 60)