# DockFlare v3.0.3 Security Fix Implementation Plan **Date:** October 5, 2025 **Issue:** Critical authentication bypass on IdP management endpoints **Root Cause:** Combination of `DISABLE_PASSWORD_LOGIN` feature + `request_loader` auto-authentication + missing `@login_required` decorators --- ## Root Cause Analysis ### Problem 1: DISABLE_PASSWORD_LOGIN Auto-Login (USER CONFIGURATION) **Your Instance:** You have `DISABLE_PASSWORD_LOGIN=True` enabled in Settings **Impact:** When enabled, ANY unauthenticated request is automatically logged in as `User('anonymous')`: ```python # File: dockflare/app/__init__.py:135-140 if app_instance.config.get('DISABLE_PASSWORD_LOGIN', False): from flask_login import login_user from app.core.user import User user = User('anonymous', auth_method='disabled') login_user(user) # ⚠️ AUTO-LOGIN WITHOUT CREDENTIALS return redirect(request.url) ``` **This is INTENDED behavior** for deployments behind a reverse proxy with external authentication (e.g., Cloudflare Access protecting DockFlare itself). However, it completely bypasses authentication when used in normal deployments. ### Problem 2: request_loader Auto-Authentication (CODE ISSUE) **All Instances:** Even with `DISABLE_PASSWORD_LOGIN=False`, all `api_v2.*` endpoints get auto-authenticated: ```python # File: dockflare/app/__init__.py:161-163 elif request.endpoint and request.endpoint.startswith('api_v2.'): from app.core.user import User return User('api_user') # ⚠️ AUTO-AUTH FOR ALL API ENDPOINTS ``` This was intended to allow MASTER_API_KEY authentication for programmatic API access, but it inadvertently authenticates endpoints in `_UI_ENDPOINT_ALLOWLIST` that should require session-based login. ### Problem 3: Missing @login_required Decorators (CODE ISSUE) **All Instances:** New IdP endpoints lack `@login_required`: ```python # File: dockflare/app/web/api_v2_routes.py:2437-2438 @api_v2_bp.route('/idp/sync', methods=['POST']) def api_sync_idps(): # ⚠️ NO @login_required ``` --- ## Is DISABLE_PASSWORD_LOGIN the Root Cause? **YES and NO:** ### ✅ YES - For Your Instance - **Your configuration:** You have `DISABLE_PASSWORD_LOGIN=True` enabled - **Your impact:** This is why you can access everything without login - **Your fix:** Disable this setting UNLESS you're running DockFlare behind an authentication proxy ### ⚠️ NO - For General Deployments - **Default configuration:** `DISABLE_PASSWORD_LOGIN=False` - **Still vulnerable:** The `request_loader` + missing `@login_required` still allows unauthenticated API access - **Why:** The `request_loader` auto-authenticates all `api_v2.*` endpoints as `'api_user'`, bypassing the need for MASTER_API_KEY on allowlisted endpoints --- ## Two-Part Security Fix ### Part A: Architectural Fix (For All Users) Fix the `request_loader` to NOT auto-authenticate UI-intended endpoints: ```python # File: dockflare/app/__init__.py @login_manager.request_loader def load_user_from_request(request): """Load user from request - bypass session auth for designated API endpoints.""" if request.path.startswith('/api/v2/auth/'): return None elif request.endpoint and request.endpoint.startswith('api_v2.'): # ✅ FIX: Check if endpoint is UI-only (should use session auth) from app.web.api_v2_routes import _UI_ENDPOINT_ALLOWLIST if request.endpoint in _UI_ENDPOINT_ALLOWLIST: # UI endpoints must use session-based auth via @login_required return None # API endpoints can use MASTER_API_KEY (handled by before_request) from app.core.user import User return User('api_user') return None ``` ### Part B: Defense-in-Depth (Add @login_required) Add `@login_required` decorators to all UI-intended endpoints: ```python # File: dockflare/app/web/api_v2_routes.py from flask_login import login_required @api_v2_bp.route('/idp/types', methods=['GET']) @login_required # ✅ ADD def api_get_idp_types(): # ... @api_v2_bp.route('/idp/list', methods=['GET']) @login_required # ✅ ADD def api_list_idps(): # ... @api_v2_bp.route('/idp/sync', methods=['POST']) @login_required # ✅ ADD def api_sync_idps(): # ... @api_v2_bp.route('/idp/create', methods=['POST']) @login_required # ✅ ADD def api_create_idp(): # ... @api_v2_bp.route('/idp/', methods=['GET']) @login_required # ✅ ADD def api_get_idp(friendly_name): # ... @api_v2_bp.route('/idp/', methods=['PUT']) @login_required # ✅ ADD def api_update_idp(friendly_name): # ... @api_v2_bp.route('/idp/', methods=['DELETE']) @login_required # ✅ ADD def api_delete_idp(friendly_name): # ... @api_v2_bp.route('/zone-policies', methods=['GET']) @login_required # ✅ ADD def get_zone_policies_api(): # ... # Also add to auth management endpoints (they're also in allowlist) @api_v2_bp.route('/auth/settings', methods=['GET', 'PUT']) @login_required # Already present - verify def manage_auth_settings(): # ... ``` --- ## Testing Strategy ### Pre-Fix Testing (Verify Vulnerability) 1. **With DISABLE_PASSWORD_LOGIN=True (Your Current Setup):** ```bash # Should succeed (vulnerable) curl -s http://localhost:5001/api/v2/idp/list ``` 2. **With DISABLE_PASSWORD_LOGIN=False:** ```bash # Set in Settings UI, then test curl -s http://localhost:5001/api/v2/idp/list # Should still succeed (still vulnerable due to request_loader) ``` ### Post-Fix Testing (Verify Fix) 1. **Test unauthenticated access is blocked:** ```bash # Should return 401 Unauthorized curl -s -b /dev/null http://localhost:5001/api/v2/idp/list ``` 2. **Test authenticated UI access still works:** ```bash # Login via browser # Open http://localhost:5001/access-policies # Should load IdP data via AJAX (uses session cookie) ``` 3. **Test MASTER_API_KEY still works for non-UI endpoints:** ```bash # Should succeed with Bearer token curl -s -H "Authorization: Bearer YOUR_MASTER_API_KEY" \ http://localhost:5001/api/v2/services ``` --- ## UI Compatibility Verification ### How UI Currently Calls IdP Endpoints **File:** `dockflare/app/templates/access_policies.html:504` ```javascript fetch('/api/v2/idp/list') .then(response => response.json()) .then(data => { // Render IdP list }); ``` **Current behavior (VULNERABLE):** - Browser makes fetch request WITHOUT credentials - `request_loader` auto-authenticates as `'api_user'` - Endpoint bypasses MASTER_API_KEY (in allowlist) - Returns data successfully **After fix (SECURE):** - Browser makes fetch request WITH session cookie (automatic in browser) - `request_loader` returns `None` (endpoint in allowlist) - `@login_required` checks session cookie - If valid session: returns data - If no session: returns 401 ### Will UI Break After Fix? **NO - It will work correctly:** 1. **User logs into DockFlare UI** (via password or OAuth) 2. **Session cookie is set** by Flask-Login 3. **Browser automatically includes cookie** in AJAX requests (same-origin) 4. **`@login_required` validates session** and allows access 5. **UI works as expected** ### Why It Works - ✅ **Same-Origin:** AJAX calls are from the same domain (cookies sent automatically) - ✅ **Session-Based:** Browser maintains session cookie after login - ✅ **No CORS Issues:** Not cross-origin, so credentials are included by default - ✅ **CSRF Token:** Already handled by Flask-WTF for POST/PUT/DELETE (if needed) --- ## Implementation Steps ### Step 1: Fix request_loader (Immediate) ```bash # Edit: dockflare/app/__init__.py # Modify load_user_from_request() function (lines 154-165) ``` ### Step 2: Add @login_required Decorators (Immediate) ```bash # Edit: dockflare/app/web/api_v2_routes.py # Add decorator to 8 endpoints (IdP + zone-policies) ``` ### Step 3: Test with DISABLE_PASSWORD_LOGIN=True (Verify Fix for Your Config) ```bash # Keep setting enabled # Restart DockFlare # Test unauthenticated curl (should fail) # Test UI access (should work via auto-login) ``` ### Step 4: Test with DISABLE_PASSWORD_LOGIN=False (Verify General Security) ```bash # Disable setting in UI # Restart DockFlare # Test unauthenticated curl (should fail) # Test UI after login (should work) ``` --- ## DISABLE_PASSWORD_LOGIN Use Cases ### ✅ SAFE Use Case - Behind External Auth Proxy **Scenario:** DockFlare is protected by Cloudflare Access or another authentication proxy **Setup:** 1. Cloudflare Access policy requires SSO login to reach DockFlare 2. Only authenticated users can reach `http://localhost:5001` 3. DockFlare sets `DISABLE_PASSWORD_LOGIN=True` 4. Users are auto-logged in as 'anonymous' (external auth already verified) **Security:** ✅ SAFE - External layer provides authentication ### ❌ UNSAFE Use Case - Direct Internet Exposure **Scenario:** DockFlare exposed directly without external authentication **Setup:** 1. DockFlare accessible at `https://dockflare.example.com` directly 2. `DISABLE_PASSWORD_LOGIN=True` enabled 3. No external authentication layer **Security:** 🔴 CRITICAL - Anyone can access without credentials --- ## Your Specific Situation **Your Config:** `DISABLE_PASSWORD_LOGIN=True` on `localhost:5001` **Question:** Is DockFlare behind an external authentication proxy? ### If NO (Direct Access): **Recommendation:** 1. **Disable this setting immediately** via Settings UI 2. Use password or OAuth login 3. Apply the code fixes above 4. Re-test security ### If YES (Behind Cloudflare Access or similar): **Recommendation:** 1. Apply the code fixes above 2. **Keep setting enabled** (intended behavior) 3. Ensure external auth layer is properly configured 4. Test that unauthenticated requests to external proxy are blocked --- ## Migration Plan for Existing Users ### Release Notes Warning ```markdown ## Security Fix - Authentication on IdP Endpoints v3.0.3 had a critical authentication bypass vulnerability. v3.0.4 fixes this issue. ### Breaking Change for DISABLE_PASSWORD_LOGIN Users If you have "Disable Password Login" enabled: - ✅ If DockFlare is behind an auth proxy (Cloudflare Access, etc.): No action needed - ⚠️ If DockFlare is directly accessible: Disable this setting immediately After upgrading to v3.0.4: 1. Verify you can still log into DockFlare UI 2. Test Access Policies page loads IdP data 3. Check browser console for 401 errors (indicates session issue) ``` --- ## Alternative: Remove Endpoints from Allowlist **Instead of adding `@login_required`, require MASTER_API_KEY:** ### Pros: - More secure (API key vs session cookie) - Better separation of UI and API concerns ### Cons: - **Breaks existing UI implementation** - Requires JavaScript changes to pass MASTER_API_KEY in AJAX headers - Exposes MASTER_API_KEY to browser (localStorage or hardcoded in JS) - Not recommended for UI-initiated calls ### Why We DON'T Recommend This: 1. **MASTER_API_KEY in Browser = Security Risk** - If stored in localStorage/sessionStorage, vulnerable to XSS - If embedded in HTML/JS, visible to all users - MASTER_API_KEY is meant for server-to-server API calls, not browser calls 2. **Session Cookies = Designed for Browser Auth** - HttpOnly flag prevents XSS access - SameSite prevents CSRF - Automatic expiration and renewal - Industry standard for browser-based authentication **Conclusion:** Use `@login_required` (session-based) for UI-initiated API calls. --- ## Recommended Fix (Final) ### Minimal Changes, Maximum Security: 1. **Fix `request_loader` in `__init__.py`** (10 lines changed) 2. **Add `@login_required` to 8 endpoints in `api_v2_routes.py`** (8 lines added) 3. **No JavaScript changes required** 4. **No database migrations required** 5. **Backwards compatible with existing UI** ### Total Code Changes: ~18 lines --- ## Next Steps 1. **Confirm:** Is your DockFlare instance behind an external auth proxy? 2. **Decision:** Should we proceed with the fix implementation? 3. **Testing:** Can you test on localhost:5001 before deploying? Let me know your answers and I'll implement the fixes.