Skip to content

security

security

Security middleware and utilities for Marianne Dashboard.

Provides security headers and input validation.

Classes

SecurityConfig dataclass

SecurityConfig(cors_origins=(lambda: ['http://localhost:8080', 'http://127.0.0.1:8080'])(), cors_allow_credentials=True, cors_allow_methods=(lambda: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])(), cors_allow_headers=(lambda: ['Accept', 'Authorization', 'Content-Type', 'X-Requested-With'])(), add_security_headers=True, content_security_policy="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.tailwindcss.com https://cdn.jsdelivr.net https://unpkg.com; style-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com https://cdn.jsdelivr.net https://unpkg.com; font-src 'self' https://cdn.jsdelivr.net; img-src 'self' data:; connect-src 'self'", strict_transport_security='max-age=31536000; includeSubDomains', x_content_type_options='nosniff', x_frame_options='SAMEORIGIN', x_xss_protection='1; mode=block', referrer_policy='strict-origin-when-cross-origin')

Security configuration.

Attributes:

Name Type Description
cors_origins list[str]

Allowed CORS origins

cors_allow_credentials bool

Allow credentials in CORS

cors_allow_methods list[str]

Allowed HTTP methods

cors_allow_headers list[str]

Allowed headers

add_security_headers bool

Add security headers to responses

content_security_policy str

CSP header value

strict_transport_security str

HSTS header value

x_content_type_options str

X-Content-Type-Options header

x_frame_options str

X-Frame-Options header

x_xss_protection str

X-XSS-Protection header

referrer_policy str

Referrer-Policy header

Functions
from_env classmethod
from_env()

Create config from environment variables.

Environment variables

MZT_CORS_ORIGINS: Comma-separated origins MZT_CORS_CREDENTIALS: true/false

Source code in src/marianne/dashboard/auth/security.py
@classmethod
def from_env(cls) -> SecurityConfig:
    """Create config from environment variables.

    Environment variables:
        MZT_CORS_ORIGINS: Comma-separated origins
        MZT_CORS_CREDENTIALS: true/false
    """
    origins_str = os.getenv("MZT_CORS_ORIGINS", "http://localhost:8080,http://127.0.0.1:8080")
    origins = [o.strip() for o in origins_str.split(",") if o.strip()]

    credentials = os.getenv("MZT_CORS_CREDENTIALS", "true").lower() == "true"

    return cls(
        cors_origins=origins,
        cors_allow_credentials=credentials,
    )

SecurityHeadersMiddleware

SecurityHeadersMiddleware(app, config=None)

Bases: BaseHTTPMiddleware

Middleware to add security headers to all responses.

Initialize middleware.

Parameters:

Name Type Description Default
app ASGIApp

ASGI application (FastAPI or Starlette app)

required
config SecurityConfig | None

Security configuration

None
Source code in src/marianne/dashboard/auth/security.py
def __init__(self, app: ASGIApp, config: SecurityConfig | None = None):
    """Initialize middleware.

    Args:
        app: ASGI application (FastAPI or Starlette app)
        config: Security configuration
    """
    super().__init__(app)
    self.config = config or SecurityConfig()
Functions
dispatch async
dispatch(request, call_next)

Add security headers to response.

Parameters:

Name Type Description Default
request Request

Incoming request

required
call_next RequestResponseEndpoint

Next middleware/handler

required

Returns:

Type Description
Response

Response with security headers

Source code in src/marianne/dashboard/auth/security.py
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
    """Add security headers to response.

    Args:
        request: Incoming request
        call_next: Next middleware/handler

    Returns:
        Response with security headers
    """
    response = await call_next(request)

    if self.config.add_security_headers:
        content_type = response.headers.get("content-type", "")
        is_html = "text/html" in content_type

        # Always add non-CSP security headers
        response.headers["X-Content-Type-Options"] = self.config.x_content_type_options
        response.headers["X-Frame-Options"] = self.config.x_frame_options
        response.headers["Referrer-Policy"] = self.config.referrer_policy

        # Remove potentially dangerous headers
        if "Server" in response.headers:
            del response.headers["Server"]

        # CSP and HSTS only on HTML responses
        if is_html:
            response.headers["Content-Security-Policy"] = self.config.content_security_policy
            response.headers["Strict-Transport-Security"] = (
                self.config.strict_transport_security
            )
            response.headers["X-XSS-Protection"] = self.config.x_xss_protection

    return response

Functions

validate_job_id

validate_job_id(job_id)

Validate job ID format to prevent injection.

Parameters:

Name Type Description Default
job_id str

Job identifier to validate

required

Returns:

Type Description
bool

True if valid format

Source code in src/marianne/dashboard/auth/security.py
def validate_job_id(job_id: str) -> bool:
    """Validate job ID format to prevent injection.

    Args:
        job_id: Job identifier to validate

    Returns:
        True if valid format
    """
    if not job_id or len(job_id) > MAX_JOB_ID_LENGTH:
        return False

    # Allow alphanumeric, hyphen, underscore, period
    allowed_chars = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.")
    return all(c in allowed_chars for c in job_id)

validate_path_component

validate_path_component(component)

Validate path component to prevent traversal attacks.

Parameters:

Name Type Description Default
component str

Path component to validate

required

Returns:

Type Description
bool

True if safe

Source code in src/marianne/dashboard/auth/security.py
def validate_path_component(component: str) -> bool:
    """Validate path component to prevent traversal attacks.

    Args:
        component: Path component to validate

    Returns:
        True if safe
    """
    if not component:
        return False

    # Block directory traversal
    if ".." in component or component.startswith("/"):
        return False

    # Block null bytes
    return "\x00" not in component

sanitize_filename

sanitize_filename(filename)

Sanitize filename for safe use.

Parameters:

Name Type Description Default
filename str

Original filename

required

Returns:

Type Description
str

Sanitized filename

Source code in src/marianne/dashboard/auth/security.py
def sanitize_filename(filename: str) -> str:
    """Sanitize filename for safe use.

    Args:
        filename: Original filename

    Returns:
        Sanitized filename
    """
    # Remove path components
    filename = filename.replace("/", "_").replace("\\", "_")

    # Remove null bytes
    filename = filename.replace("\x00", "")

    # Limit length
    if len(filename) > MAX_FILENAME_LENGTH:
        filename = filename[:MAX_FILENAME_LENGTH]

    # Ensure not empty
    if not filename:
        filename = "unnamed"

    return filename