PluginProbe
phpinfo() WP – Site Health, PHP Compatibility & Server Audit / 7.2.6
phpinfo() WP – Site Health, PHP Compatibility & Server Audit v7.2.6
7.2.7 7.2.6 7.2.5 7.2.4 7.2.3 7.2.0 7.2.1 7.2.2 7.1.0 7.0.3 7.0.4 7.0.5 trunk 6.0 7.0.0 7.0.1 7.0.2
phpinfo-wp / includes / class-security-headers.php

class-security-headers.php in phpinfo() WP – Site Health, PHP Compatibility & Server Audit 7.2.6, at includes/class-security-headers.php

114 lines 4.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 defined('ABSPATH') or die('Unauthorized Access');
3
4 class Phpinfo_WP_Security_Headers {
5
6 private static function _pro(): bool { return Phpinfo_WP_License::is_valid(); }
7
8 // Points per header — total 100
9 /**
10 * @var mixed[]
11 */
12 private static $headers = [
13 'content-security-policy' => ['label' => 'Content-Security-Policy', 'points' => 30, 'desc' => 'Prevents XSS and data injection attacks by declaring approved content sources.'],
14 'strict-transport-security' => ['label' => 'Strict-Transport-Security', 'points' => 25, 'desc' => 'Forces HTTPS connections, preventing protocol downgrade attacks.'],
15 'x-frame-options' => ['label' => 'X-Frame-Options', 'points' => 15, 'desc' => 'Prevents clickjacking by controlling whether the page can be framed.'],
16 'x-content-type-options' => ['label' => 'X-Content-Type-Options', 'points' => 15, 'desc' => 'Stops MIME-type sniffing, forcing the declared Content-Type.'],
17 'referrer-policy' => ['label' => 'Referrer-Policy', 'points' => 10, 'desc' => 'Controls how much referrer information is sent with requests.'],
18 'permissions-policy' => ['label' => 'Permissions-Policy', 'points' => 5, 'desc' => 'Restricts which browser features the page can use (camera, mic, etc.).'],
19 ];
20
21 public static function audit(string $url = ''): array {
22 if (!$url) $url = get_site_url();
23
24 $response = wp_remote_head($url, [
25 'timeout' => 15,
26 'redirection'=> 5,
27 'sslverify' => false,
28 'user-agent' => 'phpinfo-WP-auditor/' . PHPINFOWP_VERSION,
29 ]);
30
31 if (is_wp_error($response)) {
32 return ['error' => $response->get_error_message()];
33 }
34
35 $raw = wp_remote_retrieve_headers($response);
36 $status = wp_remote_retrieve_response_code($response);
37 $results = [];
38 $score = 0;
39
40 foreach (self::$headers as $key => $meta) {
41 $value = $raw[$key] ?? null;
42 $present = $value !== null;
43 if ($present) $score += $meta['points'];
44
45 $results[] = [
46 'key' => $key,
47 'label' => $meta['label'],
48 'points' => $meta['points'],
49 'desc' => $meta['desc'],
50 'present' => $present,
51 'value' => $present ? (string) $value : null,
52 'warning' => self::_warn($key, $present ? (string) $value : null),
53 ];
54 }
55
56 return [
57 'url' => $url,
58 'status' => $status,
59 'results' => $results,
60 'score' => $score,
61 'grade' => self::_grade($score),
62 'cached' => false,
63 ];
64 }
65
66 // Results are expensive (HTTP call) — cache per site for 1 hour
67 public static function get_cached(): array {
68 $cached = get_transient('phpinfowp_sec_headers');
69 if ($cached !== false) {
70 $cached['cached'] = true;
71 return $cached;
72 }
73 $result = self::audit();
74 if (!isset($result['error'])) {
75 set_transient('phpinfowp_sec_headers', $result, HOUR_IN_SECONDS);
76 }
77 return $result;
78 }
79
80 public static function bust_cache(): void {
81 delete_transient('phpinfowp_sec_headers');
82 }
83
84 private static function _grade(int $score): string {
85 if ($score >= 95) return 'A+';
86 if ($score >= 80) return 'A';
87 if ($score >= 65) return 'B';
88 if ($score >= 50) return 'C';
89 if ($score >= 35) return 'D';
90 return 'F';
91 }
92
93 // Returns a short actionable warning if header is missing or misconfigured
94 private static function _warn(string $key, ?string $value): ?string {
95 if ($value === null) return 'Missing — add this header in your web server config or .htaccess.';
96
97 switch ($key) {
98 case 'strict-transport-security':
99 if (strpos($value, 'max-age') === false) return 'max-age directive is missing.';
100 preg_match('/max-age=(\d+)/', $value, $m);
101 if (isset($m[1]) && (int)$m[1] < 31536000) return 'max-age is below recommended 31536000 (1 year).';
102 break;
103 case 'x-frame-options':
104 $v = strtoupper($value);
105 if (!in_array($v, ['DENY', 'SAMEORIGIN'], true)) return 'Value should be DENY or SAMEORIGIN.';
106 break;
107 case 'x-content-type-options':
108 if (strtolower(trim($value)) !== 'nosniff') return 'Value must be exactly "nosniff".';
109 break;
110 }
111 return null;
112 }
113 }
114