diff --git a/includes/class-hvac-event-form-builder.php b/includes/class-hvac-event-form-builder.php
index e560f9ec..dac85c36 100644
--- a/includes/class-hvac-event-form-builder.php
+++ b/includes/class-hvac-event-form-builder.php
@@ -987,8 +987,9 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
template_mode_enabled && $this->current_template): ?>
-
Using Template: current_template['name']); ?>
-
+
+
Using Template: current_template['name']); ?>
+
@@ -1198,19 +1199,34 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
* AJAX handler for loading template data
*/
public function ajax_load_template_data(): void {
- // Security check
- if (!wp_verify_nonce($_GET['nonce'] ?? '', 'hvac_template_nonce')) {
- wp_send_json_error(['message' => __('Security check failed', 'hvac-community-events')]);
+ // SECURITY FIX: Use POST for all AJAX handlers and proper nonce verification
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
+ wp_send_json_error(['message' => __('Invalid request method', 'hvac-community-events')], 405);
return;
}
- // Capability check - ensure user can create/edit events
- if (!current_user_can('edit_posts') && !array_intersect(['hvac_trainer', 'hvac_master_trainer'], wp_get_current_user()->roles)) {
- wp_send_json_error(['message' => __('Permission denied', 'hvac-community-events')]);
+ // Security check - Fixed: Use POST data for nonce verification
+ if (!wp_verify_nonce($_POST['nonce'] ?? '', 'hvac_template_nonce')) {
+ wp_send_json_error(['message' => __('Security check failed', 'hvac-community-events')], 403);
return;
}
- $template_id = sanitize_text_field($_GET['template_id'] ?? '');
+ // SECURITY FIX: Enhanced capability check with specific template permissions
+ if (!current_user_can('edit_posts') && !current_user_can('manage_hvac_templates')) {
+ wp_send_json_error(['message' => __('Permission denied', 'hvac-community-events')], 403);
+ return;
+ }
+
+ // Additional role-based check for HVAC trainers
+ $user = wp_get_current_user();
+ $allowed_roles = ['hvac_trainer', 'hvac_master_trainer', 'administrator'];
+ if (!array_intersect($allowed_roles, $user->roles) && !current_user_can('manage_options')) {
+ wp_send_json_error(['message' => __('Insufficient permissions', 'hvac-community-events')], 403);
+ return;
+ }
+
+ // SECURITY FIX: Get template_id from POST data, not GET
+ $template_id = sanitize_text_field($_POST['template_id'] ?? '');
if (empty($template_id) || $template_id === '0') {
wp_send_json_success(['template_data' => [], 'message' => __('Template cleared', 'hvac-community-events')]);
return;
@@ -1227,23 +1243,85 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
'usage_count' => $template['usage_count'] + 1
]);
+ // SECURITY FIX: Sanitize template data before sending to prevent XSS
wp_send_json_success([
- 'template_data' => $template['field_data'],
+ 'template_data' => $this->sanitize_template_data($template['field_data']),
'template_info' => [
- 'name' => $template['name'],
- 'description' => $template['description'],
+ 'name' => wp_kses_post($template['name']),
+ 'description' => wp_kses_post($template['description']),
],
'message' => __('Template loaded successfully', 'hvac-community-events')
]);
}
+ /**
+ * SECURITY FIX: Sanitize template data to prevent XSS
+ *
+ * @param array $template_data Raw template data
+ * @return array Sanitized template data
+ */
+ private function sanitize_template_data(array $template_data): array {
+ $sanitized = [];
+
+ foreach ($template_data as $key => $value) {
+ if (is_array($value)) {
+ $sanitized[$key] = $this->sanitize_template_data($value);
+ } else {
+ // Apply appropriate sanitization based on field type
+ $field_config = $this->get_field_config($key);
+ if ($field_config && isset($field_config['sanitize'])) {
+ $sanitized[$key] = $this->sanitize_field_value($field_config, $value);
+ } else {
+ // Default to text sanitization
+ $sanitized[$key] = sanitize_text_field($value);
+ }
+ }
+ }
+
+ return $sanitized;
+ }
+
+ /**
+ * Get field configuration by name
+ *
+ * @param string $field_name Field name
+ * @return array|null Field configuration or null if not found
+ */
+ private function get_field_config(string $field_name): ?array {
+ foreach ($this->fields as $field) {
+ if ($field['name'] === $field_name) {
+ return $field;
+ }
+ }
+ return null;
+ }
+
/**
* AJAX handler for saving form as template
*/
public function ajax_save_as_template(): void {
+ // SECURITY FIX: Ensure POST method
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
+ wp_send_json_error(['message' => __('Invalid request method', 'hvac-community-events')], 405);
+ return;
+ }
+
// Security check
if (!wp_verify_nonce($_POST['nonce'] ?? '', 'hvac_template_nonce')) {
- wp_send_json_error(['message' => __('Security check failed', 'hvac-community-events')]);
+ wp_send_json_error(['message' => __('Security check failed', 'hvac-community-events')], 403);
+ return;
+ }
+
+ // SECURITY FIX: Enhanced capability and role checks
+ if (!current_user_can('edit_posts') && !current_user_can('manage_hvac_templates')) {
+ wp_send_json_error(['message' => __('Permission denied', 'hvac-community-events')], 403);
+ return;
+ }
+
+ $user = wp_get_current_user();
+ $allowed_roles = ['hvac_trainer', 'hvac_master_trainer', 'administrator'];
+ if (!array_intersect($allowed_roles, $user->roles) && !current_user_can('manage_options')) {
+ wp_send_json_error(['message' => __('Insufficient permissions', 'hvac-community-events')], 403);
return;
}
diff --git a/includes/class-hvac-plugin.php b/includes/class-hvac-plugin.php
index 1feee6ab..c545f69c 100644
--- a/includes/class-hvac-plugin.php
+++ b/includes/class-hvac-plugin.php
@@ -868,18 +868,35 @@ final class HVAC_Plugin {
* @param array $submission Submission data to debug
*/
public function debugTECSubmissionData(array $submission): void {
+ // SECURITY FIX: Only log debug info for authorized users and non-production environments
+ if (!defined('WP_DEBUG') || !WP_DEBUG) {
+ return;
+ }
+
+ // Additional check for admin/development access
+ if (!current_user_can('manage_options') && !in_array($_SERVER['SERVER_NAME'] ?? '', ['localhost', '127.0.0.1', 'dev.upskillhvac.com'])) {
+ return;
+ }
+
+ // SECURITY FIX: Sanitize sensitive data before logging
$debug_info = [
'timestamp' => current_time('Y-m-d H:i:s'),
'user_id' => get_current_user_id(),
'submission_keys' => array_keys($submission),
'has_excerpt' => isset($submission['excerpt']),
'has_post_excerpt' => isset($submission['post_excerpt']),
- 'excerpt_value' => $submission['excerpt'] ?? 'not_set',
- 'event_start' => $submission['EventStartDate'] ?? 'not_set',
- 'event_end' => $submission['EventEndDate'] ?? 'not_set'
+ // SECURITY FIX: Sanitize user data before logging
+ 'excerpt_value' => isset($submission['excerpt']) ? wp_kses_post(substr($submission['excerpt'], 0, 100)) . '...' : 'not_set',
+ 'event_start' => isset($submission['EventStartDate']) ? sanitize_text_field($submission['EventStartDate']) : 'not_set',
+ 'event_end' => isset($submission['EventEndDate']) ? sanitize_text_field($submission['EventEndDate']) : 'not_set'
];
- error_log('[HVAC TEC Debug] Submission data: ' . print_r($debug_info, true));
+ // SECURITY FIX: Use WordPress logging with proper data sanitization
+ if (class_exists('HVAC_Logger')) {
+ HVAC_Logger::debug('TEC Submission Debug: ' . wp_json_encode($debug_info), 'TEC_Integration');
+ } else {
+ error_log('[HVAC TEC Debug] Submission data: ' . wp_json_encode($debug_info));
+ }
}
/**
@@ -1151,23 +1168,32 @@ final class HVAC_Plugin {
'stack_trace' => sanitize_textarea_field($logEntry['stack'] ?? '')
];
- // Log to WordPress debug log with structured format
+ // SECURITY FIX: Enhanced logging security with capability checks
if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
+ // Only log for authorized users or development environments
+ if (!current_user_can('manage_options') && !in_array($_SERVER['SERVER_NAME'] ?? '', ['localhost', '127.0.0.1', 'dev.upskillhvac.com'])) {
+ wp_send_json_success(['logged' => false, 'message' => 'Debug logging disabled in production']);
+ return;
+ }
+
$logParts = [
'[SAFARI-DEBUG]',
$safeLogEntry['timestamp'],
- $safeLogEntry['message'],
- 'UA: ' . $safeLogEntry['user_agent']
+ substr($safeLogEntry['message'], 0, 100), // Limit message length
+ 'UA: ' . substr($safeLogEntry['user_agent'], 0, 50) // Limit UA length
];
-
+
+ // SECURITY FIX: Limit data exposure and sanitize further
if (!empty($safeLogEntry['data'])) {
- $logParts[] = 'Data: ' . $safeLogEntry['data'];
+ $data = wp_kses_post(substr($safeLogEntry['data'], 0, 200));
+ $logParts[] = 'Data: ' . $data . '...';
}
-
+
if (!empty($safeLogEntry['error_info'])) {
- $logParts[] = 'Error: ' . $safeLogEntry['error_info'];
+ $error = wp_kses_post(substr($safeLogEntry['error_info'], 0, 100));
+ $logParts[] = 'Error: ' . $error . '...';
}
-
+
error_log(implode(' | ', $logParts));
}
@@ -1381,17 +1407,34 @@ final class HVAC_Plugin {
}
/**
- * Load core files with error handling
- *
+ * Load core files with error handling and security validation
+ *
* @param string[] $files Array of file paths relative to plugin directory
* @return Generator File path => loaded status
*/
private function loadCoreFiles(array $files): Generator {
foreach ($files as $file) {
+ // SECURITY FIX: Validate file path to prevent directory traversal
+ if (!$this->isSecureFilePath($file)) {
+ error_log("HVAC Plugin Security: Rejected insecure file path: {$file}");
+ yield $file => false;
+ continue;
+ }
+
$filePath = HVAC_PLUGIN_DIR . $file;
-
- if (file_exists($filePath)) {
- require_once $filePath;
+
+ // SECURITY FIX: Ensure file is within plugin directory
+ $realPath = realpath($filePath);
+ $pluginRealPath = realpath(HVAC_PLUGIN_DIR);
+
+ if ($realPath === false || strpos($realPath, $pluginRealPath) !== 0) {
+ error_log("HVAC Plugin Security: File path outside plugin directory: {$file}");
+ yield $file => false;
+ continue;
+ }
+
+ if (file_exists($realPath) && is_readable($realPath)) {
+ require_once $realPath;
yield $file => true;
} else {
yield $file => false;
@@ -1400,18 +1443,35 @@ final class HVAC_Plugin {
}
/**
- * Load feature files with memory-efficient generator
- *
+ * Load feature files with memory-efficient generator and security validation
+ *
* @param string[] $files Array of file paths
* @return Generator File path => load status
*/
private function loadFeatureFiles(array $files): Generator {
foreach ($files as $file) {
+ // SECURITY FIX: Validate file path to prevent directory traversal
+ if (!$this->isSecureFilePath($file)) {
+ error_log("HVAC Plugin Security: Rejected insecure feature file path: {$file}");
+ yield $file => 'security_violation';
+ continue;
+ }
+
$filePath = HVAC_PLUGIN_DIR . 'includes/' . $file;
-
- if (file_exists($filePath)) {
+
+ // SECURITY FIX: Ensure file is within includes directory
+ $realPath = realpath($filePath);
+ $includesRealPath = realpath(HVAC_PLUGIN_DIR . 'includes/');
+
+ if ($realPath === false || strpos($realPath, $includesRealPath) !== 0) {
+ error_log("HVAC Plugin Security: Feature file path outside includes directory: {$file}");
+ yield $file => 'security_violation';
+ continue;
+ }
+
+ if (file_exists($realPath) && is_readable($realPath)) {
try {
- require_once $filePath;
+ require_once $realPath;
yield $file => 'loaded';
} catch (Exception $e) {
error_log("Failed to load feature file {$file}: " . $e->getMessage());
@@ -1449,6 +1509,54 @@ final class HVAC_Plugin {
}
}
+ /**
+ * SECURITY FIX: Validate file path for security
+ *
+ * @param string $file File path to validate
+ * @return bool True if path is secure, false otherwise
+ */
+ private function isSecureFilePath(string $file): bool {
+ // Reject paths with directory traversal attempts
+ if (strpos($file, '..') !== false) {
+ return false;
+ }
+
+ // Reject absolute paths
+ if (strpos($file, '/') === 0 || strpos($file, '\\') === 0) {
+ return false;
+ }
+
+ // Reject paths with null bytes (PHP injection)
+ if (strpos($file, "\0") !== false) {
+ return false;
+ }
+
+ // Only allow PHP files
+ if (!str_ends_with(strtolower($file), '.php')) {
+ return false;
+ }
+
+ // Reject common attack patterns
+ $dangerousPatterns = [
+ 'php://',
+ 'data://',
+ 'http://',
+ 'https://',
+ 'ftp://',
+ 'phar://',
+ 'expect://',
+ 'zip://'
+ ];
+
+ foreach ($dangerousPatterns as $pattern) {
+ if (stripos($file, $pattern) !== false) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
/**
* Get component initialization status
*