Merge pull request #19 from christianlouis/fix-imap-credentials-logging-vulnerability-5693644702815765698

🔒 [security] Move IMAP credentials to request body
This commit is contained in:
Christian Krakau-Louis
2026-03-29 12:07:52 +02:00
committed by GitHub
11 changed files with 29 additions and 25 deletions
View File
View File
View File
+25 -21
View File
@@ -1,39 +1,43 @@
import logging import logging
from datetime import datetime from datetime import datetime
from typing import Any, Dict from typing import Any, Dict, Optional
from app.core.security import require_admin_auth from app.core.security import require_admin_auth
from app.services.imap_client import IMAPClient from app.services.imap_client import IMAPClient
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException
from pydantic import BaseModel
router = APIRouter() router = APIRouter()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class IMAPTestRequest(BaseModel):
"""IMAP connection test request body"""
server: Optional[str] = None
port: int = 993
username: Optional[str] = None
password: Optional[str] = None
ssl: bool = True
@router.post("/test-connection") @router.post("/test-connection")
async def test_imap_connection( async def test_imap_connection(
auth: dict = Depends(require_admin_auth), request: IMAPTestRequest,
server: str = None, _auth: dict = Depends(require_admin_auth),
port: int = 993,
username: str = None,
password: str = None,
ssl: bool = True,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
""" """
Test connection to an IMAP server and gather mailbox statistics Test connection to an IMAP server and gather mailbox statistics
Security: Requires authentication (X-API-Key or Bearer token) Security: Requires authentication (X-API-Key or Bearer token)
Note: Credentials should be passed in request body, not query params Credentials should be passed in request body
""" """
# Security: Don't accept credentials in query parameters (they get logged) imap_client = IMAPClient(
if any([server, username, password]): server=request.server,
logger.warning("IMAP credentials passed as query parameters - this is insecure") port=request.port,
raise HTTPException( username=request.username,
status_code=400, password=request.password,
detail="Credentials should be passed in request body, not query parameters", )
)
imap_client = IMAPClient(server=server, port=port, username=username, password=password)
success, message, stats = imap_client.test_connection() success, message, stats = imap_client.test_connection()
@@ -51,7 +55,7 @@ async def test_imap_connection(
@router.post("/fetch-reports") @router.post("/fetch-reports")
async def fetch_imap_reports( async def fetch_imap_reports(
background_tasks: BackgroundTasks, background_tasks: BackgroundTasks,
auth: dict = Depends(require_admin_auth), _auth: dict = Depends(require_admin_auth),
days: int = 7, days: int = 7,
delete_emails: bool = False, delete_emails: bool = False,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
@@ -88,14 +92,14 @@ async def fetch_imap_reports(
"timestamp": datetime.now().isoformat(), "timestamp": datetime.now().isoformat(),
} }
except Exception as e: except Exception as e:
logger.error(f"Error fetching IMAP reports: {str(e)}") logger.error("Error fetching IMAP reports: %s", str(e))
raise HTTPException( raise HTTPException(
status_code=500, detail="Failed to fetch reports. Check server logs for details." status_code=500, detail="Failed to fetch reports. Check server logs for details."
) ) from e
@router.get("/status") @router.get("/status")
async def get_imap_status(auth: dict = Depends(require_admin_auth)) -> Dict[str, Any]: async def get_imap_status(_auth: dict = Depends(require_admin_auth)) -> Dict[str, Any]:
""" """
Get the current status of IMAP polling background processes Get the current status of IMAP polling background processes
+2 -2
View File
@@ -1,4 +1,3 @@
from fastapi import APIRouter, HTTPException, status from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel, EmailStr from pydantic import BaseModel, EmailStr
@@ -38,7 +37,8 @@ class SystemConfigRequest(BaseModel):
async def get_setup_status(): async def get_setup_status():
"""Get the current setup status""" """Get the current setup status"""
return SetupStatusResponse( return SetupStatusResponse(
is_setup_complete=setup_status["is_setup_complete"], app_name=setup_status["app_name"] is_setup_complete=setup_status["is_setup_complete"],
app_name=setup_status["app_name"],
) )
View File
View File
+2 -2
View File
@@ -1,8 +1,8 @@
from app.core.database import Base
from sqlalchemy import Boolean, Column, Integer, String from sqlalchemy import Boolean, Column, Integer, String
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from app.core.database import Base
class User(Base): class User(Base):
"""User model""" """User model"""
View File
View File