Skip to content

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:

  1. Defense in Depth: Implement multiple layers of security controls
  2. Least Privilege: Provide only the minimum necessary access rights
  3. Input Validation: Never trust user input
  4. Secure by Default: Security should be the default configuration
  5. Keep It Simple: Complex systems are harder to secure

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

  1. Do not store sensitive information in the session
  2. Regenerate session IDs after login/privilege changes
  3. 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:

# In .htaccess
<Files "config.php">
    Order Allow,Deny
    Deny from all
</Files>

Third-Party Libraries

Selecting Libraries

  1. Choose actively maintained libraries
  2. Check for security vulnerabilities
  3. 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

  1. Test all forms with invalid input
  2. Attempt to bypass authentication and authorization
  3. Test file upload functionality with malicious files
  4. Check for XSS vulnerabilities in all output
  5. Test for SQL injection in all database queries

Automated Testing

Use automated tools to scan for vulnerabilities:

  1. Static code analysis tools
  2. Web application scanners
  3. 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

// For data used in URLs
echo htmlspecialchars(urlencode($variable), ENT_QUOTES, 'UTF-8');

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));

Resources


security #best-practices #xoops #module-development #authentication #authorization