From 3e26d72bd3f9b93270ec6c352a9b3c7c02586e21 Mon Sep 17 00:00:00 2001 From: Tom Stark Date: Tue, 14 Jul 2026 14:17:46 +0200 Subject: [PATCH 1/2] fix: read Authorization via getallheaders() when Apache withholds it from $_SERVER --- src/Http/RequestContext.php | 37 ++++++++++++++++++++--- tests/Http/RequestContextTest.php | 50 +++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/src/Http/RequestContext.php b/src/Http/RequestContext.php index 36b61b2..5b0c04b 100644 --- a/src/Http/RequestContext.php +++ b/src/Http/RequestContext.php @@ -36,6 +36,35 @@ public function __construct( public ?CdnRequestSignals $cdnSignals = null, ) {} + /** + * Resolve the Authorization header from $_SERVER, falling back to the + * SAPI's raw request headers. + * + * Apache with mod_php withholds Authorization from the CGI-style + * $_SERVER variables (a CGI-era security behavior), while + * getallheaders() still exposes it. Without the fallback, challenge and + * license-token verification silently fail closed on vanilla Apache — + * every bearer looks absent. + * + * @param array $server Typically $_SERVER + * @param array $requestHeaders Raw request headers as reported by getallheaders() + */ + public static function resolveAuthorizationHeader(array $server, array $requestHeaders): ?string + { + $auth = $server['HTTP_AUTHORIZATION'] ?? $server['REDIRECT_HTTP_AUTHORIZATION'] ?? null; + if (is_string($auth) && $auth !== '') { + return $auth; + } + + foreach ($requestHeaders as $name => $value) { + if (strcasecmp((string) $name, 'Authorization') === 0 && is_string($value) && $value !== '') { + return $value; + } + } + + return null; + } + /** * Build a RequestContext from PHP's $_SERVER superglobal. */ @@ -46,10 +75,10 @@ public static function fromGlobals(): self $uri = $_SERVER['REQUEST_URI'] ?? '/'; $url = "{$scheme}://{$host}{$uri}"; - // PHP strips the Authorization header in some setups; check multiple sources - $authorization = $_SERVER['HTTP_AUTHORIZATION'] - ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] - ?? null; + $authorization = self::resolveAuthorizationHeader( + $_SERVER, + function_exists('getallheaders') ? getallheaders() : [], + ); $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? null; $accept = $_SERVER['HTTP_ACCEPT'] ?? null; diff --git a/tests/Http/RequestContextTest.php b/tests/Http/RequestContextTest.php index 7dadfca..3bc21b3 100644 --- a/tests/Http/RequestContextTest.php +++ b/tests/Http/RequestContextTest.php @@ -64,6 +64,56 @@ public function test_from_globals_populates_method_client_ip_and_request_id(): v } } + public function test_resolve_authorization_prefers_server_http_authorization(): void + { + $auth = RequestContext::resolveAuthorizationHeader( + ['HTTP_AUTHORIZATION' => 'Bearer from-server'], + ['Authorization' => 'Bearer from-headers'], + ); + + $this->assertSame('Bearer from-server', $auth); + } + + public function test_resolve_authorization_falls_back_to_redirect_http_authorization(): void + { + $auth = RequestContext::resolveAuthorizationHeader( + ['REDIRECT_HTTP_AUTHORIZATION' => 'Bearer from-redirect'], + [], + ); + + $this->assertSame('Bearer from-redirect', $auth); + } + + public function test_resolve_authorization_falls_back_to_request_headers(): void + { + // Apache mod_php withholds Authorization from $_SERVER entirely; + // the raw request headers (getallheaders()) are the only source. + $auth = RequestContext::resolveAuthorizationHeader( + ['HTTP_HOST' => 'example.com'], + ['Authorization' => 'Bearer from-headers'], + ); + + $this->assertSame('Bearer from-headers', $auth); + } + + public function test_resolve_authorization_matches_header_name_case_insensitively(): void + { + $auth = RequestContext::resolveAuthorizationHeader( + [], + ['authorization' => 'License from-lowercase'], + ); + + $this->assertSame('License from-lowercase', $auth); + } + + public function test_resolve_authorization_returns_null_when_absent_everywhere(): void + { + $this->assertNull(RequestContext::resolveAuthorizationHeader( + ['HTTP_HOST' => 'example.com'], + ['User-Agent' => 'curl'], + )); + } + public function test_from_globals_leaves_injection_only_signals_null(): void { $original = $_SERVER; From 9691c5f7d18a8f4970a1a619c0cae5b4af905df6 Mon Sep 17 00:00:00 2001 From: Tom Stark Date: Tue, 14 Jul 2026 14:34:14 +0200 Subject: [PATCH 2/2] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/Http/RequestContext.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/RequestContext.php b/src/Http/RequestContext.php index 5b0c04b..fda454c 100644 --- a/src/Http/RequestContext.php +++ b/src/Http/RequestContext.php @@ -77,7 +77,7 @@ public static function fromGlobals(): self $authorization = self::resolveAuthorizationHeader( $_SERVER, - function_exists('getallheaders') ? getallheaders() : [], + function_exists('getallheaders') ? (getallheaders() ?: []) : [], ); $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? null;