12 KiB
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'):
# 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:
# 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:
# 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=Trueenabled - 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_requiredstill allows unauthenticated API access - Why: The
request_loaderauto-authenticates allapi_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:
# 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:
# 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/<friendly_name>', methods=['GET'])
@login_required # ✅ ADD
def api_get_idp(friendly_name):
# ...
@api_v2_bp.route('/idp/<friendly_name>', methods=['PUT'])
@login_required # ✅ ADD
def api_update_idp(friendly_name):
# ...
@api_v2_bp.route('/idp/<friendly_name>', 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)
- With DISABLE_PASSWORD_LOGIN=True (Your Current Setup):
# Should succeed (vulnerable)
curl -s http://localhost:5001/api/v2/idp/list
- With DISABLE_PASSWORD_LOGIN=False:
# 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)
- Test unauthenticated access is blocked:
# Should return 401 Unauthorized
curl -s -b /dev/null http://localhost:5001/api/v2/idp/list
- Test authenticated UI access still works:
# Login via browser
# Open http://localhost:5001/access-policies
# Should load IdP data via AJAX (uses session cookie)
- Test MASTER_API_KEY still works for non-UI endpoints:
# 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
fetch('/api/v2/idp/list')
.then(response => response.json())
.then(data => {
// Render IdP list
});
Current behavior (VULNERABLE):
- Browser makes fetch request WITHOUT credentials
request_loaderauto-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_loaderreturnsNone(endpoint in allowlist)@login_requiredchecks session cookie- If valid session: returns data
- If no session: returns 401
Will UI Break After Fix?
NO - It will work correctly:
- User logs into DockFlare UI (via password or OAuth)
- Session cookie is set by Flask-Login
- Browser automatically includes cookie in AJAX requests (same-origin)
@login_requiredvalidates session and allows access- 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)
# Edit: dockflare/app/__init__.py
# Modify load_user_from_request() function (lines 154-165)
Step 2: Add @login_required Decorators (Immediate)
# 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)
# 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)
# 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:
- Cloudflare Access policy requires SSO login to reach DockFlare
- Only authenticated users can reach
http://localhost:5001 - DockFlare sets
DISABLE_PASSWORD_LOGIN=True - 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:
- DockFlare accessible at
https://dockflare.example.comdirectly DISABLE_PASSWORD_LOGIN=Trueenabled- 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:
- Disable this setting immediately via Settings UI
- Use password or OAuth login
- Apply the code fixes above
- Re-test security
If YES (Behind Cloudflare Access or similar):
Recommendation:
- Apply the code fixes above
- Keep setting enabled (intended behavior)
- Ensure external auth layer is properly configured
- Test that unauthenticated requests to external proxy are blocked
Migration Plan for Existing Users
Release Notes Warning
## 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:
-
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
-
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:
- Fix
request_loaderin__init__.py(10 lines changed) - Add
@login_requiredto 8 endpoints inapi_v2_routes.py(8 lines added) - No JavaScript changes required
- No database migrations required
- Backwards compatible with existing UI
Total Code Changes: ~18 lines
Next Steps
- Confirm: Is your DockFlare instance behind an external auth proxy?
- Decision: Should we proceed with the fix implementation?
- Testing: Can you test on localhost:5001 before deploying?
Let me know your answers and I'll implement the fixes.