fix: resolve OpenRouter 401 by injecting auth header at cURL level

Hostinger's hosting environment appears to strip the Authorization
header from outgoing wp_remote_post requests before they reach the
cURL transport. This caused OpenRouter to respond with HTTP 401
"Missing Authentication header" even though the header was set in
the wp_remote_post args array.

Fixes:
- Add http_api_curl filter (priority 99) that re-sets the
  Authorization header directly on the cURL handle, bypassing any
  intermediate header stripping. Scoped to openrouter.ai URLs only.
- Applied to all three OpenRouter call sites: send_chat_completion,
  validate_credentials_detailed, and get_available_models.
- Filter is cleaned up in a finally block to avoid leaking.

Also:
- Add pre-flight key format validation (must start with "sk-or-")
  to catch corrupted decryption early with a clear error message.
- Fix retry condition: was `>= 429` (incorrectly retried 430-499),
  now `=== 429` (only retry rate-limit).
- Add detailed diagnostic logging on 401/403 auth failures.
- Add .env.credentials to .gitignore.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Terence Ong
2026-04-13 08:30:37 +07:00
parent b21d102afb
commit 98ef81f49a
3 changed files with 183 additions and 94 deletions

View File

@@ -51,6 +51,19 @@ class PRAutoBlogger_OpenRouter_Pricing {
return [];
}
// Belt-and-suspenders: inject Authorization at cURL level (see OpenRouter_Provider).
$auth_header_value = 'Bearer ' . $api_key;
$curl_auth_filter_mod = function ( $handle, $parsed_args, $url ) use ( $auth_header_value ): void {
if ( false === strpos( $url, 'openrouter.ai' ) ) {
return;
}
// phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt
curl_setopt( $handle, CURLOPT_HTTPHEADER, [
'Authorization: ' . $auth_header_value,
] );
};
add_action( 'http_api_curl', $curl_auth_filter_mod, 99, 3 );
$response = wp_remote_get(
'https://openrouter.ai/api/v1/models',
[
@@ -61,6 +74,8 @@ class PRAutoBlogger_OpenRouter_Pricing {
]
);
remove_action( 'http_api_curl', $curl_auth_filter_mod, 99 );
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
PRAutoBlogger_Logger::instance()->warning( 'Failed to fetch OpenRouter models list.', 'openrouter' );
return [];