Opened 12 years ago
Last modified 4 weeks ago
#25840 new enhancement
Feature Request: WP_ACCESSIBLE_HOSTS as option
| Reported by: |
|
Owned by: | |
|---|---|---|---|
| Milestone: | Future Release | Priority: | normal |
| Severity: | normal | Version: | 3.7.1 |
| Component: | HTTP API | Keywords: | has-patch |
| Focuses: | Cc: |
Description
Currently WP_ACCESSIBLE_HOSTS is defined as a constant. It would be great if this is a wordpress option (or something equivalent) so you can change it at runtime. If you have a multisite installation and need to add domains to the whitelist you must reload the whole installation to enable them. Writing a simple plugin for this is also not possible since constants can not be redefined.
My suggestion:
1) Store a site_option for mutlisites. This should contain a general whitelist for all blogs
2) Store a option per blog to contain additional whitelists for this single blog
3) Make it configurable via the admin interface (single textbox to enter the domains)
4) In the block_request function (https://github.com/WordPress/WordPress/blob/master/wp-includes/class-http.php#L507) the 2 options should be merged and handled like the constant
This way you could manage the whitelist at runtime.
What do you think about this?
Chris
Attachments (4)
Change History (22)
#1
@
12 years ago
Would that patch allow you to do what you want to (By filtering the list of accessible sites within block_request()) ?
#2
@
12 years ago
- Type changed from feature request to enhancement
-1 for an UI option.
A filter like 25840.diff looks good for me.
#4
@
12 years ago
- Milestone changed from Awaiting Review to 3.8
Only note, is that we currently have filters prefixed with 'wp_http_' and 'http_', so I'd prefix that filter with the former.
#5
@
12 years ago
25840.1.diff prefixes the filter as requested.
@
12 years ago
With the previous patch that the function would still block requests if WP_ACCESSIBLE_HOSTS was undefined - without ever invoking the filter. Revised patch attached - this includes the suggested rename as per Justin's patch.
#6
@
12 years ago
Hi,
thanks for the patch! I want to discuss the use of a filter here.
Currently the WP_ACCESSIBLE_HOSTS can only be managed by an admin because it is a constant and can not be overwritten by a plugin. With the use of a filter, every plugin can register it's own domains. I think the purpose of the whitelist is that it is managed by an admin and not via plugins.
Example: A plugin sends usage statistics to xxx.yyy.com. Now an admin can manage the whitelist and deny the access to this site. With the use of a filter the plugin can register xxx.yyy.com for the whitelist and the admin can not deny it.
How do you see this whitelisting feature?
#7
follow-up:
↓ 9
@
12 years ago
With the use of a filter, every plugin can register it's own domains.
Correct - but plugins may also remove domains.
A plugin sends usage statistics to xxx.yyy.com. Now an admin can manage the whitelist and deny the access to this site. With the use of a filter the plugin can register xxx.yyy.com for the whitelist and the admin can not deny it.
The admin can deny it by setting the priority of their own filter higher than that of the plugins.
#9
in reply to:
↑ 7
;
follow-up:
↓ 10
@
12 years ago
Replying to leewillis77:
With the use of a filter, every plugin can register it's own domains.
Correct - but plugins may also remove domains.
A plugin sends usage statistics to xxx.yyy.com. Now an admin can manage the whitelist and deny the access to this site. With the use of a filter the plugin can register xxx.yyy.com for the whitelist and the admin can not deny it.
The admin can deny it by setting the priority of their own filter higher than that of the plugins.
Hi,
that is an interesting argument, but there is a possibility where you can break the security!
When the plugin uses the maximum prio, then there is no more room for the admin to add a higher prio, and the queue of filters will be processed with the order of their names ... ?
The easiest and most secure solution is to set the constants in a plugin, which name begin with zero, because the plugins will be executed with the order of their names!
The constant hasn't be set in wp-config.php, so there is no problem.
Best regards,
Christian
This filter destroys the security feature of the constants
#10
in reply to:
↑ 9
;
follow-up:
↓ 11
@
12 years ago
Replying to Christian Buchhas:
that is an interesting argument, but there is a possibility where you can break the security!
When the plugin uses the maximum prio, then there is no more room for the admin to add a higher prio, and the queue of filters will be processed with the order of their names ... ?
The easiest and most secure solution is to set the constants in a plugin, which name begin with zero, because the plugins will be executed with the order of their names!
That would be true, but there's not really a "maximum" priority as such. Priorities aren't guaranteed to be integers, floats, or even numbers, so there's (almost) always something higher that you can set. e.g. if something uses PHP_INT_MAX, you can use PHP_INT_MAX + 1 and it'll transparently become a larger float. (Interesting thought: what *is* the maximum priority (last sorted value) you can get in PHP? I'd guess INF.)
---
Apart from the intellectual exercise, I'm not sure it really matters. If you enable a plugin, it can already run arbitrary code, so it's hardly a security issue.
#11
in reply to:
↑ 10
@
12 years ago
Replying to rmccue:
Apart from the intellectual exercise, I'm not sure it really matters. If you enable a plugin, it can already run arbitrary code, so it's hardly a security issue.
Yeah, these constants aren't designed as a security measure (you should have a firewall between the web server and the LAN/Web if you need to prevent connections) and are rather more designed to prevent HTTP timeouts to inaccessible hosts.
Looking at 25840.2.diff we'll also need to change the code further down that expects WP_ACCESSIBLE_HOSTS to be defined. It should instead be changed to loop over the filters results, and if * is present, add it to the wildcard list, etc.
#12
@
12 years ago
- Keywords has-patch needs-testing added
25840.3.diff is an untested patch that covers all angles of this constant & filter.
The previous patches filter only once per page load, where as this filters on each request, in addition, the logic has been changed to accomodate that in hopefully a more performance-friendly manner.
This ticket was mentioned in PR #9824 on WordPress/wordpress-develop by @pmbaldha.
5 months ago
#16
- Keywords needs-refresh removed
Trac ticket: https://core.trac.wordpress.org/ticket/25840
@mukesh27 commented on PR #9824:
5 months ago
#17
The PR is not refresh of https://core.trac.wordpress.org/attachment/ticket/25840/25840.3.diff. Some code changes missing in the PR.
#18
@
4 weeks ago
- Keywords needs-testing removed
Test Report
Description
This report validates whether the indicated patch works as expected.
Patch tested: https://github.com/WordPress/wordpress-develop/pull/9824
Environment
- WordPress: 7.0-alpha-61215-src
- PHP: 8.2.29
- Server: nginx/1.29.4
- Database: mysqli (Server: 8.4.7 / Client: mysqlnd 8.2.29)
- Browser: Chrome 144.0.0.0
- OS: macOS
- Theme: Twenty Twenty-One 2.7
- MU Plugins: None activated
- Plugins:
- Code Snippets 3.9.4
- Test Reports 1.2.1
Steps to Reproduce
- Add the following code via Code Snippets plugin, via your functions.php or install it as a plugin.
/**
* Plugin Name: Test wp_accessible_hosts Filter & Backwards Compatibility
* Description: Tests the new wp_accessible_hosts filter, WP_ACCESSIBLE_HOSTS constant backwards compatibility, and makes real API calls
* Version: 2.0
* Author: Ozgur Sar
*/
// Add our test hosts via the new filter if it exists
add_filter( 'wp_http_accessible_hosts', function ( $hosts ) {
if ( ! is_array( $hosts ) ) {
$hosts = [];
}
$hosts[] = 'jsonplaceholder.typicode.com';
$hosts[] = 'api.github.com';
return $hosts;
}, 10, 1 );
add_action( 'admin_notices', 'test_wp_accessible_hosts_comprehensive' );
function test_wp_accessible_hosts_comprehensive() {
// Check if external requests are blocked
$external_blocked = defined( 'WP_HTTP_BLOCK_EXTERNAL' ) && WP_HTTP_BLOCK_EXTERNAL;
// Check if the constant is defined
$constant_defined = defined( 'WP_ACCESSIBLE_HOSTS' );
$constant_value = $constant_defined ? WP_ACCESSIBLE_HOSTS : 'Not defined';
// Check if the filter exists in the code
$http_file = ABSPATH . 'wp-includes/class-wp-http.php';
$filter_in_code = false;
$filter_lines = array();
if ( file_exists( $http_file ) ) {
$content = file_get_contents( $http_file );
$filter_in_code = strpos( $content, 'wp_accessible_hosts' ) !== false;
if ( $filter_in_code ) {
$lines = explode( "\n", $content );
foreach ( $lines as $line_num => $line ) {
if ( strpos( $line, 'wp_accessible_hosts' ) !== false ) {
$filter_lines[] = array(
'num' => $line_num + 1,
'content' => trim( $line )
);
}
}
}
}
// Check if filter has callbacks
$has_callbacks = has_filter( 'wp_accessible_hosts' );
// Test applying the filter
$test_hosts = array( 'example.com' );
$filtered_hosts = apply_filters( 'wp_accessible_hosts', $test_hosts );
// Perform real API tests
$api_test_results = array();
// Test 1: JSONPlaceholder API (should work with our filter)
$api_test_results['jsonplaceholder'] = test_api_call( 'https://jsonplaceholder.typicode.com/posts/1' );
// Test 2: GitHub API (should work with our filter)
$api_test_results['github'] = test_api_call( 'https://api.github.com/zen' );
// Test 3: Random API that's not whitelisted (should fail if blocking is enabled)
$api_test_results['httpbin'] = test_api_call( 'https://httpbin.org/get' );
?>
<div class="notice notice-info" style="padding: 15px;">
<h2 style="margin-top: 0;">🔍 wp_accessible_hosts Filter & Backwards Compatibility Test</h2>
<!-- Configuration Status -->
<h3>📋 Current Configuration</h3>
<table class="widefat" style="max-width: 900px; background: white; margin-bottom: 20px;">
<thead>
<tr>
<th>Setting</th>
<th>Status</th>
<th>Details</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>WP_HTTP_BLOCK_EXTERNAL</strong></td>
<td><?php echo $external_blocked ? '🔴 Enabled (Blocking)' : '🟢 Disabled (Allowing All)'; ?></td>
<td>
<?php echo $external_blocked ? 'External requests are being blocked' : 'All external requests allowed'; ?>
<?php if ( ! $external_blocked ) : ?>
<br><strong style="color: #996800;">⚠️ NOTE: WP_ACCESSIBLE_HOSTS and wp_accessible_hosts filter have NO EFFECT when blocking is disabled!</strong>
<?php endif; ?>
</td>
</tr>
<tr>
<td><strong>WP_ACCESSIBLE_HOSTS constant</strong></td>
<td><?php echo $constant_defined ? '✅ Defined' : '❌ Not Defined'; ?></td>
<td>
<code><?php echo esc_html( $constant_value ); ?></code>
<?php if ( $constant_defined && ! $external_blocked ) : ?>
<br><span style="color: #996800;">⚠️ This constant is ignored because WP_HTTP_BLOCK_EXTERNAL is false</span>
<?php endif; ?>
</td>
</tr>
</tbody>
</table>
<!-- Live API Tests -->
<h3>Live API Request Tests</h3>
<p style="background: #e7f5fe; padding: 10px; border-left: 4px solid #2271b1; margin-bottom: 10px;">
<strong>💡 KEY CONCEPT:</strong> These tests make <strong>real wp_remote_get() calls</strong> through WordPress core's HTTP class.
If the patch is applied, WordPress will apply the <code>wp_accessible_hosts</code> filter during these requests.
</p>
<table class="widefat" style="max-width: 900px; background: white; margin-bottom: 20px;">
<thead>
<tr>
<th>API</th>
<th>URL</th>
<th>Result</th>
<th>Details</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>JSONPlaceholder</strong><br><small>(whitelisted via filter)</small></td>
<td><code style="font-size: 11px;">jsonplaceholder.typicode.com</code></td>
<td><?php echo format_api_result( $api_test_results['jsonplaceholder'] ); ?></td>
<td>
<?php if ( $api_test_results['jsonplaceholder']['success'] ) : ?>
<details>
<summary style="cursor: pointer; color: #2271b1;">Show response</summary>
<pre style="font-size: 11px; max-height: 150px; overflow-y: auto; background: #f6f7f7; padding: 10px; margin-top: 5px;"><?php echo esc_html( substr( $api_test_results['jsonplaceholder']['body'], 0, 500 ) ); ?></pre>
</details>
<?php else : ?>
<span style="color: #d63638;"><?php echo esc_html( $api_test_results['jsonplaceholder']['message'] ); ?></span>
<?php endif; ?>
</td>
</tr>
<tr>
<td><strong>GitHub API</strong><br><small>(whitelisted via filter)</small></td>
<td><code style="font-size: 11px;">api.github.com</code></td>
<td><?php echo format_api_result( $api_test_results['github'] ); ?></td>
<td>
<?php if ( $api_test_results['github']['success'] ) : ?>
<?php if ( $external_blocked && $filter_in_code ) : ?>
<span style="color: #00a32a; font-weight: bold;">✅ FILTER IS WORKING IN CORE!</span><br>
<small>This API is ONLY whitelisted via the filter (not in constant), and it succeeded!</small>
<?php elseif ( $external_blocked && ! $filter_in_code ) : ?>
<span style="color: #996800; font-weight: bold;">⚠️ Unexpected Success</span><br>
<small>Blocking is enabled but filter not in core - this shouldn't work!</small>
<?php else : ?>
<span style="color: #00a32a;">Success (blocking disabled)</span>
<?php endif; ?>
<details>
<summary style="cursor: pointer; color: #2271b1;">Show response</summary>
<pre style="font-size: 11px; background: #f6f7f7; padding: 10px; margin-top: 5px;"><?php echo esc_html( $api_test_results['github']['body'] ); ?></pre>
</details>
<?php else : ?>
<?php if ( $external_blocked && ! $filter_in_code ) : ?>
<span style="color: #d63638; font-weight: bold;">✅ EXPECTED FAILURE</span><br>
<small>Filter not in core, so this API should be blocked. This confirms the patch is NOT applied.</small>
<?php else : ?>
<span style="color: #d63638;"><?php echo esc_html( $api_test_results['github']['message'] ); ?></span>
<?php endif; ?>
<?php endif; ?>
</td>
</tr>
<tr>
<td><strong>HTTPBin</strong><br><small>(NOT whitelisted)</small></td>
<td><code style="font-size: 11px;">httpbin.org</code></td>
<td><?php echo format_api_result( $api_test_results['httpbin'] ); ?></td>
<td>
<?php if ( $api_test_results['httpbin']['success'] ) : ?>
<span style="color: #00a32a;">Request succeeded (blocking not enabled or filter working)</span>
<?php else : ?>
<span style="color: #996800;">Expected: should be blocked if WP_HTTP_BLOCK_EXTERNAL is true</span>
<?php endif; ?>
</td>
</tr>
</tbody>
</table>
</div>
<?php
}
/**
* Make a test API call
*/
function test_api_call( $url ) {
$response = wp_remote_get( $url, array(
'timeout' => 5,
'sslverify' => true,
) );
if ( is_wp_error( $response ) ) {
return array(
'success' => false,
'message' => $response->get_error_message(),
'body' => ''
);
}
$response_code = wp_remote_retrieve_response_code( $response );
$body = wp_remote_retrieve_body( $response );
return array(
'success' => $response_code >= 200 && $response_code < 300,
'message' => 'HTTP ' . $response_code,
'body' => $body,
'code' => $response_code
);
}
/**
* Format API result for display
*/
function format_api_result( $result ) {
if ( $result['success'] ) {
return '<span style="color: #00a32a; font-weight: bold;">✅ Success</span><br><small>HTTP ' . $result['code'] . '</small>';
} else {
return '<span style="color: #d63638; font-weight: bold;">❌ Failed</span><br><small>' . esc_html( $result['message'] ) . '</small>';
}
}
- Check WP Dashboard home to view the patch implementation results.
Actual Results
- ✅ Issue resolved with patch.
- ❌ Without patch applied, allowed hosts via filter are not reachable (e.g. jsonplaceholder.typicode.com and api.github.com in this case. Change them in the plugin file for your test case.)
- ✅ When patch applied, hosts allowed via filter are reachable. See screenshots below for example outputs and actual http response from the jsonplaceholder.typicode.com API.
Additional Notes
- To be able to test the patch, add wp-config.php and add the following lines:
define( 'WP_HTTP_BLOCK_EXTERNAL', true ); define( 'WP_ACCESSIBLE_HOSTS', 'example.com' );
- Defining only
WP_HTTP_BLOCK_EXTERNALis not enough. Filter works only ifWP_ACCESSIBLE_HOSTSis defined and at least set to an empty string.
- When I use the filter, I received the following PHP warning:
Warning: Undefined variable $w_accessible_hosts in /var/www/src/wp-includes/class-wp-http.php



Proposed patch