16patsle/wordpress-csp-manager

add_filter

Opened this issue · 2 comments

buzi commented

Hi, do you provide a filters (add_filter) so rules including dynamically generated nonce-xxx can be merged into the generated header ?
This could be a great feature.
thanks

A dedicated filter to customize the policies before they are output as HTTP Headers would indeed be appreciated.

Until then, I've created the following workaround that ensures any dynamically changed directives/policies are not persisted to the database (when saving changes from the plugin's settings page):

/**
 * Determines if the CSP Manager is preparing to output HTTP Headers.
 *
 * Uses a PHP backtrace to locate calls from {@see \CSP_Manager\Core::csp_init()}.
 */
function is_csp_manager_doing_http_headers(): bool {
	foreach ( debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 6 ) as $call ) {
		if (
			isset( $call['function'], $call['class'] ) &&
			'csp_init' === $call['function'] &&
			'CSP_Manager\Core' === $call['class']
		) {
			return true;
		}
	}

	return false;
}

/**
 * Customizes the CSP Manager policies to include
 * the staging environment for remote assets.
 *
 * @listens filter:option_csp_manager_admin
 * @listens filter:option_csp_manager_loggedin
 * @listens filter:option_csp_manager_frontend
 *
 * @param  mixed  $settings The CSP directives, policies, and options.
 * @return mixed
 */
function filter_csp_manager_customize_settings( mixed $settings ): mixed {
	if (
		$settings === false ||
		! isset( $settings['mode'] ) ||
		'disabled' === $settings['mode'] ||
		! is_csp_manager_doing_http_headers()
	) {
		return $settings;
	}

	$directives = [
		'img-src' => 'https://example.com',
	];

	foreach ( $directives as $directive => $policy ) {
		if (
			isset( $settings[ 'enable_' . $directive ] ) &&
			$settings[ 'enable_' . $directive ]
		) {
			$settings[ $directive ] = (
				isset( $settings[ $directive ] )
				? $settings[ $directive ] . ' '
				: ''
			) . $policy;
		}
	}

	return $settings;
}

add_action( 'option_csp_manager_admin',    'filter_csp_manager_customize_settings', 10, 1 );
add_action( 'option_csp_manager_loggedin', 'filter_csp_manager_customize_settings', 10, 1 );
add_action( 'option_csp_manager_frontend', 'filter_csp_manager_customize_settings', 10, 1 );

What this does is mutate the three main options of the CSP Manager:

  • csp_manager_admin
  • csp_manager_loggedin
  • csp_manager_frontend

Via the option_{$option} hook.

The function is_csp_manager_doing_http_headers() is used to search through a slice of the PHP backtrace to determine if the filter function filter_csp_manager_customize_settings() was called from CSP_Manager\Core::csp_init() which handles the output of HTTP Headers.

The main purpose of this filter function, for my use case, is to append our staging environment's hostname (using example.com for demonstration) to the img-src directive to allow my localhost to fetch images from the staging environment if they are absent locally.

buzi commented

Fantastic. i will try it out ...