PluginProbe
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… / 3.0.5
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… v3.0.5
3.0.5 3.0.4 3.0.3 3.0.2 3.0.1 3.0.0 2.11.12 2.11.11 2.11.10 2.11.9 2.11.7 2.11.8 2.11.6 2.11.5 2.11.4 2.11.3 2.11.1 2.11.2 2.11.0 2.10.5 2.10.4 2.10.3 2.10.2 2.10.1 2.10.0 All 93 releases
← All changes | includes/class-security-headers.php +70 -97 2.10.0 → 3.0.5 View file →
@@ -53,9 +53,14 @@
53 53 * Apply security headers to .htaccess
54 54 *
55 55 * @return bool|WP_Error
56 56 */
57 - public function apply_rules() {
57 + /**
58 + * @param bool $automatic True when Vigilant is refreshing the block by
59 + * itself rather than because someone pressed Save.
60 + * See Vigilante_Htaccess_Manager::add_block().
61 + */
62 + public function apply_rules( $automatic = false ) {
58 63 require_once VIGILANTE_INCLUDES_DIR . 'class-htaccess-manager.php';
59 64
60 65 $manager = Vigilante_Htaccess_Manager::get_instance();
61 66
@@ -68,9 +73,9 @@
68 73 }
69 74
70 75 $rules = $this->generate_rules_content();
71 76
72 - $result = $manager->add_block( self::MARKER_START, self::MARKER_END, $rules, 'top' );
77 + $result = $manager->add_block( self::MARKER_START, self::MARKER_END, $rules, 'top', $automatic );
73 78
74 79 if ( true === $result ) {
75 80 /** This action is documented in class-wpconfig-security.php */
76 81 do_action( 'vigilante_critical_file_written', '.htaccess' );
@@ -304,8 +309,28 @@
304 309 return str_replace( array( '"', "\r", "\n" ), '', (string) $value );
305 310 }
306 311
307 312 /**
313 + * Whether the site address is an https:// one
314 + *
315 + * Read from the stored address and from nothing else. is_ssl() is false
316 + * behind a proxy that terminates TLS and says nothing in a cron run or from
317 + * WP-CLI, and home_url() takes its scheme from is_ssl() when the request is
318 + * secure (wp-includes/link-template.php:3471-3477 in 7.1.2), so the same site
319 + * would write different rules from an https request and from a scheduled
320 + * task. Vigilante_Htaccess_Recovery and File
321 + * Integrity compare the file with what this class would write now, so the
322 + * answer has to be the same wherever it is asked.
323 + *
324 + * @since 3.0.2
325 + *
326 + * @return bool
327 + */
328 + private function site_address_is_https() {
329 + return 0 === strpos( (string) get_option( 'home' ), 'https://' );
330 + }
331 +
332 + /**
308 333 * Generate rules content (without markers)
309 334 *
310 335 * Public since 2.10.0: it is also "what this configuration would write right
311 336 * now", which is what Vigilante_Htaccess_Recovery compares the file against.
@@ -409,9 +434,25 @@
409 434 }
410 435 }
411 436
412 437 if ( ! empty( $csp['directives'] ) && is_array( $csp['directives'] ) ) {
438 + $site_is_https = $this->site_address_is_https();
439 +
413 440 foreach ( $csp['directives'] as $directive => $value ) {
441 + /*
442 + * upgrade-insecure-requests tells the browser to ask for every
443 + * http:// resource of the page over https. On a site whose
444 + * address is http:// nothing answers there, so every
445 + * stylesheet, script and image stops loading, and the factory
446 + * policy carries it. It is written only when the site address
447 + * is https://. The setting itself is left as it is, so the
448 + * next rewrite of the block after the address changes writes
449 + * it.
450 + */
451 + if ( 'upgrade-insecure-requests' === strtolower( (string) $directive ) && ! $site_is_https ) {
452 + continue;
453 + }
454 +
414 455 // Override frame-ancestors with synchronized value if X-Frame-Options is set
415 456 if ( 'frame-ancestors' === $directive && ! empty( $frame_ancestors_value ) ) {
416 457 $directives[] = $directive . ' ' . $frame_ancestors_value;
417 458 } elseif ( true === $value ) {
@@ -510,114 +551,46 @@
510 551 return implode( "\n", $rules );
511 552 }
512 553
513 554 /**
514 - * Get security grade based on enabled headers
555 + * Warnings about the Content-Security-Policy as it is configured
515 556 *
516 - * @return array
557 + * They are about what is saved in the tab, not about what reaches the
558 + * browser: that is measured by the checks of Security Check, which is
559 + * also what the Test Headers button shows. Until 3.0.4 that button graded
560 + * these settings and listed them as enabled headers without asking the
561 + * site for anything.
562 + *
563 + * @since 3.0.5
564 + *
565 + * @return string[]
517 566 */
518 - public function get_security_grade() {
519 - $score = 0;
520 - $enabled = array();
521 - $missing = array();
567 + public function get_csp_warnings() {
522 568 $warnings = array();
523 569
524 - // X-Frame-Options (15 points)
525 - if ( ! empty( $this->options['x_frame_options'] ) ) {
526 - $score += 15;
527 - $enabled[] = 'X-Frame-Options: ' . $this->options['x_frame_options'];
528 - } else {
529 - $missing[] = 'X-Frame-Options';
570 + if ( empty( $this->options['csp']['enabled'] ) ) {
571 + return $warnings;
530 572 }
531 573
532 - // X-Content-Type-Options (15 points)
533 - if ( ! empty( $this->options['x_content_type_options'] ) ) {
534 - $score += 15;
535 - $enabled[] = 'X-Content-Type-Options: nosniff';
536 - } else {
537 - $missing[] = 'X-Content-Type-Options';
574 + if ( ! empty( $this->options['csp']['report_only'] ) ) {
575 + $warnings[] = __( 'CSP is in report-only mode: the browser reports what the policy would block, and blocks nothing.', 'vigilante' );
576 + return $warnings;
538 577 }
539 578
540 - // HSTS (20 points)
541 - if ( ! empty( $this->options['hsts']['enabled'] ) ) {
542 - $hsts = $this->options['hsts'];
543 - if ( $hsts['max_age'] >= 31536000 ) {
544 - $score += 20;
545 - } else {
546 - $score += 10;
547 - $warnings[] = 'HSTS max-age should be at least 1 year (31536000 seconds)';
548 - }
549 - $enabled[] = 'Strict-Transport-Security';
550 - } else {
551 - $missing[] = 'Strict-Transport-Security (HSTS)';
579 + if ( $this->is_csp_restrictive() ) {
580 + $warnings[] = __( 'Restrictive CSP detected. Admin area is automatically excluded to prevent breaking the dashboard and block editor.', 'vigilante' );
552 581 }
553 582
554 - // CSP (30 points; absorbs the 10 points freed by retiring the deprecated X-XSS-Protection)
555 - if ( ! empty( $this->options['csp']['enabled'] ) ) {
556 - if ( empty( $this->options['csp']['report_only'] ) ) {
557 - $score += 30;
558 - $enabled[] = 'Content-Security-Policy';
559 -
560 - // Add warning if CSP is restrictive
561 - if ( $this->is_csp_restrictive() ) {
562 - $warnings[] = __( 'Restrictive CSP detected. Admin area is automatically excluded to prevent breaking the dashboard and block editor.', 'vigilante' );
563 - }
564 -
565 - // Check for specific compatibility issues
566 - $csp_issues = $this->get_csp_compatibility_issues();
567 - foreach ( $csp_issues as $issue ) {
568 - if ( 'high' === $issue['severity'] ) {
569 - $warnings[] = sprintf(
570 - /* translators: 1: CSP directive name, 2: Issue description */
571 - __( 'CSP %1$s: %2$s', 'vigilante' ),
572 - $issue['directive'],
573 - $issue['issue']
574 - );
575 - }
576 - }
577 - } else {
578 - $score += 15;
579 - $enabled[] = 'Content-Security-Policy-Report-Only';
580 - $warnings[] = 'CSP is in report-only mode (recommended for testing)';
583 + foreach ( $this->get_csp_compatibility_issues() as $issue ) {
584 + if ( 'high' === $issue['severity'] ) {
585 + $warnings[] = sprintf(
586 + /* translators: 1: CSP directive name, 2: Issue description */
587 + __( 'CSP %1$s: %2$s', 'vigilante' ),
588 + $issue['directive'],
589 + $issue['issue']
590 + );
581 591 }
582 - } else {
583 - $missing[] = 'Content-Security-Policy';
584 592 }
585 593
586 - // Referrer-Policy (10 points)
587 - if ( ! empty( $this->options['referrer_policy'] ) ) {
588 - $score += 10;
589 - $enabled[] = 'Referrer-Policy: ' . $this->options['referrer_policy'];
590 - } else {
591 - $missing[] = 'Referrer-Policy';
592 - }
593 -
594 - // Permissions-Policy (10 points)
595 - if ( ! empty( $this->options['permissions_policy']['enabled'] ) ) {
596 - $score += 10;
597 - $enabled[] = 'Permissions-Policy';
598 - } else {
599 - $missing[] = 'Permissions-Policy';
600 - }
601 -
602 - // Calculate grade
603 - if ( $score >= 90 ) {
604 - $grade = 'A';
605 - } elseif ( $score >= 80 ) {
606 - $grade = 'B';
607 - } elseif ( $score >= 70 ) {
608 - $grade = 'C';
609 - } elseif ( $score >= 60 ) {
610 - $grade = 'D';
611 - } else {
612 - $grade = 'F';
613 - }
614 -
615 - return array(
616 - 'grade' => $grade,
617 - 'score' => $score,
618 - 'headers' => $enabled,
619 - 'missing' => $missing,
620 - 'warnings' => $warnings,
621 - );
594 + return $warnings;
622 595 }
623 596 }