You've already forked wp-prometheus
security: Fix XSS, insecure token generation, and harden import/export (v0.4.9)
Security audit findings addressed: - Replace jQuery .html() with safe .text() DOM construction (XSS prevention) - Use crypto.getRandomValues() instead of Math.random() for token generation - Add 1MB import size limit to prevent DoS via large JSON payloads - Remove site_url from metric exports (information disclosure) - Add import mode allowlist validation Refactoring: - Extract shared wp_prometheus_authenticate_request() function (DRY) - Extract showNotice() helper in admin.js (DRY) - Extract is_hpos_enabled() helper in Collector (DRY) Performance: - Optimize WooCommerce product counting with paginate COUNT query Housekeeping: - Add missing options to Installer::uninstall() cleanup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
19
CHANGELOG.md
19
CHANGELOG.md
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.4.9] - 2026-02-26
|
||||
|
||||
### Security
|
||||
|
||||
- Fixed XSS vulnerability: replaced all jQuery `.html()` injections with safe `.text()` DOM construction in admin.js
|
||||
- Fixed insecure token generation: replaced `Math.random()` with `crypto.getRandomValues()` (Web Crypto API)
|
||||
- Fixed XSS via string interpolation in `updateValueRows()`: replaced HTML string building with jQuery DOM construction
|
||||
- Added 1 MB import size limit to prevent DoS via large JSON payloads in CustomMetricBuilder
|
||||
- Removed `site_url` from metric export data to prevent information disclosure
|
||||
- Added import mode validation (allowlist check) in CustomMetricBuilder
|
||||
|
||||
### Changed
|
||||
|
||||
- Extracted shared authentication logic (`wp_prometheus_authenticate_request()`) to eliminate code duplication between MetricsEndpoint and isolated mode handler
|
||||
- Extracted `showNotice()` helper in admin.js to DRY up 10+ duplicated AJAX response handling patterns
|
||||
- Extracted `is_hpos_enabled()` helper method in Collector to DRY up WooCommerce HPOS checks
|
||||
- Optimized WooCommerce product type counting: uses `paginate: true` COUNT query instead of loading all product IDs into memory
|
||||
- Added missing options to `Installer::uninstall()` cleanup (isolated_mode, storage adapter, Redis/APCu config)
|
||||
|
||||
## [0.4.8] - 2026-02-07
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -235,31 +235,19 @@
|
||||
$spinner.removeClass('is-active');
|
||||
|
||||
if (response.success) {
|
||||
$message
|
||||
.removeClass('notice-error')
|
||||
.addClass('notice notice-success')
|
||||
.html('<p>' + response.data.message + '</p>')
|
||||
.show();
|
||||
showNotice($message, response.data.message, 'success');
|
||||
|
||||
// Reload page after successful validation/activation.
|
||||
setTimeout(function() {
|
||||
location.reload();
|
||||
}, 1500);
|
||||
} else {
|
||||
$message
|
||||
.removeClass('notice-success')
|
||||
.addClass('notice notice-error')
|
||||
.html('<p>' + (response.data.message || 'An error occurred.') + '</p>')
|
||||
.show();
|
||||
showNotice($message, response.data.message || 'An error occurred.', 'error');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$spinner.removeClass('is-active');
|
||||
$message
|
||||
.removeClass('notice-success')
|
||||
.addClass('notice notice-error')
|
||||
.html('<p>Connection error. Please try again.</p>')
|
||||
.show();
|
||||
showNotice($message, 'Connection error. Please try again.', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -287,30 +275,18 @@
|
||||
$spinner.removeClass('is-active');
|
||||
|
||||
if (response.success) {
|
||||
$message
|
||||
.removeClass('notice-error')
|
||||
.addClass('notice notice-success')
|
||||
.html('<p>' + response.data.message + '</p>')
|
||||
.show();
|
||||
showNotice($message, response.data.message, 'success');
|
||||
|
||||
setTimeout(function() {
|
||||
window.location.href = window.location.pathname + '?page=wp-prometheus&tab=custom';
|
||||
}, 1000);
|
||||
} else {
|
||||
$message
|
||||
.removeClass('notice-success')
|
||||
.addClass('notice notice-error')
|
||||
.html('<p>' + (response.data.message || 'An error occurred.') + '</p>')
|
||||
.show();
|
||||
showNotice($message, response.data.message || 'An error occurred.', 'error');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$spinner.removeClass('is-active');
|
||||
$message
|
||||
.removeClass('notice-success')
|
||||
.addClass('notice notice-error')
|
||||
.html('<p>Connection error. Please try again.</p>')
|
||||
.show();
|
||||
showNotice($message, 'Connection error. Please try again.', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -398,11 +374,7 @@
|
||||
$spinner.removeClass('is-active');
|
||||
|
||||
if (response.success) {
|
||||
$message
|
||||
.removeClass('notice-error')
|
||||
.addClass('notice notice-success')
|
||||
.html('<p>' + response.data.message + '</p>')
|
||||
.show();
|
||||
showNotice($message, response.data.message, 'success');
|
||||
|
||||
$('#import-options').slideUp();
|
||||
$('#import-metrics-file').val('');
|
||||
@@ -412,20 +384,12 @@
|
||||
location.reload();
|
||||
}, 1500);
|
||||
} else {
|
||||
$message
|
||||
.removeClass('notice-success')
|
||||
.addClass('notice notice-error')
|
||||
.html('<p>' + (response.data.message || 'An error occurred.') + '</p>')
|
||||
.show();
|
||||
showNotice($message, response.data.message || 'An error occurred.', 'error');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$spinner.removeClass('is-active');
|
||||
$message
|
||||
.removeClass('notice-success')
|
||||
.addClass('notice notice-error')
|
||||
.html('<p>Connection error. Please try again.</p>')
|
||||
.show();
|
||||
showNotice($message, 'Connection error. Please try again.', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -478,26 +442,14 @@
|
||||
$spinner.removeClass('is-active');
|
||||
|
||||
if (response.success) {
|
||||
$message
|
||||
.removeClass('notice-error')
|
||||
.addClass('notice notice-success')
|
||||
.html('<p>' + response.data.message + '</p>')
|
||||
.show();
|
||||
showNotice($message, response.data.message, 'success');
|
||||
} else {
|
||||
$message
|
||||
.removeClass('notice-success')
|
||||
.addClass('notice notice-error')
|
||||
.html('<p>' + (response.data.message || 'An error occurred.') + '</p>')
|
||||
.show();
|
||||
showNotice($message, response.data.message || 'An error occurred.', 'error');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$spinner.removeClass('is-active');
|
||||
$message
|
||||
.removeClass('notice-success')
|
||||
.addClass('notice notice-error')
|
||||
.html('<p>Connection error. Please try again.</p>')
|
||||
.show();
|
||||
showNotice($message, 'Connection error. Please try again.', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -546,15 +498,30 @@
|
||||
// Remove all inputs except the value and button.
|
||||
$row.find('input').remove();
|
||||
|
||||
// Re-add label inputs.
|
||||
// Re-add label inputs using safe DOM construction.
|
||||
for (var i = 0; i < labelCount; i++) {
|
||||
var val = currentValues[i] || '';
|
||||
$row.prepend('<input type="text" name="label_values[' + rowIndex + '][]" class="small-text" placeholder="' + (labels[i] || 'value') + '" value="' + val + '">');
|
||||
var $input = $('<input>', {
|
||||
type: 'text',
|
||||
name: 'label_values[' + rowIndex + '][]',
|
||||
'class': 'small-text',
|
||||
placeholder: labels[i] || 'value',
|
||||
value: val
|
||||
});
|
||||
$row.prepend($input);
|
||||
}
|
||||
|
||||
// Re-add value input.
|
||||
// Re-add value input using safe DOM construction.
|
||||
var metricVal = currentValues[currentValues.length - 1] || '';
|
||||
$row.find('.remove-value-row').before('<input type="number" name="label_values[' + rowIndex + '][]" class="small-text" step="any" placeholder="Value" value="' + metricVal + '">');
|
||||
var $valueInput = $('<input>', {
|
||||
type: 'number',
|
||||
name: 'label_values[' + rowIndex + '][]',
|
||||
'class': 'small-text',
|
||||
step: 'any',
|
||||
placeholder: 'Value',
|
||||
value: metricVal
|
||||
});
|
||||
$row.find('.remove-value-row').before($valueInput);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -584,7 +551,7 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random token.
|
||||
* Generate a cryptographically secure random token.
|
||||
*
|
||||
* @param {number} length Token length.
|
||||
* @return {string} Generated token.
|
||||
@@ -592,12 +559,31 @@
|
||||
function generateToken(length) {
|
||||
var charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
var token = '';
|
||||
var randomValues = new Uint32Array(length);
|
||||
window.crypto.getRandomValues(randomValues);
|
||||
for (var i = 0; i < length; i++) {
|
||||
token += charset.charAt(Math.floor(Math.random() * charset.length));
|
||||
token += charset.charAt(randomValues[i] % charset.length);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a notice message safely (XSS-safe).
|
||||
*
|
||||
* @param {jQuery} $element The message container element.
|
||||
* @param {string} message The message text.
|
||||
* @param {string} type Notice type: 'success', 'error', or 'warning'.
|
||||
*/
|
||||
function showNotice($element, message, type) {
|
||||
var removeClasses = 'notice-error notice-success notice-warning';
|
||||
$element
|
||||
.removeClass(removeClasses)
|
||||
.addClass('notice notice-' + type)
|
||||
.empty()
|
||||
.append($('<p>').text(message))
|
||||
.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file.
|
||||
*
|
||||
@@ -666,12 +652,8 @@
|
||||
$spinner.removeClass('is-active');
|
||||
|
||||
if (response.success) {
|
||||
var noticeClass = response.data.warning ? 'notice-warning' : 'notice-success';
|
||||
$message
|
||||
.removeClass('notice-error notice-success notice-warning')
|
||||
.addClass('notice ' + noticeClass)
|
||||
.html('<p>' + response.data.message + '</p>')
|
||||
.show();
|
||||
var type = response.data.warning ? 'warning' : 'success';
|
||||
showNotice($message, response.data.message, type);
|
||||
|
||||
if (!response.data.warning) {
|
||||
setTimeout(function() {
|
||||
@@ -679,20 +661,12 @@
|
||||
}, 1500);
|
||||
}
|
||||
} else {
|
||||
$message
|
||||
.removeClass('notice-success notice-warning')
|
||||
.addClass('notice notice-error')
|
||||
.html('<p>' + (response.data.message || 'An error occurred.') + '</p>')
|
||||
.show();
|
||||
showNotice($message, response.data.message || 'An error occurred.', 'error');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$spinner.removeClass('is-active');
|
||||
$message
|
||||
.removeClass('notice-success notice-warning')
|
||||
.addClass('notice notice-error')
|
||||
.html('<p>Connection error. Please try again.</p>')
|
||||
.show();
|
||||
showNotice($message, 'Connection error. Please try again.', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -720,26 +694,14 @@
|
||||
$spinner.removeClass('is-active');
|
||||
|
||||
if (response.success) {
|
||||
$message
|
||||
.removeClass('notice-error notice-warning')
|
||||
.addClass('notice notice-success')
|
||||
.html('<p>' + response.data.message + '</p>')
|
||||
.show();
|
||||
showNotice($message, response.data.message, 'success');
|
||||
} else {
|
||||
$message
|
||||
.removeClass('notice-success notice-warning')
|
||||
.addClass('notice notice-error')
|
||||
.html('<p>' + (response.data.message || 'Connection test failed.') + '</p>')
|
||||
.show();
|
||||
showNotice($message, response.data.message || 'Connection test failed.', 'error');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$spinner.removeClass('is-active');
|
||||
$message
|
||||
.removeClass('notice-success notice-warning')
|
||||
.addClass('notice notice-error')
|
||||
.html('<p>Connection error. Please try again.</p>')
|
||||
.show();
|
||||
showNotice($message, 'Connection error. Please try again.', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -102,52 +102,12 @@ class MetricsEndpoint {
|
||||
/**
|
||||
* Authenticate the metrics request.
|
||||
*
|
||||
* Uses the shared authentication helper to avoid code duplication
|
||||
* with the isolated mode handler in wp-prometheus.php.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function authenticate(): bool {
|
||||
$auth_token = get_option( 'wp_prometheus_auth_token', '' );
|
||||
|
||||
// If no token is set, deny access.
|
||||
if ( empty( $auth_token ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for Bearer token in Authorization header.
|
||||
$auth_header = $this->get_authorization_header();
|
||||
if ( ! empty( $auth_header ) && preg_match( '/Bearer\s+(.*)$/i', $auth_header, $matches ) ) {
|
||||
return hash_equals( $auth_token, $matches[1] );
|
||||
}
|
||||
|
||||
// Check for token in query parameter (less secure but useful for testing).
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Auth token check.
|
||||
if ( isset( $_GET['token'] ) && hash_equals( $auth_token, sanitize_text_field( wp_unslash( $_GET['token'] ) ) ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Authorization header from the request.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_authorization_header(): string {
|
||||
if ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) ) {
|
||||
return sanitize_text_field( wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ) {
|
||||
return sanitize_text_field( wp_unslash( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) );
|
||||
}
|
||||
|
||||
if ( function_exists( 'apache_request_headers' ) ) {
|
||||
$headers = apache_request_headers();
|
||||
if ( isset( $headers['Authorization'] ) ) {
|
||||
return sanitize_text_field( $headers['Authorization'] );
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
return wp_prometheus_authenticate_request();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,14 @@ final class Installer {
|
||||
'wp_prometheus_enabled_metrics',
|
||||
'wp_prometheus_runtime_metrics',
|
||||
'wp_prometheus_custom_metrics',
|
||||
'wp_prometheus_isolated_mode',
|
||||
'wp_prometheus_storage_adapter',
|
||||
'wp_prometheus_redis_host',
|
||||
'wp_prometheus_redis_port',
|
||||
'wp_prometheus_redis_password',
|
||||
'wp_prometheus_redis_database',
|
||||
'wp_prometheus_redis_prefix',
|
||||
'wp_prometheus_apcu_prefix',
|
||||
);
|
||||
|
||||
foreach ( $options as $option ) {
|
||||
|
||||
@@ -447,6 +447,16 @@ class Collector {
|
||||
return class_exists( 'WooCommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if WooCommerce HPOS (High-Performance Order Storage) is enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_hpos_enabled(): bool {
|
||||
return class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' )
|
||||
&& \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect WooCommerce metrics.
|
||||
*
|
||||
@@ -498,16 +508,17 @@ class Collector {
|
||||
}
|
||||
}
|
||||
|
||||
// Count by product type (for published products only).
|
||||
// Count by product type (for published products only) using count query.
|
||||
foreach ( array_keys( $product_types ) as $type ) {
|
||||
$args = array(
|
||||
'status' => 'publish',
|
||||
'type' => $type,
|
||||
'limit' => -1,
|
||||
'limit' => 1,
|
||||
'return' => 'ids',
|
||||
'paginate' => true,
|
||||
);
|
||||
$products = wc_get_products( $args );
|
||||
$gauge->set( count( $products ), array( 'publish', $type ) );
|
||||
$result = wc_get_products( $args );
|
||||
$gauge->set( $result->total, array( 'publish', $type ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,8 +564,7 @@ class Collector {
|
||||
$currency = get_woocommerce_currency();
|
||||
|
||||
// Check if HPOS (High-Performance Order Storage) is enabled.
|
||||
$hpos_enabled = class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' )
|
||||
&& \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled();
|
||||
$hpos_enabled = $this->is_hpos_enabled();
|
||||
|
||||
if ( $hpos_enabled ) {
|
||||
$orders_table = $wpdb->prefix . 'wc_orders';
|
||||
|
||||
@@ -47,6 +47,13 @@ class CustomMetricBuilder {
|
||||
*/
|
||||
const MAX_LABEL_VALUES = 50;
|
||||
|
||||
/**
|
||||
* Maximum import JSON size in bytes (1 MB).
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const MAX_IMPORT_SIZE = 1048576;
|
||||
|
||||
/**
|
||||
* Get all custom metrics.
|
||||
*
|
||||
@@ -332,7 +339,6 @@ class CustomMetricBuilder {
|
||||
'version' => self::EXPORT_VERSION,
|
||||
'plugin_version' => WP_PROMETHEUS_VERSION,
|
||||
'exported_at' => gmdate( 'c' ),
|
||||
'site_url' => home_url(),
|
||||
'metrics' => array_values( $metrics ),
|
||||
);
|
||||
|
||||
@@ -348,6 +354,17 @@ class CustomMetricBuilder {
|
||||
* @throws \InvalidArgumentException If JSON is invalid.
|
||||
*/
|
||||
public function import( string $json, string $mode = 'skip' ): array {
|
||||
// Prevent DoS via excessively large imports.
|
||||
if ( strlen( $json ) > self::MAX_IMPORT_SIZE ) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf(
|
||||
/* translators: %s: Maximum size */
|
||||
__( 'Import data exceeds maximum size of %s.', 'wp-prometheus' ),
|
||||
size_format( self::MAX_IMPORT_SIZE )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$data = json_decode( $json, true );
|
||||
|
||||
if ( json_last_error() !== JSON_ERROR_NONE ) {
|
||||
@@ -358,6 +375,12 @@ class CustomMetricBuilder {
|
||||
throw new \InvalidArgumentException( __( 'No metrics found in import file.', 'wp-prometheus' ) );
|
||||
}
|
||||
|
||||
// Validate import mode.
|
||||
$valid_modes = array( 'skip', 'overwrite', 'rename' );
|
||||
if ( ! in_array( $mode, $valid_modes, true ) ) {
|
||||
$mode = 'skip';
|
||||
}
|
||||
|
||||
$result = array(
|
||||
'imported' => 0,
|
||||
'skipped' => 0,
|
||||
|
||||
@@ -107,36 +107,8 @@ function wp_prometheus_isolated_metrics_handler(): void {
|
||||
return; // Let normal flow handle unlicensed state.
|
||||
}
|
||||
|
||||
// Authenticate.
|
||||
$auth_token = get_option( 'wp_prometheus_auth_token', '' );
|
||||
if ( empty( $auth_token ) ) {
|
||||
status_header( 401 );
|
||||
header( 'WWW-Authenticate: Bearer realm="WP Prometheus Metrics"' );
|
||||
header( 'Content-Type: text/plain; charset=utf-8' );
|
||||
echo 'Unauthorized';
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check Bearer token.
|
||||
$auth_header = '';
|
||||
if ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) ) {
|
||||
$auth_header = sanitize_text_field( wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ) );
|
||||
} elseif ( isset( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ) {
|
||||
$auth_header = sanitize_text_field( wp_unslash( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) );
|
||||
}
|
||||
|
||||
$authenticated = false;
|
||||
if ( ! empty( $auth_header ) && preg_match( '/Bearer\s+(.*)$/i', $auth_header, $matches ) ) {
|
||||
$authenticated = hash_equals( $auth_token, $matches[1] );
|
||||
}
|
||||
|
||||
// Check query parameter fallback.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Auth token check.
|
||||
if ( ! $authenticated && isset( $_GET['token'] ) ) {
|
||||
$authenticated = hash_equals( $auth_token, sanitize_text_field( wp_unslash( $_GET['token'] ) ) );
|
||||
}
|
||||
|
||||
if ( ! $authenticated ) {
|
||||
// Authenticate using shared helper.
|
||||
if ( ! wp_prometheus_authenticate_request() ) {
|
||||
status_header( 401 );
|
||||
header( 'WWW-Authenticate: Bearer realm="WP Prometheus Metrics"' );
|
||||
header( 'Content-Type: text/plain; charset=utf-8' );
|
||||
@@ -161,6 +133,64 @@ function wp_prometheus_isolated_metrics_handler(): void {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate a metrics request using Bearer token or query parameter.
|
||||
*
|
||||
* Shared authentication logic used by both the MetricsEndpoint class
|
||||
* and the isolated mode handler to avoid code duplication.
|
||||
*
|
||||
* @return bool True if authenticated, false otherwise.
|
||||
*/
|
||||
function wp_prometheus_authenticate_request(): bool {
|
||||
$auth_token = get_option( 'wp_prometheus_auth_token', '' );
|
||||
|
||||
// If no token is set, deny access.
|
||||
if ( empty( $auth_token ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for Bearer token in Authorization header.
|
||||
$auth_header = wp_prometheus_get_authorization_header();
|
||||
if ( ! empty( $auth_header ) && preg_match( '/Bearer\s+(.*)$/i', $auth_header, $matches ) ) {
|
||||
return hash_equals( $auth_token, $matches[1] );
|
||||
}
|
||||
|
||||
// Check for token in query parameter (less secure but useful for testing).
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Auth token check.
|
||||
if ( isset( $_GET['token'] ) && hash_equals( $auth_token, sanitize_text_field( wp_unslash( $_GET['token'] ) ) ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Authorization header from the request.
|
||||
*
|
||||
* Checks multiple sources for the Authorization header to support
|
||||
* different server configurations (Apache, nginx, CGI, etc.).
|
||||
*
|
||||
* @return string The Authorization header value, or empty string if not found.
|
||||
*/
|
||||
function wp_prometheus_get_authorization_header(): string {
|
||||
if ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) ) {
|
||||
return sanitize_text_field( wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ) {
|
||||
return sanitize_text_field( wp_unslash( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) );
|
||||
}
|
||||
|
||||
if ( function_exists( 'apache_request_headers' ) ) {
|
||||
$headers = apache_request_headers();
|
||||
if ( isset( $headers['Authorization'] ) ) {
|
||||
return sanitize_text_field( $headers['Authorization'] );
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// Try early metrics handling before full plugin initialization.
|
||||
wp_prometheus_early_metrics_check();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user