Security Best Practices¶
2.5.x ✅ 2026 ✅
Security APIs are stable across versions
The security practices and APIs documented here work in both XOOPS 2.5.x and 2026. Core security classes (XoopsSecurity, MyTextSanitizer) remain stable.
This document provides comprehensive security best practices for XOOPS module developers. Following these guidelines will help ensure that your modules are secure and do not introduce vulnerabilities into XOOPS installations.
Security Principles¶
Every XOOPS developer should follow these fundamental security principles:
- Defense in Depth: Implement multiple layers of security controls
- Least Privilege: Provide only the minimum necessary access rights
- Input Validation: Never trust user input
- Secure by Default: Security should be the default configuration
- Keep It Simple: Complex systems are harder to secure
Related Documentation¶
- CSRF-Protection - Token system and XoopsSecurity class
- Input-Sanitization - MyTextSanitizer and validation
- SQL-Injection-Prevention - Database security practices
Quick Reference Checklist¶
Before releasing your module, verify:
- All forms include XOOPS tokens
- All user input is validated and sanitized
- All output is properly escaped
- All database queries use parameterized statements
- File uploads are properly validated
- Authentication and authorization checks are in place
- Error handling does not reveal sensitive information
- Sensitive configuration is protected
- Third-party libraries are up to date
- Security testing has been performed
Authentication and Authorization¶
Checking User Authentication¶
// Check if user is logged in
if (!is_object($GLOBALS['xoopsUser'])) {
redirect_header(XOOPS_URL, 3, _NOPERM);
exit();
}
Checking User Permissions¶
// Check if user has permission to access this module
if (!$GLOBALS['xoopsUser']->isAdmin($xoopsModule->mid())) {
redirect_header(XOOPS_URL, 3, _NOPERM);
exit();
}
// Check specific permission
$moduleHandler = xoops_getHandler('module');
$module = $moduleHandler->getByDirname('mymodule');
$moduleperm_handler = xoops_getHandler('groupperm');
$groups = $GLOBALS['xoopsUser']->getGroups();
if (!$moduleperm_handler->checkRight('mymodule_view', $item_id, $groups, $module->getVar('mid'))) {
redirect_header(XOOPS_URL, 3, _NOPERM);
exit();
}
Setting Up Module Permissions¶
// Create permission in install/update function
$gpermHandler = xoops_getHandler('groupperm');
$gpermHandler->deleteByModule($module->getVar('mid'), 'mymodule_view');
// Add permission for all groups
$groups = [XOOPS_GROUP_ADMIN, XOOPS_GROUP_USERS, XOOPS_GROUP_ANONYMOUS];
foreach ($groups as $group_id) {
$gpermHandler->addRight('mymodule_view', 1, $group_id, $module->getVar('mid'));
}
Session Security¶
Session Handling Best Practices¶
- Do not store sensitive information in the session
- Regenerate session IDs after login/privilege changes
- Validate session data before using it
// Regenerate session ID after login
session_regenerate_id(true);
// Validate session data
if (isset($_SESSION['mymodule_user_id'])) {
$user_id = (int)$_SESSION['mymodule_user_id'];
// Verify user exists in database
}
Preventing Session Fixation¶
// After successful login
session_regenerate_id(true);
$_SESSION['mymodule_user_ip'] = $_SERVER['REMOTE_ADDR'];
// On subsequent requests
if ($_SESSION['mymodule_user_ip'] !== $_SERVER['REMOTE_ADDR']) {
// Possible session hijacking attempt
session_destroy();
redirect_header('index.php', 3, 'Session error');
exit();
}
File Upload Security¶
Validating File Uploads¶
// Check if file was uploaded properly
if (!isset($_FILES['userfile']) || $_FILES['userfile']['error'] != UPLOAD_ERR_OK) {
redirect_header('index.php', 3, 'File upload error');
exit();
}
// Check file size
if ($_FILES['userfile']['size'] > 1000000) { // 1MB limit
redirect_header('index.php', 3, 'File too large');
exit();
}
// Check file type
$allowed_types = ['image/jpeg', 'image/png', 'image/gif'];
if (!in_array($_FILES['userfile']['type'], $allowed_types)) {
redirect_header('index.php', 3, 'Invalid file type');
exit();
}
// Validate file extension
$filename = $_FILES['userfile']['name'];
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$allowed_extensions = ['jpg', 'jpeg', 'png', 'gif'];
if (!in_array($ext, $allowed_extensions)) {
redirect_header('index.php', 3, 'Invalid file extension');
exit();
}
Using XOOPS Uploader¶
include_once XOOPS_ROOT_PATH . '/class/uploader.php';
$allowed_mimetypes = ['image/gif', 'image/jpeg', 'image/png'];
$maxsize = 1000000; // 1MB
$maxwidth = 1024;
$maxheight = 768;
$upload_dir = XOOPS_ROOT_PATH . '/uploads/mymodule';
$uploader = new XoopsMediaUploader(
$upload_dir,
$allowed_mimetypes,
$maxsize,
$maxwidth,
$maxheight
);
if ($uploader->fetchMedia('userfile')) {
$uploader->setPrefix('mymodule_');
if ($uploader->upload()) {
$filename = $uploader->getSavedFileName();
// Save filename to database
} else {
echo $uploader->getErrors();
}
} else {
echo $uploader->getErrors();
}
Storing Uploaded Files Securely¶
// Define upload directory outside web root
$upload_dir = XOOPS_VAR_PATH . '/uploads/mymodule';
// Create directory if it doesn't exist
if (!is_dir($upload_dir)) {
mkdir($upload_dir, 0755, true);
}
// Move uploaded file
move_uploaded_file($_FILES['userfile']['tmp_name'], $upload_dir . '/' . $safe_filename);
Error Handling and Logging¶
Secure Error Handling¶
try {
$result = someFunction();
if (!$result) {
throw new Exception('Operation failed');
}
} catch (Exception $e) {
// Log the error
xoops_error($e->getMessage());
// Display a generic error message to the user
redirect_header('index.php', 3, 'An error occurred. Please try again later.');
exit();
}
Logging Security Events¶
// Log security events
xoops_loadLanguage('logger', 'mymodule');
$GLOBALS['xoopsLogger']->addExtra('Security', 'Failed login attempt for user: ' . $username);
Configuration Security¶
Storing Sensitive Configuration¶
// Define configuration path outside web root
$config_path = XOOPS_VAR_PATH . '/configs/mymodule/config.php';
// Load configuration
if (file_exists($config_path)) {
include $config_path;
} else {
// Handle missing configuration
}
Protecting Configuration Files¶
Use .htaccess to protect configuration files:
Third-Party Libraries¶
Selecting Libraries¶
- Choose actively maintained libraries
- Check for security vulnerabilities
- Verify the library's license is compatible with XOOPS
Updating Libraries¶
// Check library version
if (version_compare(LIBRARY_VERSION, '1.2.3', '<')) {
xoops_error('Please update the library to version 1.2.3 or higher');
}
Isolating Libraries¶
// Load library in a controlled way
function loadLibrary($file)
{
$allowed = ['parser.php', 'formatter.php'];
if (!in_array($file, $allowed)) {
return false;
}
include_once XOOPS_ROOT_PATH . '/modules/mymodule/libraries/' . $file;
return true;
}
Security Testing¶
Manual Testing Checklist¶
- Test all forms with invalid input
- Attempt to bypass authentication and authorization
- Test file upload functionality with malicious files
- Check for XSS vulnerabilities in all output
- Test for SQL injection in all database queries
Automated Testing¶
Use automated tools to scan for vulnerabilities:
- Static code analysis tools
- Web application scanners
- Dependency checkers for third-party libraries
Output Escaping¶
HTML Context¶
// For regular HTML content
echo htmlspecialchars($variable, ENT_QUOTES, 'UTF-8');
// Using MyTextSanitizer
$myts = MyTextSanitizer::getInstance();
echo $myts->htmlSpecialChars($variable);
JavaScript Context¶
// For data used in JavaScript
echo json_encode($variable);
// For inline JavaScript
echo 'var data = ' . json_encode($variable) . ';';
URL Context¶
Template Variables¶
// Assign variables to Smarty template
$GLOBALS['xoopsTpl']->assign('title', htmlspecialchars($title, ENT_QUOTES, 'UTF-8'));
// For HTML content that should be displayed as-is
$GLOBALS['xoopsTpl']->assign('content', $myts->displayTarea($content, 1, 1, 1, 1, 1));