@@ -85,57 +85,48 @@ export function validateForwardedHeaders(
8585) : { protocol ?: string ; host ?: string ; port ?: string } {
8686 const result : { protocol ?: string ; host ?: string ; port ?: string } = { } ;
8787
88- // Validate protocol
89- if ( forwardedProtocol ) {
90- if ( allowedDomains && allowedDomains . length > 0 ) {
91- const hasProtocolPatterns = allowedDomains . some ( ( pattern ) => pattern . protocol !== undefined ) ;
92- if ( hasProtocolPatterns ) {
93- // Validate against allowedDomains patterns
94- try {
95- const testUrl = new URL ( `${ forwardedProtocol } ://example.com` ) ;
96- const isAllowed = allowedDomains . some ( ( pattern ) => matchPattern ( testUrl , pattern ) ) ;
97- if ( isAllowed ) {
98- result . protocol = forwardedProtocol ;
99- }
100- } catch {
101- // Invalid protocol, omit from result
102- }
103- } else if ( / ^ h t t p s ? $ / . test ( forwardedProtocol ) ) {
104- // allowedDomains exist but no protocol patterns, allow http/https
105- result . protocol = forwardedProtocol ;
106- }
107- } else if ( / ^ h t t p s ? $ / . test ( forwardedProtocol ) ) {
108- // No allowedDomains, only allow http/https
109- result . protocol = forwardedProtocol ;
110- }
88+ // Require allowed domains to validate any forwarded headers - prevents trusting unvalidated headers
89+ if ( ! allowedDomains || allowedDomains . length === 0 ) {
90+ return result
11191 }
11292
113- // Validate port first
114- if ( forwardedPort && allowedDomains && allowedDomains . length > 0 ) {
115- const hasPortPatterns = allowedDomains . some ( ( pattern ) => pattern . port !== undefined ) ;
116- if ( hasPortPatterns ) {
117- // Validate against allowedDomains patterns
118- const isAllowed = allowedDomains . some ( ( pattern ) => pattern . port === forwardedPort ) ;
119- if ( isAllowed ) {
120- result . port = forwardedPort ;
121- }
122- }
123- // If no port patterns, reject the header (strict security default)
124- }
93+ let allowedDomain : RemotePattern = { }
12594
12695 // Validate host (extract port from hostname for validation)
12796 // Reject empty strings and sanitize to prevent path injection
128- if ( forwardedHost && forwardedHost . length > 0 && allowedDomains && allowedDomains . length > 0 ) {
129- const protoForValidation = result . protocol || 'https' ;
97+ if ( forwardedHost && forwardedHost . length > 0 ) {
13098 const sanitized = sanitizeHost ( forwardedHost ) ;
13199 if ( sanitized ) {
132- const { hostname, port : portFromHost } = parseHost ( sanitized ) ;
133- const portForValidation = result . port || portFromHost ;
134- if ( matchesAllowedDomains ( hostname , protoForValidation , portForValidation , allowedDomains ) ) {
100+ const { hostname } = parseHost ( sanitized ) ;
101+ const found = allowedDomains . find ( ( pattern ) => pattern . hostname === hostname ) ;
102+ if ( found ) {
135103 result . host = sanitized ;
104+ allowedDomain = found
136105 }
137106 }
138107 }
139108
109+ // If host is not valid, we cannot trust protocol or port from forwarded headers
110+ if ( ! result . host || ! allowedDomain . hostname ) {
111+ return result ;
112+ }
113+
114+ // Validate port
115+ if ( forwardedPort && allowedDomain . port && allowedDomain . port === forwardedPort ) {
116+ result . port = forwardedPort ;
117+ }
118+
119+ // Validate protocol
120+ if ( forwardedProtocol ) {
121+ if ( allowedDomain . protocol ) {
122+ if ( allowedDomain . protocol === forwardedProtocol ) {
123+ result . protocol = forwardedProtocol ;
124+ }
125+ } else if ( / ^ h t t p s ? $ / . test ( forwardedProtocol ) ) {
126+ // allowdDomains does not contain protocol, allow protocol
127+ result . protocol = forwardedProtocol ;
128+ }
129+ }
130+
140131 return result ;
141132}
0 commit comments