Skip to content

Commit 6081a85

Browse files
aeneasrclaude
andcommitted
fix: strip client-supplied Ory-* headers before re-applying
Prevents clients from spoofing Ory-Base-URL-Rewrite*, Ory-Base-URL-Rewrite-Token, and Ory-No-Custom-Domain-Redirect headers. Spoofed headers would otherwise be forwarded unchanged to the developer's upstream app, or — when no API key is configured — passed through to Ory. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 7aa57bf commit 6081a85

2 files changed

Lines changed: 46 additions & 0 deletions

File tree

cmd/cloudx/proxy/helpers.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,15 @@ and configure your SDKs to point to it, for example in JavaScript:
298298
// upstream application do not need — and must not receive — these headers.
299299
func reqMiddleware(conf *config, oryURL *url.URL, apiKey string) proxy.ReqMiddleware {
300300
return func(r *httputil.ProxyRequest, c *proxy.HostConfig, body []byte) ([]byte, error) {
301+
// Strip any client-supplied Ory-* headers before selectively re-applying
302+
// them below. Otherwise a client could spoof these headers: they would be
303+
// forwarded unchanged to the developer's upstream app, or — when apiKey is
304+
// empty — an attacker-supplied Ory-Base-URL-Rewrite-Token would be passed
305+
// through to Ory.
306+
r.Out.Header.Del("Ory-No-Custom-Domain-Redirect")
307+
r.Out.Header.Del("Ory-Base-URL-Rewrite")
308+
r.Out.Header.Del("Ory-Base-URL-Rewrite-Token")
309+
301310
if r.Out.URL.Host == oryURL.Host {
302311
r.Out.URL.Path = strings.TrimPrefix(r.Out.URL.Path, conf.pathPrefix)
303312
r.Out.Host = oryURL.Host

cmd/cloudx/proxy/helpers_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,41 @@ func TestReqMiddleware(t *testing.T) {
7070
assert.Equal(t, "upstream.internal", r.Out.Host)
7171
assert.Empty(t, r.Out.Header.Get(headerToken), "API key must not leak to the upstream app")
7272
})
73+
74+
t.Run("case=client-spoofed Ory headers are stripped from upstream-bound requests", func(t *testing.T) {
75+
conf := &config{publicURL: publicURL}
76+
r := newRequest(t, "localhost:3000")
77+
r.Out.Header.Set(headerToken, "ory_apikey_spoofed")
78+
r.Out.Header.Set(headerRewrite, "http://evil.example")
79+
r.Out.Header.Set(headerNoCustom, "true")
80+
81+
_, err := reqMiddleware(conf, oryURL, apiKey)(r, &proxy.HostConfig{}, nil)
82+
require.NoError(t, err)
83+
84+
assert.Empty(t, r.Out.Header.Get(headerToken), "spoofed token must not be forwarded to the upstream app")
85+
assert.Empty(t, r.Out.Header.Get(headerRewrite), "spoofed header must not be forwarded to the upstream app")
86+
assert.Empty(t, r.Out.Header.Get(headerNoCustom), "spoofed header must not be forwarded to the upstream app")
87+
})
88+
89+
t.Run("case=client-spoofed token is stripped from Ory-bound requests when apiKey is empty", func(t *testing.T) {
90+
conf := &config{publicURL: publicURL}
91+
r := newRequest(t, oryURL.Host)
92+
r.Out.Header.Set(headerToken, "ory_apikey_spoofed")
93+
94+
_, err := reqMiddleware(conf, oryURL, "")(r, &proxy.HostConfig{}, nil)
95+
require.NoError(t, err)
96+
97+
assert.Empty(t, r.Out.Header.Get(headerToken), "spoofed token must not be passed through to Ory when apiKey is empty")
98+
})
99+
100+
t.Run("case=real apiKey overwrites a client-spoofed token on Ory-bound requests", func(t *testing.T) {
101+
conf := &config{publicURL: publicURL}
102+
r := newRequest(t, oryURL.Host)
103+
r.Out.Header.Set(headerToken, "ory_apikey_spoofed")
104+
105+
_, err := reqMiddleware(conf, oryURL, apiKey)(r, &proxy.HostConfig{}, nil)
106+
require.NoError(t, err)
107+
108+
assert.Equal(t, apiKey, r.Out.Header.Get(headerToken), "genuine key must overwrite any spoofed token")
109+
})
73110
}

0 commit comments

Comments
 (0)