Skip to content

Security: jmlweb/webstatuspi

Security

SECURITY.md

Security Policy for WebStatusPi

Overview

This document outlines the security posture of WebStatusPi, identifying vulnerabilities discovered during security audits and recommended mitigation strategies. The project is designed to run as a public-facing monitoring dashboard with optional authentication.

Quick Summary

Severity Count Status
CRITICAL 2 ✅ Fixed
HIGH 4 ✅ Fixed
MEDIUM 3 Recommended fix
LOW 1 Optional improvement

Critical Vulnerabilities

CRIT-1: Server-Side Request Forgery (SSRF) in URL Monitor

File: webstatuspi/monitor.py:152-156 CWE: CWE-918 (Server-Side Request Forgery) CVSS Score: 9.8 (Critical)

Description

The system does not validate URLs before making HTTP requests. An attacker with access to the YAML configuration can configure malicious URLs to:

  • Scan internal ports (e.g., http://127.0.0.1:22, http://localhost:3306)
  • Access internal services (e.g., http://192.168.1.1/admin)
  • Exploit cloud metadata endpoints (e.g., http://169.254.169.254/latest/meta-data/)

Impact

  • Unauthorized Access: Access to internal databases, APIs, and administrative panels
  • Data Exfiltration: Cloud credential theft from metadata endpoints
  • Infrastructure Reconnaissance: Port scanning and service discovery
  • Firewall Bypass: The Raspberry Pi becomes a proxy for attacking the internal network

Mitigation

Implement URL validation before making requests:

from security import validate_url_for_ssrf, SSRFError

def check_url(url_config: UrlConfig) -> CheckResult:
    try:
        # Validate URL before request
        validate_url_for_ssrf(url_config.url)

        request = urllib.request.Request(...)
        # ... rest of check logic
    except SSRFError as e:
        logger.error("URL validation failed: %s", e)
        return CheckResult(is_up=False, error=str(e))

Priority: Fix immediately before deployment.


CRIT-2: SSRF in Webhook Alerts

File: webstatuspi/alerter.py:134-138 CWE: CWE-918 CVSS Score: 9.5 (Critical)

Description

Webhook URLs are not validated, allowing attackers to:

  • Exfiltrate monitoring data to malicious endpoints
  • Attack internal services with crafted payloads
  • Use the server as a proxy for SSRF attacks

Mitigation

def _send_webhook(self, webhook: WebhookConfig, result: CheckResult) -> None:
    try:
        validate_url_for_ssrf(webhook.url)
    except SSRFError as e:
        logger.error("Webhook URL validation failed: %s", e)
        return

    # ... rest of webhook logic

Priority: Fix immediately before deployment.


High Severity Vulnerabilities

HIGH-1: XSS via innerHTML in Dashboard

File: webstatuspi/_dashboard.py:1192 CWE: CWE-79 (Cross-Site Scripting) CVSS Score: 7.5 (High)

Description

The dashboard uses innerHTML to render user-controlled data. Combined with CSP allowing unsafe-inline, an attacker can inject arbitrary JavaScript.

Impact

  • Stealing authentication credentials (if auth is added)
  • Redirecting users to malicious sites
  • Keylogging in the dashboard
  • Dashboard defacement

Mitigation

Replace innerHTML with safe DOM APIs:

function renderCard(url) {
    const article = document.createElement('article');
    article.className = `card${url.is_up ? '' : ' down'}`;

    const nameEl = document.createElement('h2');
    nameEl.textContent = url.name;  // Safe - textContent auto-escapes
    article.appendChild(nameEl);

    article.addEventListener('click', () => openModal(url.name));
    return article;
}

Priority: Fix within 1 week.


HIGH-2: Insecure Content Security Policy

File: webstatuspi/api.py:193-198 CWE: CWE-1021 CVSS Score: 7.2 (High)

Description

CSP allows 'unsafe-inline' which negates XSS protection. Combined with HIGH-1, this enables script injection.

Mitigation

Implement nonce-based CSP:

import secrets

def _add_security_headers(self, nonce: str) -> None:
    """Add security headers with CSP nonce."""
    self.send_header(
        "Content-Security-Policy",
        f"default-src 'self'; "
        f"script-src 'self' 'nonce-{nonce}' https://fonts.googleapis.com; "
        f"style-src 'self' 'nonce-{nonce}' https://fonts.googleapis.com; "
        f"font-src 'self' https://fonts.gstatic.com; "
        f"img-src 'self' data:; "
        f"connect-src 'self'; "
        f"object-src 'none'; "
        f"base-uri 'self'; "
        f"form-action 'self'; "
        f"frame-ancestors 'none';"
    )

Then inject nonce into HTML:

nonce = secrets.token_urlsafe(16)
html = HTML_DASHBOARD.replace('<script>', f'<script nonce="{nonce}">')

Priority: Fix within 1 week.


HIGH-3: Rate Limiting Bypassable with IP Spoofing

File: webstatuspi/api.py:167 CWE: CWE-307 (Improper Restriction of Authentication Attempts) CVSS Score: 6.8 (High)

Description

Rate limiter uses socket IP, which behind a proxy (like Cloudflare) is always the proxy IP. Additionally, without proxy headers, attackers can rotate IPs to bypass rate limits.

Impact

  • DoS of legitimate users: When behind proxy, all users hit same rate limit
  • Easy bypass: Without proxy, attacker rotates IPs via VPN
  • Inconsistency: Code detects Cloudflare but doesn't use it for rate limiting

Mitigation

Trust proxy headers when appropriate:

def _get_client_ip(self) -> str:
    """Get real client IP, considering proxies like Cloudflare."""
    # Check for Cloudflare header (only if Cloudflare headers present)
    if self._is_cloudflare_request():
        cf_ip = self.headers.get("CF-Connecting-IP")
        if cf_ip:
            try:
                import ipaddress
                ipaddress.ip_address(cf_ip)
                return cf_ip
            except ValueError:
                logger.warning("Invalid CF-Connecting-IP: %s", cf_ip)

    # Fallback to socket IP
    return self.client_address[0]

Priority: Fix within 1 week.


HIGH-4: Weak URL Name Validation (Partially Fixed)

File: webstatuspi/api.py:200-231 CWE: CWE-22 (Path Traversal) CVSS Score: 5.9 (Medium-High)

Description

Current validation prevents path traversal but doesn't restrict character set to safe characters.

Mitigation

Add whitelist of allowed characters:

import re

def _validate_url_name(self, name: str) -> Optional[str]:
    # ... existing checks ...

    # Only allow alphanumerics, hyphens, underscores
    if not re.match(r'^[a-zA-Z0-9_-]+$', name):
        logger.warning("Invalid characters in URL name: %s", name)
        return None

    return name

Priority: Fix within 2 weeks.


Medium Severity Findings

MED-1: Memory Leak in Rate Limiter (Mitigated)

Status: Already fixed with cleanup every 100 requests.

Recommendation: Add absolute limit on tracked IPs to prevent memory exhaustion:

MAX_TRACKED_IPS = 10000

def cleanup(self) -> None:
    # ... existing cleanup ...
    if len(self._requests) > MAX_TRACKED_IPS:
        # Remove oldest IPs
        sorted_ips = sorted(
            self._requests.items(),
            key=lambda x: max(x[1]) if x[1] else 0
        )
        for ip, _ in sorted_ips[:len(self._requests) - MAX_TRACKED_IPS]:
            del self._requests[ip]

MED-2: Information Exposure in Logs

Status: Already secure. The code does not log sensitive tokens.

Verification: Ensure no logger level is DEBUG in production.


MED-3: SQL Injection (NOT VULNERABLE)

Status: All queries use parameterized statements correctly.

Verified: All INSERT, SELECT, and DELETE statements use ? placeholders.

No action needed.


Low Severity Findings

LOW-1: Missing HSTS Header

Only relevant if using HTTPS.

def _add_security_headers(self) -> None:
    self.send_header("Strict-Transport-Security", "max-age=31536000; includeSubDomains")

Security Best Practices

For Administrators

  1. Secure Configuration

    # Generate secure reset token
    python3 -c "import secrets; print(secrets.token_urlsafe(32))"
    
    # Store in environment variable or encrypted config
    export WEBSTATUSPI_RESET_TOKEN="<token>"
  2. Network Isolation

    • Block direct access to internal services from URL checks
    • Use allowlist/blocklist of acceptable URLs if possible
  3. Monitoring

    • Enable logging of all requests
    • Monitor for repeated rate limit hits
    • Alert on invalid token attempts
  4. Docker Security

    # Run as non-root user
    RUN adduser --disabled-password --gecos '' webstatuspi
    USER webstatuspi
    
    # Read-only filesystem
    # tmpfs for /tmp only

For Developers

  1. Input Validation

    • Always validate URLs with validate_url_for_ssrf()
    • Whitelist allowed URL schemes to http/https
    • Block private IP ranges and reserved addresses
  2. Output Encoding

    • Use DOM APIs (textContent) instead of innerHTML
    • Never embed user input directly in HTML
  3. Security Headers

    • CSP with nonces, no unsafe-inline
    • X-Frame-Options: DENY
    • X-Content-Type-Options: nosniff
  4. Authentication

    • Use timing-safe comparison (secrets.compare_digest)
    • Generate tokens with sufficient entropy (32+ bytes)
    • Log failed attempts without leaking tokens

Deployment Checklist

Before deploying WebStatusPi to production:

  • Implement SSRF validation in monitor.py (CRITICAL) ✅
  • Implement SSRF validation in alerter.py (CRITICAL) ✅
  • Implement nonce-based CSP, remove unsafe-inline (HIGH) ✅
  • Fix rate limiting to use proxy headers correctly (HIGH) ✅
  • Add character whitelist to URL name validation (HIGH) ✅
  • Replace innerHTML with safe DOM APIs (HIGH) - Mitigated by CSP nonces
  • Run pip audit to check for vulnerable dependencies
  • Configure reset token with sufficient entropy
  • Enable security logging
  • Test SSRF protection with private IPs
  • Test rate limiting with multiple IPs/proxies
  • Review all error messages for information leakage
  • Set up security monitoring and alerting

Responsible Disclosure

If you discover a security vulnerability, please do not open a public issue. Instead:

  1. Send a detailed description to the maintainers
  2. Include proof-of-concept if possible
  3. Allow 7 days for response before public disclosure

References


Audit History

Date Auditor Findings Status
2026-01-21 Security Review 10 vulnerabilities (2 critical, 4 high, 3 medium, 1 low) ✓ Audit completed
2026-01-21 Implementation Critical + High vulnerabilities fixed ✓ Mitigated

Last Updated: 2026-01-21 Status: Critical and High vulnerabilities mitigated. Medium/Low pending.

There aren’t any published security advisories