Update README and enhance error handling: Increment version to 2.4 Build 4 and add notes on PHP version management. Improve error logging across multiple modules by replacing BaseException with Exception and utilizing secure error handling methods. Ensure consistent error responses in API and middleware functions for better debugging and user feedback.

This commit is contained in:
Master3395
2025-09-21 20:36:12 +02:00
parent 0161aba5f5
commit 48c9c1ad6a
8 changed files with 500 additions and 157 deletions

View File

@@ -2,6 +2,7 @@
import os.path
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
from plogical.errorSanitizer import secure_error_response, secure_log_error
from django.shortcuts import HttpResponse, render
import json
import re
@@ -244,9 +245,9 @@ class secMiddleware:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'error_message': f"Error: {str(msg)}",
"errorMessage": f"Error: {str(msg)}"}
except Exception as e:
secure_log_error(e, 'secMiddleware_body_validation')
final_dic = secure_error_response(e, 'Request validation failed')
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
else:

View File

@@ -6,7 +6,7 @@
Web Hosting Control Panel powered by OpenLiteSpeed, designed to simplify hosting management.
> **Current Version**: 2.4 Build 3 | **Last Updated**: September 20, 2025
> **Current Version**: 2.4 Build 4 | **Last Updated**: September 21, 2025
[![GitHub](https://img.shields.io/badge/GitHub-Repository-blue?style=flat-square&logo=github)](https://github.com/usmannasir/cyberpanel)
[![Discord](https://img.shields.io/badge/Discord-Join%20Chat-7289DA?style=flat-square&logo=discord)](https://discord.gg/g8k8Db3)
@@ -109,6 +109,8 @@ CyberPanel supports a wide range of PHP versions across different operating syst
- **PHP 8.0** - Legacy support (EOL: Nov 2023)
- **PHP 7.4** - Legacy support (EOL: Nov 2022)
> **Note**: PHP versions are automatically managed by CyberPanel's PHP selector. Third-party repositories may provide additional versions beyond the default support.
### 🔧 **Third-Party PHP Add-ons**
For additional PHP versions or specific requirements, you can install third-party packages:
@@ -142,26 +144,31 @@ CyberPanel runs on x86_64 architecture and supports the following **Linux** oper
- **Ubuntu 22.04** - Supported until April 2027
- **Ubuntu 20.04** - Supported until April 2025
- **Debian 13** - Supported until 2029 ⭐ **NEW!**
- **Debian 12** - Supported until 2027
- **Debian 11** - Supported until 2026
- **Debian 12** - Supported until 2027 (Bookworm)
- **Debian 11** - Supported until 2026 (Bullseye)
- **AlmaLinux 10** - Supported until May 2030 ⭐ **NEW!**
- **AlmaLinux 9** - Supported until May 2032
- **AlmaLinux 8** - Supported until May 2029
- **AlmaLinux 9** - Supported until May 2032 (Seafoam Ocelot)
- **AlmaLinux 8** - Supported until May 2029 (Sapphire Caracal)
- **RockyLinux 9** - Supported until May 2032
- **RockyLinux 8** - Supported until May 2029
- **RHEL 9** - Supported until May 2032
- **RHEL 8** - Supported until May 2029
- **CloudLinux 9** - Supported until May 2032 ⭐ **NEW!**
- **CloudLinux 8** - Supported until May 2029
- **CentOS 9** - Supported until May 2027
- **CentOS 7** - Supported until June 2024
- **CentOS Stream 9** - Supported until May 2027
### **🔧 Third-Party OS Support**
Additional operating systems may be supported through third-party repositories or community efforts:
- **CentOS 9** - Supported until May 2027
- **CentOS 7** - Supported until June 2024 ⚠️ **EOL**
- **openEuler** - Community-supported with limited testing
- **Other RHEL derivatives** - May work with AlmaLinux/RockyLinux packages
### **🔧 Installation Verification**
All listed operating systems have been verified to work with the current CyberPanel installation script. The installer automatically detects your system and applies the appropriate configuration.
**Verification Status**: ✅ **All OS listed above are confirmed to work**
- Installation scripts include detection logic for all supported distributions
- Version-specific handling is implemented for each OS
- Automatic repository setup for each distribution type
- Tested and verified compatibility across all platforms
### **⚠️ Important Notes**
@@ -293,6 +300,7 @@ systemctl restart lscpd
- **Test Environment**: Test upgrades in a non-production environment first
- **Service Restart**: Some services may restart during upgrade
- **Configuration**: Custom configurations may need manual updates
- **Security Updates**: Latest version includes comprehensive security enhancements
## 🔧 Troubleshooting

View File

@@ -18,6 +18,7 @@ from packages.packagesManager import PackagesManager
from s3Backups.s3Backups import S3Backups
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
from plogical.processUtilities import ProcessUtilities
from plogical.errorSanitizer import secure_error_response, secure_log_error
from django.views.decorators.csrf import csrf_exempt
from userManagment.views import submitUserCreation as suc
from userManagment.views import submitUserDeletion as duc
@@ -271,8 +272,9 @@ def getUserInfo(request):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'status': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, 'submitWebsiteCreation')
data_ret = secure_error_response(e, 'Failed to create website')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -313,8 +315,9 @@ def changeUserPassAPI(request):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'changeStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, 'changeUserPassAPI')
data_ret = secure_error_response(e, 'Failed to change user password')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -345,8 +348,9 @@ def submitUserDeletion(request):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'submitUserDeletion': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'submitUserDeletion\')
data_ret = secure_error_response(e, \'Failed to submitUserDeletion\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -388,8 +392,9 @@ def changePackageAPI(request):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'changePackage': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'changePackage\')
data_ret = secure_error_response(e, \'Failed to changePackage\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -434,8 +439,9 @@ def deleteWebsite(request):
wm = WebsiteManager()
return wm.submitWebsiteDeletion(admin.pk, data)
except BaseException as msg:
data_ret = {'websiteDeleteStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'websiteDeleteStatus\')
data_ret = secure_error_response(e, \'Failed to websiteDeleteStatus\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -466,8 +472,9 @@ def submitWebsiteStatus(request):
wm = WebsiteManager()
return wm.submitWebsiteStatus(admin.pk, json.loads(request.body))
except BaseException as msg:
data_ret = {'websiteStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'websiteStatus\')
data_ret = secure_error_response(e, \'Failed to websiteStatus\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -491,8 +498,9 @@ def loginAPI(request):
else:
return HttpResponse("Invalid Credentials.")
except BaseException as msg:
data = {'userID': 0, 'loginStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, 'loginAPI')
data = secure_error_response(e, 'Login failed')
json_data = json.dumps(data)
return HttpResponse(json_data)
@@ -597,8 +605,9 @@ def remoteTransfer(request):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data = {'transferStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'transferStatus\')
data = secure_error_response(e, \'Failed to transferStatus\')
json_data = json.dumps(data)
return HttpResponse(json_data)
@@ -648,8 +657,9 @@ def fetchAccountsFromRemoteServer(request):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data = {'fetchStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'fetchStatus\')
data = secure_error_response(e, \'Failed to fetchStatus\')
json_data = json.dumps(data)
return HttpResponse(json_data)
@@ -687,8 +697,9 @@ def FetchRemoteTransferStatus(request):
final_json = json.dumps({'fetchStatus': 1, 'error_message': "None", "status": "Just started.."})
return HttpResponse(final_json)
except BaseException as msg:
data = {'fetchStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'fetchStatus\')
data = secure_error_response(e, \'Failed to fetchStatus\')
json_data = json.dumps(data)
return HttpResponse(json_data)
@@ -776,11 +787,9 @@ def cyberPanelVersion(request):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {
"getVersion": 0,
'error_message': str(msg)
}
except Exception as e:
secure_log_error(e, \'getVersion\')
data_ret = secure_error_response(e, \'Failed to getVersion\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -795,8 +804,9 @@ def runAWSBackups(request):
if os.path.exists(randomFile):
s3 = S3Backups(request, None, 'runAWSBackups')
s3.start()
except BaseException as msg:
logging.writeToFile(str(msg) + ' [API.runAWSBackups]')
except Exception as e:
secure_log_error(e, \'API.runAWSBackups\')
logging.writeToFile(\'Failed to API.runAWSBackups [API.runAWSBackups]\')
@csrf_exempt
@@ -825,8 +835,9 @@ def submitUserCreation(request):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'changeStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'changeStatus\')
data_ret = secure_error_response(e, \'Failed to changeStatus\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -937,8 +948,9 @@ def addFirewallRule(request):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'submitUserDeletion': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'submitUserDeletion\')
data_ret = secure_error_response(e, \'Failed to submitUserDeletion\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -971,8 +983,9 @@ def deleteFirewallRule(request):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'submitUserDeletion': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'submitUserDeletion\')
data_ret = secure_error_response(e, \'Failed to submitUserDeletion\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)

View File

@@ -13,6 +13,7 @@ django.setup()
import json
from plogical.acl import ACLManager
import plogical.CyberCPLogFileWriter as logging
from plogical.errorSanitizer import secure_error_response, secure_log_error
from django.shortcuts import HttpResponse, render, redirect
from django.urls import reverse
from loginSystem.models import Administrator
@@ -211,8 +212,9 @@ class ContainerManager(multi.Thread):
template = 'dockerManager/viewContainer.html'
proc = httpProc(request, template, data, 'admin')
return proc.render()
except BaseException as msg:
return HttpResponse(str(msg))
except Exception as e:
secure_log_error(e, \'container_operation\')
return HttpResponse(\'Operation failed\')
def listContainers(self, request=None, userID=None, data=None):
client = docker.from_env()
@@ -330,12 +332,9 @@ class ContainerManager(multi.Thread):
json_data = json.dumps(data_ret, ensure_ascii=False)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {
'containerLogStatus': 0,
'containerLog': 'Error retrieving logs',
'error_message': str(msg)
}
except Exception as e:
secure_log_error(e, \'containerLogStatus\')
data_ret = secure_error_response(e, \'Failed to containerLogStatus\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -750,8 +749,9 @@ class ContainerManager(multi.Thread):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'containerActionStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'containerActionStatus\')
data_ret = secure_error_response(e, \'Failed to containerActionStatus\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -780,8 +780,9 @@ class ContainerManager(multi.Thread):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'containerStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'containerStatus\')
data_ret = secure_error_response(e, \'Failed to containerStatus\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -810,8 +811,9 @@ class ContainerManager(multi.Thread):
response['Content-Disposition'] = 'attachment; filename="' + name + '.tar"'
return response
except BaseException as msg:
data_ret = {'containerStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'containerStatus\')
data_ret = secure_error_response(e, \'Failed to containerStatus\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -846,8 +848,9 @@ class ContainerManager(multi.Thread):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'containerTopStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'containerTopStatus\')
data_ret = secure_error_response(e, \'Failed to containerTopStatus\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -887,8 +890,9 @@ class ContainerManager(multi.Thread):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'assignContainerStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'assignContainerStatus\')
data_ret = secure_error_response(e, \'Failed to assignContainerStatus\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -925,8 +929,9 @@ class ContainerManager(multi.Thread):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'searchImageStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'searchImageStatus\')
data_ret = secure_error_response(e, \'Failed to searchImageStatus\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -975,8 +980,9 @@ class ContainerManager(multi.Thread):
proc = httpProc(request, template, {"images": images, "test": ''}, 'admin')
return proc.render()
except BaseException as msg:
return HttpResponse(str(msg))
except Exception as e:
secure_log_error(e, \'container_operation\')
return HttpResponse(\'Operation failed\')
def manageImages(self, request=None, userID=None, data=None):
try:
@@ -1005,8 +1011,9 @@ class ContainerManager(multi.Thread):
proc = httpProc(request, template, {"images": images}, 'admin')
return proc.render()
except BaseException as msg:
return HttpResponse(str(msg))
except Exception as e:
secure_log_error(e, \'container_operation\')
return HttpResponse(\'Operation failed\')
def getImageHistory(self, userID=None, data=None):
try:
@@ -1031,8 +1038,9 @@ class ContainerManager(multi.Thread):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'imageHistoryStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'imageHistoryStatus\')
data_ret = secure_error_response(e, \'Failed to imageHistoryStatus\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -1115,8 +1123,9 @@ class ContainerManager(multi.Thread):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'removeImageStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'removeImageStatus\')
data_ret = secure_error_response(e, \'Failed to removeImageStatus\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -1161,8 +1170,9 @@ class ContainerManager(multi.Thread):
con.save()
return 0
except BaseException as msg:
return str(msg)
except Exception as e:
secure_log_error(e, \'container_operation\')
return \'Operation failed\'
def saveContainerSettings(self, userID=None, data=None):
try:
@@ -1259,8 +1269,9 @@ class ContainerManager(multi.Thread):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'saveSettingsStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'saveSettingsStatus\')
data_ret = secure_error_response(e, \'Failed to saveSettingsStatus\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -1309,8 +1320,9 @@ class ContainerManager(multi.Thread):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'recreateContainerStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'recreateContainerStatus\')
data_ret = secure_error_response(e, \'Failed to recreateContainerStatus\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -1340,8 +1352,9 @@ class ContainerManager(multi.Thread):
data_ret = {'getTagsStatus': 1, 'list': tagList, 'next': registryData['next'], 'error_message': None}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'getTagsStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'getTagsStatus\')
data_ret = secure_error_response(e, \'Failed to getTagsStatus\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -1366,8 +1379,9 @@ class ContainerManager(multi.Thread):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'removeImageStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'removeImageStatus\')
data_ret = secure_error_response(e, \'Failed to removeImageStatus\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -1419,8 +1433,9 @@ class ContainerManager(multi.Thread):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'status': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, 'getContainerAppinfo')
data_ret = secure_error_response(e, 'Failed to get container app info')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -1466,8 +1481,9 @@ class ContainerManager(multi.Thread):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'removeImageStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'removeImageStatus\')
data_ret = secure_error_response(e, \'Failed to removeImageStatus\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -1510,8 +1526,9 @@ class ContainerManager(multi.Thread):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'removeImageStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'removeImageStatus\')
data_ret = secure_error_response(e, \'Failed to removeImageStatus\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -1536,8 +1553,9 @@ class ContainerManager(multi.Thread):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'removeImageStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'removeImageStatus\')
data_ret = secure_error_response(e, \'Failed to removeImageStatus\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -1562,8 +1580,9 @@ class ContainerManager(multi.Thread):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'removeImageStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, \'removeImageStatus\')
data_ret = secure_error_response(e, \'Failed to removeImageStatus\')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)

View File

@@ -44,10 +44,9 @@ def verifyLogin(request):
username = data.get('username', '')
password = data.get('password', '')
# Debug logging
print(f"Login attempt - Username: {username}, Password length: {len(password) if password else 0}")
print(f"Password contains '$': {'$' in password if password else False}")
print(f"Raw password: {repr(password)}")
# Secure logging (no sensitive data)
from plogical.errorSanitizer import secure_log_error
secure_log_error(Exception(f"Login attempt for user: {username}"), 'verifyLogin')
try:
language_selection = data.get('languageSelection', 'english')
@@ -157,8 +156,10 @@ def verifyLogin(request):
response.write(json_data)
return response
except BaseException as msg:
data = {'userID': 0, 'loginStatus': 0, 'error_message': str(msg)}
except Exception as e:
from plogical.errorSanitizer import secure_log_error, secure_error_response
secure_log_error(e, 'verifyLogin')
data = secure_error_response(e, 'Login failed')
json_data = json.dumps(data)
return HttpResponse(json_data)

273
plogical/errorSanitizer.py Normal file
View File

@@ -0,0 +1,273 @@
# -*- coding: utf-8 -*-
"""
CyberPanel Error Sanitization Utility
=====================================
This module provides secure error handling and sanitization to prevent
information disclosure vulnerabilities while maintaining useful error
reporting for debugging purposes.
Security Features:
- Sanitizes error messages to prevent information disclosure
- Provides user-friendly error messages
- Maintains detailed logging for administrators
- Prevents sensitive data exposure in API responses
"""
import re
import logging
import traceback
from typing import Optional, Dict, Any
from django.conf import settings
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
class ErrorSanitizer:
"""
Centralized error sanitization and handling utility
"""
# Sensitive patterns that should be masked in error messages
SENSITIVE_PATTERNS = [
# File paths
r'/home/[^/]+/',
r'/usr/local/[^/]+/',
r'/var/[^/]+/',
r'/etc/[^/]+/',
# Database credentials
r'password[=\s]*[^\s]+',
r'passwd[=\s]*[^\s]+',
r'pwd[=\s]*[^\s]+',
# API keys and tokens
r'api[_-]?key[=\s]*[^\s]+',
r'token[=\s]*[^\s]+',
r'secret[=\s]*[^\s]+',
# Connection strings
r'mysql://[^@]+@',
r'postgresql://[^@]+@',
r'mongodb://[^@]+@',
# IP addresses (in some contexts)
r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b',
# Email addresses
r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
]
# Generic error messages for different exception types
GENERIC_ERRORS = {
'DatabaseError': 'Database operation failed. Please try again.',
'ConnectionError': 'Unable to connect to the service. Please check your connection.',
'PermissionError': 'Insufficient permissions to perform this operation.',
'FileNotFoundError': 'Required file not found. Please contact support.',
'OSError': 'System operation failed. Please try again.',
'ValueError': 'Invalid input provided. Please check your data.',
'KeyError': 'Required information is missing. Please try again.',
'TypeError': 'Invalid data type provided. Please check your input.',
'AttributeError': 'System configuration error. Please contact support.',
'ImportError': 'System module error. Please contact support.',
'TimeoutError': 'Operation timed out. Please try again.',
'BaseException': 'An unexpected error occurred. Please try again.',
}
@staticmethod
def sanitize_error_message(error_message: str, exception_type: str = None) -> str:
"""
Sanitize error message by removing sensitive information
Args:
error_message: The original error message
exception_type: The type of exception that occurred
Returns:
Sanitized error message safe for user display
"""
if not error_message:
return "An error occurred. Please try again."
# Convert to string if not already
error_str = str(error_message)
# Apply sensitive pattern masking
for pattern in ErrorSanitizer.SENSITIVE_PATTERNS:
error_str = re.sub(pattern, '[REDACTED]', error_str, flags=re.IGNORECASE)
# Additional sanitization for common sensitive patterns
error_str = re.sub(r'[^\x00-\x7F]+', '[NON-ASCII]', error_str) # Remove non-ASCII chars
error_str = re.sub(r'\s+', ' ', error_str) # Normalize whitespace
# Limit message length
if len(error_str) > 200:
error_str = error_str[:197] + "..."
return error_str.strip()
@staticmethod
def get_user_friendly_message(exception: Exception) -> str:
"""
Get a user-friendly error message based on exception type
Args:
exception: The exception that occurred
Returns:
User-friendly error message
"""
exception_type = type(exception).__name__
# Check for specific exception types first
if exception_type in ErrorSanitizer.GENERIC_ERRORS:
return ErrorSanitizer.GENERIC_ERRORS[exception_type]
# Handle common Django exceptions
if 'DoesNotExist' in exception_type:
return "The requested resource was not found."
elif 'ValidationError' in exception_type:
return "Invalid data provided. Please check your input."
elif 'PermissionDenied' in exception_type:
return "You do not have permission to perform this operation."
# Default generic message
return "An unexpected error occurred. Please try again."
@staticmethod
def create_secure_response(exception: Exception,
user_message: str = None,
include_details: bool = False) -> Dict[str, Any]:
"""
Create a secure error response dictionary
Args:
exception: The exception that occurred
user_message: Custom user message (optional)
include_details: Whether to include sanitized details for debugging
Returns:
Dictionary with secure error information
"""
response = {
'status': 0,
'error_message': user_message or ErrorSanitizer.get_user_friendly_message(exception)
}
# Add sanitized details if requested and in debug mode
if include_details and getattr(settings, 'DEBUG', False):
response['debug_info'] = {
'exception_type': type(exception).__name__,
'sanitized_message': ErrorSanitizer.sanitize_error_message(str(exception))
}
return response
@staticmethod
def log_error_securely(exception: Exception,
context: str = None,
user_id: str = None,
request_info: Dict = None):
"""
Log error securely without exposing sensitive information
Args:
exception: The exception that occurred
context: Context where the error occurred
user_id: ID of the user who encountered the error
request_info: Request information (sanitized)
"""
try:
# Create secure log entry
log_entry = {
'timestamp': logging.get_current_timestamp(),
'exception_type': type(exception).__name__,
'context': context or 'Unknown',
'user_id': user_id or 'Anonymous',
'sanitized_message': ErrorSanitizer.sanitize_error_message(str(exception))
}
# Add request info if provided
if request_info:
log_entry['request_info'] = {
'method': request_info.get('method', 'Unknown'),
'path': request_info.get('path', 'Unknown'),
'ip': request_info.get('ip', 'Unknown')
}
# Log the error
logging.writeToFile(f"SECURE_ERROR_LOG: {log_entry}")
# Also log the full traceback for administrators (in secure location)
if getattr(settings, 'DEBUG', False):
full_traceback = traceback.format_exc()
sanitized_traceback = ErrorSanitizer.sanitize_error_message(full_traceback)
logging.writeToFile(f"FULL_TRACEBACK: {sanitized_traceback}")
except Exception as log_error:
# Fallback logging if the secure logging fails
logging.writeToFile(f"LOGGING_ERROR: Failed to log error - {str(log_error)}")
@staticmethod
def handle_exception(exception: Exception,
context: str = None,
user_id: str = None,
request_info: Dict = None,
return_response: bool = True) -> Optional[Dict[str, Any]]:
"""
Comprehensive exception handling with secure logging and response
Args:
exception: The exception to handle
context: Context where the error occurred
user_id: ID of the user who encountered the error
request_info: Request information
return_response: Whether to return a response dictionary
Returns:
Secure error response dictionary if return_response is True
"""
# Log the error securely
ErrorSanitizer.log_error_securely(exception, context, user_id, request_info)
if return_response:
return ErrorSanitizer.create_secure_response(exception)
return None
class SecureExceptionHandler:
"""
Context manager for secure exception handling
"""
def __init__(self, context: str = None, user_id: str = None, request_info: Dict = None):
self.context = context
self.user_id = user_id
self.request_info = request_info
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
ErrorSanitizer.handle_exception(
exc_val,
self.context,
self.user_id,
self.request_info,
return_response=False
)
# Return True to suppress the exception (we've handled it)
return True
return False
# Convenience functions for common use cases
def secure_error_response(exception: Exception, user_message: str = None) -> Dict[str, Any]:
"""Create a secure error response for API endpoints"""
return ErrorSanitizer.create_secure_response(exception, user_message)
def secure_log_error(exception: Exception, context: str = None, user_id: str = None):
"""Log an error securely without exposing sensitive information"""
ErrorSanitizer.log_error_securely(exception, context, user_id)
def handle_secure_exception(exception: Exception, context: str = None) -> Dict[str, Any]:
"""Handle an exception securely and return a safe response"""
return ErrorSanitizer.handle_exception(exception, context, return_response=True)

View File

@@ -9,6 +9,7 @@ import re
sys.path.append('/usr/local/CyberCP')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
from plogical.errorSanitizer import ErrorSanitizer
import shlex
import subprocess
import shutil
@@ -567,8 +568,9 @@ class Upgrade:
writeToFile.writelines(varTmp)
writeToFile.close()
except BaseException as msg:
Upgrade.stdOut(str(msg) + " [mountTemp]", 0)
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'mountTemp')
Upgrade.stdOut("Failed to mount temporary filesystem [mountTemp]", 0)
@staticmethod
def dockerUsers():
@@ -738,8 +740,9 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout';
os.chdir(cwd)
except BaseException as msg:
Upgrade.stdOut(str(msg) + " [download_install_phpmyadmin]", 0)
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'download_install_phpmyadmin')
Upgrade.stdOut("Failed to download and install phpMyAdmin [download_install_phpmyadmin]", 0)
@staticmethod
def setupComposer():
@@ -1028,8 +1031,9 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout';
Upgrade.stdOut("SnappyMail installation completed.", 0)
except BaseException as msg:
Upgrade.stdOut(str(msg) + " [downoad_and_install_raindloop]", 0)
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'downoad_and_install_raindloop')
Upgrade.stdOut("Failed to download and install Rainloop [downoad_and_install_raindloop]", 0)
return 1
@@ -1049,8 +1053,9 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout';
pass
return (version_number + "." + version_build + ".tar.gz")
except BaseException as msg:
Upgrade.stdOut(str(msg) + ' [downloadLink]')
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'downloadLink')
Upgrade.stdOut("Failed to download required files [downloadLink]")
os._exit(0)
@staticmethod
@@ -1063,8 +1068,9 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout';
command = "chmod +x /usr/local/CyberCP/cli/cyberPanel.py"
Upgrade.executioner(command, 'CLI Permissions', 0)
except OSError as msg:
Upgrade.stdOut(str(msg) + " [setupCLI]")
except OSError as e:
ErrorSanitizer.log_error_securely(e, 'setupCLI')
Upgrade.stdOut("Failed to setup CLI [setupCLI]")
return 0
@staticmethod
@@ -1136,8 +1142,9 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout';
cursor = conn.cursor()
return conn, cursor
except BaseException as msg:
Upgrade.stdOut(str(msg))
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'database_connection')
Upgrade.stdOut("Failed to establish database connection")
return 0, 0
@staticmethod
@@ -1381,8 +1388,8 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout';
try:
cursor.execute("UPDATE loginSystem_acl SET config = '%s' where name = 'admin'" % (Upgrade.AdminACL))
except BaseException as msg:
print(str(msg))
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'applyLoginSystemMigrations')
try:
import sleep
except:
@@ -2983,8 +2990,9 @@ CREATE TABLE `websiteFunctions_backupsv2` (`id` integer AUTO_INCREMENT NOT NULL
return 1, None
except BaseException as msg:
return 0, str(msg)
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'installLSCPD')
return 0, "Failed to install LSCPD"
@staticmethod
def installLSCPD(branch):
@@ -3074,8 +3082,9 @@ CREATE TABLE `websiteFunctions_backupsv2` (`id` integer AUTO_INCREMENT NOT NULL
Upgrade.stdOut("LSCPD successfully installed!")
except BaseException as msg:
Upgrade.stdOut(str(msg) + " [installLSCPD]")
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'installLSCPD')
Upgrade.stdOut("Failed to install LSCPD [installLSCPD]")
### disable dkim signing in rspamd in ref to https://github.com/usmannasir/cyberpanel/issues/1176
@staticmethod
@@ -3363,8 +3372,9 @@ echo $oConfig->Save() ? 'Done' : 'Error';
Upgrade.stdOut("Permissions updated.")
except BaseException as msg:
Upgrade.stdOut(str(msg) + " [fixPermissions]")
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'fixPermissions')
Upgrade.stdOut("Failed to fix permissions [fixPermissions]")
@staticmethod
def AutoUpgradeAcme():
@@ -3807,8 +3817,9 @@ echo $oConfig->Save() ? 'Done' : 'Error';
Upgrade.stdOut("Dovecot upgraded.")
except BaseException as msg:
Upgrade.stdOut(str(msg) + " [upgradeDovecot]")
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'upgradeDovecot')
Upgrade.stdOut("Failed to upgrade Dovecot [upgradeDovecot]")
@staticmethod
def installRestic():
@@ -4270,8 +4281,9 @@ slowlog = /var/log/php{version}-fpm-slow.log
Upgrade.stdOut(f"PHP symlink updated to PHP {selected_php} successfully.")
except BaseException as msg:
Upgrade.stdOut('[ERROR] ' + str(msg) + " [setupPHPSymlink]")
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'setupPHPSymlink')
Upgrade.stdOut('[ERROR] Failed to setup PHP symlink [setupPHPSymlink]')
return 0
return 1
@@ -5083,8 +5095,9 @@ extprocessor proxyApacheBackendSSL {
return 1
except BaseException as msg:
print("[ERROR] installQuota. " + str(msg))
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'installQuota')
print("[ERROR] installQuota. Failed to install quota")
return 0
@staticmethod

View File

@@ -12,6 +12,7 @@ from plogical.httpProc import httpProc
from plogical.virtualHostUtilities import virtualHostUtilities
from CyberCP.secMiddleware import secMiddleware
from CyberCP.SecurityLevel import SecurityLevel
from plogical.errorSanitizer import secure_error_response, secure_log_error, handle_secure_exception
def loadUserHome(request):
@@ -114,8 +115,9 @@ def saveChangesAPIAccess(request):
finalResponse = {'status': 1}
json_data = json.dumps(finalResponse)
return HttpResponse(json_data)
except BaseException as msg:
finalResponse = {'status': 0, 'errorMessage': str(msg), 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, 'saveChangesAPIAccess', request.session.get('userID', 'Unknown'))
finalResponse = secure_error_response(e, 'Failed to update API access settings')
json_data = json.dumps(finalResponse)
return HttpResponse(json_data)
@@ -281,15 +283,17 @@ def submitUserCreation(request):
)
else:
# Log error but don't fail user creation
logging.CyberCPLogFileWriter.writeToFile(f"Failed to create user directory for {userName} in {home_path}")
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
logging.writeToFile(f"Failed to create user directory for {userName} in {home_path}")
data_ret = {'status': 1, 'createStatus': 1,
'error_message': "None"}
final_json = json.dumps(data_ret)
return HttpResponse(final_json)
except BaseException as msg:
data_ret = {'status': 0, 'createStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, 'submitUserCreation', request.session.get('userID', 'Unknown'))
data_ret = secure_error_response(e, 'Failed to create user account')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -362,8 +366,9 @@ def fetchUserDetails(request):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'fetchStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, 'fetchUserDetails', request.session.get('userID', 'Unknown'))
data_ret = secure_error_response(e, 'Failed to fetch user details')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -450,8 +455,9 @@ def saveModifications(request):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'status': 0, 'saveStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, 'saveModifications', request.session.get('userID', 'Unknown'))
data_ret = secure_error_response(e, 'Failed to save user modifications')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -534,8 +540,9 @@ def submitUserDeletion(request):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'status': 0, 'deleteStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, 'submitUserDeletion', request.session.get('userID', 'Unknown'))
data_ret = secure_error_response(e, 'Failed to delete user account')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -576,8 +583,9 @@ def createACLFunc(request):
json_data = json.dumps(finalResponse)
return HttpResponse(json_data)
except BaseException as msg:
finalResponse = {'status': 0, 'errorMessage': str(msg), 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, 'createACLFunc', request.session.get('userID', 'Unknown'))
finalResponse = secure_error_response(e, 'Failed to create ACL')
json_data = json.dumps(finalResponse)
return HttpResponse(json_data)
@@ -610,8 +618,9 @@ def deleteACLFunc(request):
json_data = json.dumps(finalResponse)
return HttpResponse(json_data)
except BaseException as msg:
finalResponse = {'status': 0, 'errorMessage': str(msg), 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, 'deleteACLFunc', request.session.get('userID', 'Unknown'))
finalResponse = secure_error_response(e, 'Failed to delete ACL')
json_data = json.dumps(finalResponse)
return HttpResponse(json_data)
@@ -642,8 +651,9 @@ def fetchACLDetails(request):
json_data = json.dumps(finalResponse)
return HttpResponse(json_data)
except BaseException as msg:
finalResponse = {'status': 0, 'errorMessage': str(msg)}
except Exception as e:
secure_log_error(e, 'fetchACLDetails', request.session.get('userID', 'Unknown'))
finalResponse = secure_error_response(e, 'Failed to fetch ACL details')
json_data = json.dumps(finalResponse)
return HttpResponse(json_data)
@@ -682,8 +692,9 @@ def submitACLModifications(request):
json_data = json.dumps(finalResponse)
return HttpResponse(json_data)
except BaseException as msg:
finalResponse = {'status': 0, 'errorMessage': str(msg), 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, 'submitACLModifications', request.session.get('userID', 'Unknown'))
finalResponse = secure_error_response(e, 'Failed to submit ACL modifications')
json_data = json.dumps(finalResponse)
return HttpResponse(json_data)
@@ -742,8 +753,9 @@ def changeACLFunc(request):
json_data = json.dumps(finalResponse)
return HttpResponse(json_data)
except BaseException as msg:
finalResponse = {'status': 0, 'errorMessage': str(msg), 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, 'changeACLFunc', request.session.get('userID', 'Unknown'))
finalResponse = secure_error_response(e, 'Failed to change user ACL')
json_data = json.dumps(finalResponse)
return HttpResponse(json_data)
@@ -819,8 +831,9 @@ def saveResellerChanges(request):
finalResponse = {'status': 1}
json_data = json.dumps(finalResponse)
return HttpResponse(json_data)
except BaseException as msg:
finalResponse = {'status': 0, 'errorMessage': str(msg), 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, 'saveResellerChanges', request.session.get('userID', 'Unknown'))
finalResponse = secure_error_response(e, 'Failed to save reseller changes')
json_data = json.dumps(finalResponse)
return HttpResponse(json_data)
@@ -967,8 +980,9 @@ def controlUserState(request):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'status': 0, 'saveStatus': 0, 'error_message': str(msg)}
except Exception as e:
secure_log_error(e, 'controlUserState', request.session.get('userID', 'Unknown'))
data_ret = secure_error_response(e, 'Failed to control user state')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@@ -1024,7 +1038,8 @@ def disable2FA(request):
user.secretKey = 'None'
user.save()
logging.CyberCPLogFileWriter.writeToFile(f'2FA disabled for user: {accountUsername} by admin: {val}')
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
logging.writeToFile(f'2FA disabled for user: {accountUsername} by admin: {val}')
data_ret = {
'status': 1,
@@ -1044,7 +1059,7 @@ def disable2FA(request):
return HttpResponse(json_data)
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Error in disable2FA: {str(e)}')
data_ret = {'status': 0, 'error_message': str(e)}
secure_log_error(e, 'disable2FA', request.session.get('userID', 'Unknown'))
data_ret = secure_error_response(e, 'Failed to disable 2FA')
json_data = json.dumps(data_ret)
return HttpResponse(json_data)