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 +63 -95 2.10.2 → 3.0.5 View file →
@@ -309,8 +309,28 @@
309 309 return str_replace( array( '"', "\r", "\n" ), '', (string) $value );
310 310 }
311 311
312 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 + /**
313 333 * Generate rules content (without markers)
314 334 *
315 335 * Public since 2.10.0: it is also "what this configuration would write right
316 336 * now", which is what Vigilante_Htaccess_Recovery compares the file against.
@@ -414,9 +434,25 @@
414 434 }
415 435 }
416 436
417 437 if ( ! empty( $csp['directives'] ) && is_array( $csp['directives'] ) ) {
438 + $site_is_https = $this->site_address_is_https();
439 +
418 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 +
419 455 // Override frame-ancestors with synchronized value if X-Frame-Options is set
420 456 if ( 'frame-ancestors' === $directive && ! empty( $frame_ancestors_value ) ) {
421 457 $directives[] = $directive . ' ' . $frame_ancestors_value;
422 458 } elseif ( true === $value ) {
@@ -515,114 +551,46 @@
515 551 return implode( "\n", $rules );
516 552 }
517 553
518 554 /**
519 - * Get security grade based on enabled headers
555 + * Warnings about the Content-Security-Policy as it is configured
520 556 *
521 - * @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[]
522 566 */
523 - public function get_security_grade() {
524 - $score = 0;
525 - $enabled = array();
526 - $missing = array();
567 + public function get_csp_warnings() {
527 568 $warnings = array();
528 569
529 - // X-Frame-Options (15 points)
530 - if ( ! empty( $this->options['x_frame_options'] ) ) {
531 - $score += 15;
532 - $enabled[] = 'X-Frame-Options: ' . $this->options['x_frame_options'];
533 - } else {
534 - $missing[] = 'X-Frame-Options';
570 + if ( empty( $this->options['csp']['enabled'] ) ) {
571 + return $warnings;
535 572 }
536 573
537 - // X-Content-Type-Options (15 points)
538 - if ( ! empty( $this->options['x_content_type_options'] ) ) {
539 - $score += 15;
540 - $enabled[] = 'X-Content-Type-Options: nosniff';
541 - } else {
542 - $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;
543 577 }
544 578
545 - // HSTS (20 points)
546 - if ( ! empty( $this->options['hsts']['enabled'] ) ) {
547 - $hsts = $this->options['hsts'];
548 - if ( $hsts['max_age'] >= 31536000 ) {
549 - $score += 20;
550 - } else {
551 - $score += 10;
552 - $warnings[] = 'HSTS max-age should be at least 1 year (31536000 seconds)';
553 - }
554 - $enabled[] = 'Strict-Transport-Security';
555 - } else {
556 - $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' );
557 581 }
558 582
559 - // CSP (30 points; absorbs the 10 points freed by retiring the deprecated X-XSS-Protection)
560 - if ( ! empty( $this->options['csp']['enabled'] ) ) {
561 - if ( empty( $this->options['csp']['report_only'] ) ) {
562 - $score += 30;
563 - $enabled[] = 'Content-Security-Policy';
564 -
565 - // Add warning if CSP is restrictive
566 - if ( $this->is_csp_restrictive() ) {
567 - $warnings[] = __( 'Restrictive CSP detected. Admin area is automatically excluded to prevent breaking the dashboard and block editor.', 'vigilante' );
568 - }
569 -
570 - // Check for specific compatibility issues
571 - $csp_issues = $this->get_csp_compatibility_issues();
572 - foreach ( $csp_issues as $issue ) {
573 - if ( 'high' === $issue['severity'] ) {
574 - $warnings[] = sprintf(
575 - /* translators: 1: CSP directive name, 2: Issue description */
576 - __( 'CSP %1$s: %2$s', 'vigilante' ),
577 - $issue['directive'],
578 - $issue['issue']
579 - );
580 - }
581 - }
582 - } else {
583 - $score += 15;
584 - $enabled[] = 'Content-Security-Policy-Report-Only';
585 - $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 + );
586 591 }
587 - } else {
588 - $missing[] = 'Content-Security-Policy';
589 592 }
590 593
591 - // Referrer-Policy (10 points)
592 - if ( ! empty( $this->options['referrer_policy'] ) ) {
593 - $score += 10;
594 - $enabled[] = 'Referrer-Policy: ' . $this->options['referrer_policy'];
595 - } else {
596 - $missing[] = 'Referrer-Policy';
597 - }
598 -
599 - // Permissions-Policy (10 points)
600 - if ( ! empty( $this->options['permissions_policy']['enabled'] ) ) {
601 - $score += 10;
602 - $enabled[] = 'Permissions-Policy';
603 - } else {
604 - $missing[] = 'Permissions-Policy';
605 - }
606 -
607 - // Calculate grade
608 - if ( $score >= 90 ) {
609 - $grade = 'A';
610 - } elseif ( $score >= 80 ) {
611 - $grade = 'B';
612 - } elseif ( $score >= 70 ) {
613 - $grade = 'C';
614 - } elseif ( $score >= 60 ) {
615 - $grade = 'D';
616 - } else {
617 - $grade = 'F';
618 - }
619 -
620 - return array(
621 - 'grade' => $grade,
622 - 'score' => $score,
623 - 'headers' => $enabled,
624 - 'missing' => $missing,
625 - 'warnings' => $warnings,
626 - );
594 + return $warnings;
627 595 }
628 596 }