| @@ -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' ); |
| @@ -306,11 +311,14 @@ | ||
| 306 | 311 | |
| 307 | 312 | /** |
| 308 | 313 | * Generate rules content (without markers) |
| 309 | 314 | * |
| 315 | + * Public since 2.10.0: it is also "what this configuration would write right | |
| 316 | + * now", which is what Vigilante_Htaccess_Recovery compares the file against. | |
| 317 | + * | |
| 310 | 318 | * @return string |
| 311 | 319 | */ |
| 312 | - private function generate_rules_content() { | |
| 320 | + public function generate_rules_content() { | |
| 313 | 321 | $rules = array(); |
| 314 | 322 | |
| 315 | 323 | $rules[] = '# Vigilante - Security Headers'; |
| 316 | 324 | $rules[] = '# Generated: ' . gmdate( 'Y-m-d H:i:s' ) . ' UTC'; |
| @@ -465,22 +473,37 @@ | ||
| 465 | 473 | } |
| 466 | 474 | } |
| 467 | 475 | } |
| 468 | 476 | |
| 469 | - // Cross-Origin policies | |
| 477 | + /* | |
| 478 | + * Cross-Origin policies. | |
| 479 | + * | |
| 480 | + * Allow-listed rather than escaped, for the same reason sanitize_header_value() | |
| 481 | + * runs at generation time: these three became editable in 2.10.0 and their value | |
| 482 | + * is written verbatim inside a Header directive. Only the tokens the specs define | |
| 483 | + * are ever emitted, so an unexpected value (a crafted request, an imported | |
| 484 | + * settings file, a direct write to the option) drops the header instead of | |
| 485 | + * reaching the .htaccess. | |
| 486 | + * | |
| 487 | + * COEP deliberately omits unsafe-none: it is the browser default, so emitting it | |
| 488 | + * adds nothing. That was the behaviour before this list existed and it is kept. | |
| 489 | + */ | |
| 470 | 490 | if ( ! empty( $this->options['cross_origin_policies'] ) ) { |
| 471 | 491 | $policies = $this->options['cross_origin_policies']; |
| 472 | 492 | |
| 473 | - if ( ! empty( $policies['embedder_policy'] ) && 'unsafe-none' !== $policies['embedder_policy'] ) { | |
| 474 | - $rules[] = ' Header always set Cross-Origin-Embedder-Policy "' . $this->sanitize_header_value( $policies['embedder_policy'] ) . '"'; | |
| 493 | + $coep = isset( $policies['embedder_policy'] ) ? (string) $policies['embedder_policy'] : ''; | |
| 494 | + if ( in_array( $coep, array( 'require-corp', 'credentialless' ), true ) ) { | |
| 495 | + $rules[] = ' Header always set Cross-Origin-Embedder-Policy "' . $coep . '"'; | |
| 475 | 496 | } |
| 476 | 497 | |
| 477 | - if ( ! empty( $policies['opener_policy'] ) ) { | |
| 478 | - $rules[] = ' Header always set Cross-Origin-Opener-Policy "' . $this->sanitize_header_value( $policies['opener_policy'] ) . '"'; | |
| 498 | + $coop = isset( $policies['opener_policy'] ) ? (string) $policies['opener_policy'] : ''; | |
| 499 | + if ( in_array( $coop, array( 'unsafe-none', 'same-origin-allow-popups', 'same-origin' ), true ) ) { | |
| 500 | + $rules[] = ' Header always set Cross-Origin-Opener-Policy "' . $coop . '"'; | |
| 479 | 501 | } |
| 480 | 502 | |
| 481 | - if ( ! empty( $policies['resource_policy'] ) ) { | |
| 482 | - $rules[] = ' Header always set Cross-Origin-Resource-Policy "' . $this->sanitize_header_value( $policies['resource_policy'] ) . '"'; | |
| 503 | + $corp = isset( $policies['resource_policy'] ) ? (string) $policies['resource_policy'] : ''; | |
| 504 | + if ( in_array( $corp, array( 'same-site', 'same-origin', 'cross-origin' ), true ) ) { | |
| 505 | + $rules[] = ' Header always set Cross-Origin-Resource-Policy "' . $corp . '"'; | |
| 483 | 506 | } |
| 484 | 507 | } |
| 485 | 508 | |
| 486 | 509 | // Remove X-Powered-By |
| @@ -489,43 +512,8 @@ | ||
| 489 | 512 | |
| 490 | 513 | $rules[] = '</IfModule>'; |
| 491 | 514 | |
| 492 | 515 | return implode( "\n", $rules ); |
| 493 | - } | |
| 494 | - | |
| 495 | - /** | |
| 496 | - * Get headers preview | |
| 497 | - * | |
| 498 | - * @return array | |
| 499 | - */ | |
| 500 | - public function get_headers_preview() { | |
| 501 | - $headers = array(); | |
| 502 | - | |
| 503 | - if ( ! empty( $this->options['x_frame_options'] ) ) { | |
| 504 | - $headers['X-Frame-Options'] = $this->options['x_frame_options']; | |
| 505 | - } | |
| 506 | - | |
| 507 | - if ( ! empty( $this->options['x_content_type_options'] ) ) { | |
| 508 | - $headers['X-Content-Type-Options'] = 'nosniff'; | |
| 509 | - } | |
| 510 | - | |
| 511 | - if ( ! empty( $this->options['referrer_policy'] ) ) { | |
| 512 | - $headers['Referrer-Policy'] = $this->options['referrer_policy']; | |
| 513 | - } | |
| 514 | - | |
| 515 | - if ( ! empty( $this->options['hsts']['enabled'] ) ) { | |
| 516 | - $hsts = $this->options['hsts']; | |
| 517 | - $value = 'max-age=' . absint( $hsts['max_age'] ); | |
| 518 | - if ( ! empty( $hsts['include_subdomains'] ) ) { | |
| 519 | - $value .= '; includeSubDomains'; | |
| 520 | - } | |
| 521 | - if ( ! empty( $hsts['preload'] ) ) { | |
| 522 | - $value .= '; preload'; | |
| 523 | - } | |
| 524 | - $headers['Strict-Transport-Security'] = $value; | |
| 525 | - } | |
| 526 | - | |
| 527 | - return $headers; | |
| 528 | 516 | } |
| 529 | 517 | |
| 530 | 518 | /** |
| 531 | 519 | * Get security grade based on enabled headers |