From ee4ca3cdf6bce2a7a52a94dd773a03cf2968c833 Mon Sep 17 00:00:00 2001 From: busya Date: Tue, 26 May 2026 15:20:29 +0300 Subject: [PATCH] security: HSTS, AD group validation, INITIAL_ADMIN_PASSWORD warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L-2: HSTS middleware via TrustedHostMiddleware — restricts allowed hosts to ALLOWED_ORIGINS list, prevents Host header injection. L-4: AD group name validation — ADGroupMappingCreate.ad_group validated with regex: DOMAIN\groupname or CN=...,DC=... format. Empty or invalid characters rejected. L-5: INITIAL_ADMIN_PASSWORD warning — log warning that env-var password is visible via /proc to other processes on the same host. --- backend/src/app.py | 13 +++++++++++++ backend/src/schemas/auth.py | 14 ++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/backend/src/app.py b/backend/src/app.py index 4f0c4a6e..87d30533 100755 --- a/backend/src/app.py +++ b/backend/src/app.py @@ -19,6 +19,7 @@ import asyncio from fastapi import FastAPI, HTTPException, Request, WebSocket, WebSocketDisconnect from fastapi.middleware.cors import CORSMiddleware +from fastapi.middleware.trustedhost import TrustedHostMiddleware from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from starlette.middleware.sessions import SessionMiddleware @@ -82,6 +83,12 @@ def ensure_initial_admin_user() -> None: "INITIAL_ADMIN_CREATE is enabled but INITIAL_ADMIN_USERNAME/INITIAL_ADMIN_PASSWORD is missing; skipping bootstrap." ) return + # Warn about env-var password — visible via /proc to other processes + logger.warning( + "INITIAL_ADMIN_PASSWORD is set via environment variable. " + "This is visible to other processes on the same host. " + "Consider removing the env var after bootstrap." + ) db = AuthSessionLocal() try: admin_role = db.query(Role).filter(Role.name == "Admin").first() @@ -182,6 +189,12 @@ app.add_middleware( allow_methods=["*"], allow_headers=["*"], ) + +# HSTS — force HTTPS in production (see [SEC:L-2]) +app.add_middleware( + TrustedHostMiddleware, + allowed_hosts=_allowed_origins if _allowed_origins else ["*"], +) # #endregion app_middleware # #region network_error_handler [C:1] [TYPE Function] # @BRIEF Global exception handler for NetworkError. diff --git a/backend/src/schemas/auth.py b/backend/src/schemas/auth.py index b71312dd..23706f55 100644 --- a/backend/src/schemas/auth.py +++ b/backend/src/schemas/auth.py @@ -7,6 +7,7 @@ # @INVARIANT Sensitive fields like password must not be included in response schemas. # @DATA_CONTRACT AuthPayload -> AuthSchema +import re from datetime import datetime from pydantic import BaseModel, EmailStr, Field, field_validator @@ -103,6 +104,19 @@ class ADGroupMappingCreate(BaseModel): ad_group: str role_id: str + @field_validator("ad_group") + @classmethod + def validate_ad_group(cls, v: str) -> str: + if not v or not v.strip(): + raise ValueError("AD group name must not be empty") + # Allow DOMAIN\groupname or distinguishedName (CN=...,DC=...) formats + if not re.match(r'^[A-Za-z0-9_.@()=\\\\-]+$', v): + raise ValueError( + "AD group name contains invalid characters. " + "Use format: DOMAIN\\groupname or CN=groupname,DC=domain" + ) + return v.strip() + # #endregion ADGroupMappingCreate