Security Model

Security Model

Input Validation

All external input is validated before use:

Source Sanitizer Example
$_SERVER headers sanitize_text_field(wp_unslash(...)) $_SERVER['HTTP_IF_NONE_MATCH']
$_POST AJAX data sanitize_key(), absint(), sanitize_text_field() Tab export, import, purge
REST API ?q= sanitize_callback in route schema /cybermaps/v1/search
Settings form input Type-specific sanitizers in sanitize_callback All settings fields

Output Escaping

Context Escaper
HTML attributes esc_attr()
HTML text content esc_html(), esc_html__()
URLs esc_url()
JSON wp_json_encode() with flags
Discovery endpoint content Targeted phpcs:ignore after integrity checks

CSRF Protection

  • All destructive admin actions: check_admin_referer() / check_ajax_referer()
  • REST /purge endpoint: X-WP-Nonce header verified via wp_verify_nonce($nonce, 'wp_rest')
  • Tab export/import: cybermaps_exchange_action nonce

API Authentication

Protected REST endpoints (/urls, /status) require an API secret passed via X-Cybermaps-Secret header:

public function check_secret_permission(\WP_REST_Request $request): bool {
    $secret = (string) ($settings['api_secret'] ?? '');
    if ('' === $secret) return false;
    $header = $request->get_header('X-Cybermaps-Secret');
    return $header && hash_equals($secret, $header);
}

hash_equals() is used for timing-safe string comparison.

Capability Granularity

Capability Default Role Controls Access To
manage_options Administrator All plugin settings (default WordPress cap)
cybermaps_manage_settings Administrator Plugin settings pages
cybermaps_view_logs Administrator Logs dashboard and CSV export
cybermaps_manage_reports Administrator Report generation and scheduling

Capabilities are registered on init and assigned to the Administrator role. Agencies can create custom roles with granular access, e.g., a “Client” role with only cybermaps_view_logs.

Fatal Error Recovery

A register_shutdown_function() handler catches fatal errors and prevents site white-screens:

register_shutdown_function(static function(): void {
    $error = error_get_last();
    if (!$error || !in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR], true)) return;
    $msg = gmdate('c') . ' ' . $error['message'] . ' in ' . $error['file'] . ':' . $error['line'];
    $existing = get_option('cybermaps_fatal_errors', []);
    $existing[] = $msg;
    if (count($existing) > 20) $existing = array_slice($existing, -20);
    update_option('cybermaps_fatal_errors', $existing);
});

The last 20 errors are stored in the cybermaps_fatal_errors option. Admin can review them without the site going down.

Direct File Access

All PHP files include:

if (!defined('ABSPATH')) {
    exit;
}