Files
BanGUI/Docs/Security.md
Lukas dafe8d61e2 feat(security): add CSRF header constants and security-headers endpoint
Move X-BanGUI-Request header name/value to backend/app/utils/constants.py as single source of truth. Add GET /api/v1/config/security-headers endpoint. Update csrf middleware, frontend api client, and docs to use shared constants.
2026-05-03 22:06:43 +02:00

7.3 KiB

Security — Guidelines and Implementation

Security considerations and implementation details for BanGUI.


HTTP Security Headers

BanGUI implements defense-in-depth against client-side attacks by sending security-related HTTP response headers on all responses.

Headers Implemented

Header Value Purpose
Content-Security-Policy default-src 'self' Prevents XSS attacks by restricting script, style, font, image, and other resource origins to self only. Browsers refuse to load resources from other origins.
X-Frame-Options DENY Prevents clickjacking attacks by forbidding the page from being embedded in <iframe> tags on any origin.
X-Content-Type-Options nosniff Prevents MIME-type sniffing attacks by forcing browsers to respect the declared Content-Type. Blocks execution of misidentified scripts.
X-XSS-Protection 1; mode=block Enables browser XSS filters (legacy header for older browsers). Modern browsers prioritize CSP.

Implementation

Backend: The SecurityHeadersMiddleware in backend/app/main.py adds these headers to every HTTP response, including error responses and non-API routes.

response.headers["Content-Security-Policy"] = "default-src 'self'"
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-XSS-Protection"] = "1; mode=block"

Frontend: The <meta http-equiv="Content-Security-Policy" content="default-src 'self'" /> tag in frontend/index.html provides an additional defense layer in case the backend headers are ever stripped (e.g., by a proxy).

CSP Policy Details

The current policy default-src 'self' means:

  • Allowed: Inline scripts, stylesheets, fonts, images, and other resources from the same origin (self)
  • Blocked: Resources from external domains, inline event handlers, eval(), and setTimeout(string)

Why no 'unsafe-inline'?

  • 'unsafe-inline' defeats CSP's primary purpose (XSS prevention) by allowing arbitrarily-embedded scripts
  • All scripts and styles must be in separate files (never inline), which is best practice anyway
  • The frontend build system (Vite) automatically handles asset bundling and file separation

If external CDN resources are needed:

  1. Explicitly add the CDN origin to the CSP policy, e.g.: default-src 'self' https://cdn.example.com
  2. Document the CDN addition with a justification comment
  3. Ensure the CDN certificate chain is valid and trusted
  4. Consider using Subresource Integrity (SRI) to verify resource authenticity

Verification

To verify headers are being sent correctly:

  1. Chrome DevTools:

    • Open DevTools (F12)
    • Go to Network tab
    • Reload the page
    • Click on any request and open the Response Headers section
    • Look for Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection
  2. Command line (curl):

    curl -I http://localhost:8000/
    curl -I http://localhost:5173/
    
  3. Online tools:

Future Improvements

  • Stricter CSP: If functionality allows, tighten to default-src 'none' and explicitly allow individual resources
  • SRI (Subresource Integrity): Add integrity attributes to external script/style tags to prevent tampering
  • Preload headers: Use Link: <...>; rel=preload to optimize critical resource delivery
  • HSTS: Consider adding Strict-Transport-Security for production deployments to force HTTPS

CSRF Protection

BanGUI protects cookie-authenticated state-mutating requests (POST, PUT, DELETE, PATCH) with a custom header check. Requests using the session cookie must include the header X-BanGUI-Request: 1. Bearer token authentication is exempt since tokens in headers are not CSRF-vulnerable.

Single Source of Truth

The header name and value are defined once in backend/app/utils/constants.py (CSRF_HEADER_NAME and CSRF_HEADER_VALUE) and consumed by:

  • backend/app/middleware/csrf.py — validates the header on incoming requests
  • frontend/src/api/client.ts — attaches the header to state-mutating fetch calls
  • frontend/src/utils/constants.ts — mirrors the values for type-safe import

Endpoint

GET /api/v1/config/security-headers — returns the CSRF header name and value to authenticated clients:

{
  "csrf_header_name": "X-BanGUI-Request",
  "csrf_header_value": "1"
}

This allows the frontend to discover the required header at runtime. Both frontend and backend constants must remain in sync — a build-time check is recommended when updating either constant.

Header Rationale

The custom header is required because browsers block cross-site requests from setting custom headers without a CORS preflight, which BanGUI rejects for non-allowed origins.


Session Security

See backend/app/middleware/csrf.py and backend/app/middleware/rate_limit.py for CSRF protection and rate limiting.


Password Security

  • Passwords are hashed with SHA256 on the frontend before transmission
  • The backend never stores plain-text passwords
  • See backend/app/services/auth.py for authentication implementation
  • Common password prevention: The setup validator rejects a list of ~75 common plaintext passwords that pass structural complexity checks (e.g., Password1!). The list is embedded in backend/app/models/setup.py and is checked case-insensitively.

Database Security

  • The SQLite database contains no sensitive data (no passwords, API keys, or tokens stored)
  • Database queries use parameterized statements to prevent SQL injection
  • See backend/app/repositories/ for data access patterns

Regex (ReDoS) Protection

BanGUI validates all user-supplied regex patterns before they are compiled or stored.

How It Works

  1. Static analysis via regexploit detects catastrophic backtracking patterns before compilation
  2. Timeout enforcement stops compilation if it exceeds 2 seconds (prevents hanging on pathological patterns)
  3. Length limit (1000 characters) prevents memory exhaustion via bloated patterns

Protected Endpoints

All endpoints that accept regex patterns validate them:

  • Filter configuration (prefregex, failregex, ignorregex)
  • Action configuration (any regex used in actions)
  • Direct config editing

ReDoS Pattern Examples

Patterns with nested quantifiers on overlapping text are blocked:

Pattern Why Blocked
(a+)+b Plus inside plus — exponential backtracking
([a-z]+)*d Quantifier inside quantifier
(x+)+y Nested quantifiers
a[bcd]*e[bcd]*e Multiple unbounded quantifiers

Legitimate Complex Patterns

Not all complex patterns are blocked. Email and IP validation patterns typically pass:

r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"  # OK
r"^(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"         # OK

If Your Pattern Is Rejected

  1. Rewrite to avoid nested quantifiers on the same text
  2. Use atomic groups or possessive quantifiers: (?>a+)+b instead of (a+)+b
  3. Test locally with Python's re module before deploying
  4. If you believe the pattern is safe, check with regexploit directly