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.
| Severity | Count | Status |
|---|---|---|
| CRITICAL | 2 | ✅ Fixed |
| HIGH | 4 | ✅ Fixed |
| MEDIUM | 3 | Recommended fix |
| LOW | 1 | Optional improvement |
File: webstatuspi/monitor.py:152-156
CWE: CWE-918 (Server-Side Request Forgery)
CVSS Score: 9.8 (Critical)
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/)
- 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
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.
File: webstatuspi/alerter.py:134-138
CWE: CWE-918
CVSS Score: 9.5 (Critical)
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
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 logicPriority: Fix immediately before deployment.
File: webstatuspi/_dashboard.py:1192
CWE: CWE-79 (Cross-Site Scripting)
CVSS Score: 7.5 (High)
The dashboard uses innerHTML to render user-controlled data. Combined with CSP allowing unsafe-inline, an attacker can inject arbitrary JavaScript.
- Stealing authentication credentials (if auth is added)
- Redirecting users to malicious sites
- Keylogging in the dashboard
- Dashboard defacement
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.
File: webstatuspi/api.py:193-198
CWE: CWE-1021
CVSS Score: 7.2 (High)
CSP allows 'unsafe-inline' which negates XSS protection. Combined with HIGH-1, this enables script injection.
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.
File: webstatuspi/api.py:167
CWE: CWE-307 (Improper Restriction of Authentication Attempts)
CVSS Score: 6.8 (High)
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.
- 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
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.
File: webstatuspi/api.py:200-231
CWE: CWE-22 (Path Traversal)
CVSS Score: 5.9 (Medium-High)
Current validation prevents path traversal but doesn't restrict character set to safe characters.
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 namePriority: Fix within 2 weeks.
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]Status: Already secure. The code does not log sensitive tokens.
Verification: Ensure no logger level is DEBUG in production.
Status: All queries use parameterized statements correctly.
Verified: All INSERT, SELECT, and DELETE statements use ? placeholders.
No action needed.
Only relevant if using HTTPS.
def _add_security_headers(self) -> None:
self.send_header("Strict-Transport-Security", "max-age=31536000; includeSubDomains")-
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>"
-
Network Isolation
- Block direct access to internal services from URL checks
- Use allowlist/blocklist of acceptable URLs if possible
-
Monitoring
- Enable logging of all requests
- Monitor for repeated rate limit hits
- Alert on invalid token attempts
-
Docker Security
# Run as non-root user RUN adduser --disabled-password --gecos '' webstatuspi USER webstatuspi # Read-only filesystem # tmpfs for /tmp only
-
Input Validation
- Always validate URLs with
validate_url_for_ssrf() - Whitelist allowed URL schemes to http/https
- Block private IP ranges and reserved addresses
- Always validate URLs with
-
Output Encoding
- Use DOM APIs (textContent) instead of innerHTML
- Never embed user input directly in HTML
-
Security Headers
- CSP with nonces, no
unsafe-inline - X-Frame-Options: DENY
- X-Content-Type-Options: nosniff
- CSP with nonces, no
-
Authentication
- Use timing-safe comparison (secrets.compare_digest)
- Generate tokens with sufficient entropy (32+ bytes)
- Log failed attempts without leaking tokens
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 auditto 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
If you discover a security vulnerability, please do not open a public issue. Instead:
- Send a detailed description to the maintainers
- Include proof-of-concept if possible
- Allow 7 days for response before public disclosure
| 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.