PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / trunk
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO vtrunk
2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 1.11.0 All 47 releases
thinkrank / includes / core / class-security-headers.php

class-security-headers.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO trunk, at includes/core/class-security-headers.php

205 lines 6.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Security Headers Manager
4 *
5 * Manages security headers for the ThinkRank plugin to prevent
6 * XSS, clickjacking, and other security vulnerabilities.
7 *
8 * @package ThinkRank
9 * @subpackage Core
10 * @since 1.0.0
11 */
12
13 declare(strict_types=1);
14
15 namespace ThinkRank\Core;
16
17 // Prevent direct access
18 if (!defined('ABSPATH')) {
19 exit;
20 }
21
22 /**
23 * Security Headers Manager Class
24 *
25 * Implements basic security headers for admin pages and API endpoints
26 * to enhance security posture.
27 *
28 * @since 1.0.0
29 */
30 class Security_Headers {
31
32 /**
33 * Initialize security headers
34 *
35 * @since 1.0.0
36 */
37 public function __construct() {
38 add_action('admin_init', [$this, 'add_admin_security_headers']);
39 add_action('rest_api_init', [$this, 'add_api_security_headers']);
40 }
41
42 /**
43 * Add security headers for admin pages
44 *
45 * @since 1.0.0
46 */
47 public function add_admin_security_headers(): void {
48 // Only add headers on ThinkRank admin pages
49 if (!$this->is_thinkrank_admin_page()) {
50 return;
51 }
52
53 // Prevent clickjacking
54 if (!headers_sent()) {
55 header('X-Frame-Options: SAMEORIGIN');
56 header('X-Content-Type-Options: nosniff');
57 header('X-XSS-Protection: 1; mode=block');
58 header('Referrer-Policy: strict-origin-when-cross-origin');
59
60 // Basic CSP for admin pages
61 $csp = $this->get_admin_csp_policy();
62 header("Content-Security-Policy: {$csp}");
63 }
64 }
65
66 /**
67 * Add security headers for API endpoints
68 *
69 * @since 1.0.0
70 */
71 public function add_api_security_headers(): void {
72 add_filter('rest_pre_serve_request', [$this, 'add_rest_security_headers'], 10, 4);
73 }
74
75 /**
76 * Add security headers to REST API responses
77 *
78 * @since 1.0.0
79 *
80 * @param bool $served Whether the request has already been served
81 * @param WP_HTTP_Response $result Result to send to the client
82 * @param WP_REST_Request $request Request used to generate the response
83 * @param WP_REST_Server $server Server instance
84 * @return bool
85 */
86 public function add_rest_security_headers($served, $result, $request, $server): bool {
87 // Only add headers to ThinkRank API endpoints
88 if (!$this->is_thinkrank_api_request($request)) {
89 return $served;
90 }
91
92 if (!headers_sent()) {
93 header('X-Content-Type-Options: nosniff');
94 header('X-Frame-Options: DENY');
95 header('X-XSS-Protection: 1; mode=block');
96 header('Referrer-Policy: strict-origin');
97
98 // CORS headers for API
99 $this->add_cors_headers();
100 }
101
102 return $served;
103 }
104
105 /**
106 * Check if current page is a ThinkRank admin page
107 *
108 * @since 1.0.0
109 *
110 * @return bool
111 */
112 private function is_thinkrank_admin_page(): bool {
113 if (!is_admin()) {
114 return false;
115 }
116
117 $screen = get_current_screen();
118 if (!$screen) {
119 return false;
120 }
121
122 // Check if it's a ThinkRank admin page
123 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading page parameter for screen detection, not processing form data.
124 return strpos($screen->id, 'thinkrank') !== false ||
125 strpos($screen->base, 'thinkrank') !== false ||
126 (isset($_GET['page']) && strpos(sanitize_text_field(wp_unslash($_GET['page'])), 'thinkrank') !== false); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
127 }
128
129 /**
130 * Check if request is to a ThinkRank API endpoint
131 *
132 * @since 1.0.0
133 *
134 * @param WP_REST_Request $request Request object
135 * @return bool
136 */
137 private function is_thinkrank_api_request($request): bool {
138 $route = $request->get_route();
139 return strpos($route, '/thinkrank/') === 0;
140 }
141
142 /**
143 * Get Content Security Policy for admin pages
144 *
145 * @since 1.0.0
146 *
147 * @return string CSP policy string
148 */
149 private function get_admin_csp_policy(): string {
150 $site_url = get_site_url();
151 $admin_url = admin_url();
152
153 $policies = [
154 "default-src 'self'",
155 // 'unsafe-eval' dropped — the React admin bundle doesn't need it. We
156 // still allow 'unsafe-inline' because WordPress core emits unnonced
157 // inline admin scripts; moving the SPA to nonces/hashes is tracked
158 // separately and can't be done without breaking core admin output.
159 "script-src 'self' 'unsafe-inline' {$site_url} {$admin_url}",
160 "style-src 'self' 'unsafe-inline' {$site_url} {$admin_url}",
161 "img-src 'self' data: {$site_url}",
162 "font-src 'self' {$site_url}",
163 "connect-src 'self' {$site_url} https://api.openai.com https://api.anthropic.com https://generativelanguage.googleapis.com",
164 "frame-src 'none'",
165 "object-src 'none'",
166 "base-uri 'self'"
167 ];
168
169 return implode('; ', $policies);
170 }
171
172 /**
173 * Add CORS headers for API endpoints
174 *
175 * @since 1.0.0
176 */
177 private function add_cors_headers(): void {
178 $origin = get_site_url();
179
180 header("Access-Control-Allow-Origin: {$origin}");
181 header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
182 header('Access-Control-Allow-Headers: Content-Type, Authorization, X-WP-Nonce');
183 header('Access-Control-Allow-Credentials: true');
184 header('Access-Control-Max-Age: 86400'); // 24 hours
185 }
186
187 /**
188 * Get security headers status
189 *
190 * @since 1.0.0
191 *
192 * @return array Security headers status
193 */
194 public function get_security_status(): array {
195 return [
196 'headers_enabled' => true,
197 'csp_enabled' => true,
198 'cors_configured' => true,
199 'xss_protection' => true,
200 'clickjacking_protection' => true,
201 'content_type_protection' => true
202 ];
203 }
204 }
205