From dbfc8a2a328b6742fe9b9dd59ce0c42046c80c05 Mon Sep 17 00:00:00 2001 From: Pouzor Date: Mon, 9 Mar 2026 00:05:10 +0100 Subject: [PATCH] security: fix C3, C1 and H1 C3 - config.yml contains credentials, remove from git tracking: - Add backend/config.yml to .gitignore - git rm --cached to untrack it - Add backend/config.yml.example with instructions C1 - SECRET_KEY must come from .env, no unsafe default: - Remove hardcoded "change_me_in_production" default from config.py - App now fails to start if SECRET_KEY is not set (pydantic required field) - Generate real random key in backend/.env (gitignored) - Add backend/.env.example for new contributors H1 - WebSocket /ws/status was unauthenticated: - Backend: require ?token= query param, validate via decode_token(), close with code 1008 (Policy Violation) if missing or invalid - Frontend: append ?token= to WebSocket URL --- .gitignore | 3 +++ backend/.env.example | 4 ++++ backend/app/api/routes/status.py | 7 ++++++- backend/app/core/config.py | 2 +- backend/config.yml | 9 --------- backend/config.yml.example | 9 +++++++++ frontend/src/hooks/useStatusPolling.ts | 2 +- 7 files changed, 24 insertions(+), 12 deletions(-) create mode 100644 backend/.env.example delete mode 100644 backend/config.yml create mode 100644 backend/config.yml.example diff --git a/.gitignore b/.gitignore index 3856de0..4b69641 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,9 @@ build/ htmlcov/ .coverage +# App config — contains credentials, never commit +backend/config.yml + # SQLite *.db *.db-shm diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..14c8692 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,4 @@ +SECRET_KEY= +SQLITE_PATH=./data/homelab.db +CONFIG_PATH=./config.yml +CORS_ORIGINS=["http://localhost:5173","http://localhost:3000"] diff --git a/backend/app/api/routes/status.py b/backend/app/api/routes/status.py index d2f4894..106af8c 100644 --- a/backend/app/api/routes/status.py +++ b/backend/app/api/routes/status.py @@ -2,6 +2,8 @@ import json from fastapi import APIRouter, WebSocket, WebSocketDisconnect +from app.core.security import decode_token + router = APIRouter() # Active WebSocket connections @@ -9,7 +11,10 @@ _connections: list[WebSocket] = [] @router.websocket("/ws/status") -async def ws_status(websocket: WebSocket) -> None: +async def ws_status(websocket: WebSocket, token: str | None = None) -> None: + if not token or not decode_token(token): + await websocket.close(code=1008) # Policy Violation + return await websocket.accept() _connections.append(websocket) try: diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 96f40bc..1c8f4b3 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -4,7 +4,7 @@ from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8") - secret_key: str = "change_me_in_production" + secret_key: str # Required — set SECRET_KEY in .env sqlite_path: str = "./data/homelab.db" config_path: str = "./config.yml" cors_origins: list[str] = ["http://localhost:5173", "http://localhost:3000"] diff --git a/backend/config.yml b/backend/config.yml deleted file mode 100644 index c902bc2..0000000 --- a/backend/config.yml +++ /dev/null @@ -1,9 +0,0 @@ -auth: - password_hash: $2b$12$o/LWyvmBc978CNpSsHxcveXN0WqjAGW/gBR0.U.HURWbaYD3GCDqS - username: admin -scanner: - interval: null - ranges: - - 192.168.1.0/24 -status_checker: - interval_seconds: 60 diff --git a/backend/config.yml.example b/backend/config.yml.example new file mode 100644 index 0000000..25594a5 --- /dev/null +++ b/backend/config.yml.example @@ -0,0 +1,9 @@ +auth: + username: admin + password_hash: "" # Generate with: python -c "from passlib.context import CryptContext; print(CryptContext(schemes=['bcrypt']).hash('yourpassword'))" +scanner: + interval: null + ranges: + - 192.168.1.0/24 +status_checker: + interval_seconds: 60 diff --git a/frontend/src/hooks/useStatusPolling.ts b/frontend/src/hooks/useStatusPolling.ts index c7405b3..a39103b 100644 --- a/frontend/src/hooks/useStatusPolling.ts +++ b/frontend/src/hooks/useStatusPolling.ts @@ -22,7 +22,7 @@ export function useStatusPolling() { const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws' const host = window.location.hostname - const url = `${protocol}://${host}:8000/api/v1/status/ws/status` + const url = `${protocol}://${host}:8000/api/v1/status/ws/status?token=${encodeURIComponent(token)}` const ws = new WebSocket(url) wsRef.current = ws