From 305abcfb3d5d9a0c9f4a6e8c97fda777ffc00783 Mon Sep 17 00:00:00 2001 From: Arne Luenser Date: Wed, 19 Aug 2026 15:51:30 +0200 Subject: [PATCH 1/2] feat: send the rate-limit exemption header on tunnel and proxy requests The ORY_RATE_LIMIT_HEADER environment variable already exempts SDK API calls and wrapped CLI commands from Ory Network's per-IP rate limits. Attach the same header to Ory-bound requests of the reverse proxy behind `ory tunnel` and `ory proxy`, so that E2E suites driving traffic through the tunnel are not rate limited. The header is attached only to requests destined for Ory Network, never to the developer's upstream application, and a client-supplied header still passes through when no value is configured. Co-Authored-By: Claude Fable 5 --- cmd/cloudx/proxy/helpers.go | 16 ++++++--- cmd/cloudx/proxy/helpers_test.go | 59 ++++++++++++++++++++++++++++---- 2 files changed, 64 insertions(+), 11 deletions(-) diff --git a/cmd/cloudx/proxy/helpers.go b/cmd/cloudx/proxy/helpers.go index 2a4d1a43..b8ccf219 100644 --- a/cmd/cloudx/proxy/helpers.go +++ b/cmd/cloudx/proxy/helpers.go @@ -143,6 +143,8 @@ func runReverseProxy(ctx context.Context, h *client.CommandHelper, stdErr io.Wri oryURL := client.CloudAPIsURL(project.Slug + ".projects") oryURL.Host = strings.TrimSuffix(oryURL.Host, ":443") + rateLimitName, rateLimitValue, _ := client.RateLimitHeader() + writer := herodot.NewJSONWriter(&errorLogger{Writer: stdErr}) mw := negroni.New() @@ -194,7 +196,7 @@ func runReverseProxy(ctx context.Context, h *client.CommandHelper, stdErr io.Wri PathPrefix: "", }, nil }, - proxy.WithReqMiddleware(reqMiddleware(conf, oryURL, apiKey)), + proxy.WithReqMiddleware(reqMiddleware(conf, oryURL, apiKey, rateLimitName, rateLimitValue)), proxy.WithRespMiddleware(respMiddleware(conf)), )) @@ -272,10 +274,11 @@ and configure your SDKs to point to it, for example in JavaScript: } // reqMiddleware returns the request middleware used by the reverse proxy. The -// Ory-* headers (including the temporary API key in Ory-Base-URL-Rewrite-Token) -// are only attached to Ory-bound requests. Requests forwarded to the developer's -// upstream application do not need — and must not receive — these headers. -func reqMiddleware(conf *config, oryURL *url.URL, apiKey string) proxy.ReqMiddleware { +// Ory-* headers (including the temporary API key in Ory-Base-URL-Rewrite-Token +// and the rate-limit exemption header) are only attached to Ory-bound requests. +// Requests forwarded to the developer's upstream application do not need — and +// must not receive — these headers. +func reqMiddleware(conf *config, oryURL *url.URL, apiKey, rateLimitName, rateLimitValue string) proxy.ReqMiddleware { return func(r *httputil.ProxyRequest, c *proxy.HostConfig, body []byte) ([]byte, error) { // Strip any client-supplied Ory-* headers before selectively re-applying // them below. Otherwise a client could spoof these headers: they would be @@ -300,6 +303,9 @@ func reqMiddleware(conf *config, oryURL *url.URL, apiKey string) proxy.ReqMiddle if len(apiKey) > 0 { r.Out.Header.Set("Ory-Base-URL-Rewrite-Token", apiKey) } + if rateLimitValue != "" { + r.Out.Header.Set(rateLimitName, rateLimitValue) + } } else if conf.rewriteHost { r.Out.Header.Set("X-Forwarded-Host", r.In.Host) r.Out.Host = c.UpstreamHost diff --git a/cmd/cloudx/proxy/helpers_test.go b/cmd/cloudx/proxy/helpers_test.go index db0dfc74..7781d44e 100644 --- a/cmd/cloudx/proxy/helpers_test.go +++ b/cmd/cloudx/proxy/helpers_test.go @@ -153,7 +153,7 @@ func TestReqMiddleware(t *testing.T) { conf := &config{publicURL: publicURL} r := newRequest(t, oryURL.Host) - _, err := reqMiddleware(conf, oryURL, apiKey)(r, &proxy.HostConfig{}, nil) + _, err := reqMiddleware(conf, oryURL, apiKey, "", "")(r, &proxy.HostConfig{}, nil) require.NoError(t, err) assert.Equal(t, apiKey, r.Out.Header.Get(headerToken)) @@ -165,7 +165,7 @@ func TestReqMiddleware(t *testing.T) { conf := &config{publicURL: publicURL} r := newRequest(t, "localhost:3000") - _, err := reqMiddleware(conf, oryURL, apiKey)(r, &proxy.HostConfig{}, nil) + _, err := reqMiddleware(conf, oryURL, apiKey, "", "")(r, &proxy.HostConfig{}, nil) require.NoError(t, err) assert.Empty(t, r.Out.Header.Get(headerToken), "API key must not leak to the upstream app") @@ -177,7 +177,7 @@ func TestReqMiddleware(t *testing.T) { conf := &config{publicURL: publicURL, rewriteHost: true} r := newRequest(t, "localhost:3000") - _, err := reqMiddleware(conf, oryURL, apiKey)(r, &proxy.HostConfig{UpstreamHost: "upstream.internal"}, nil) + _, err := reqMiddleware(conf, oryURL, apiKey, "", "")(r, &proxy.HostConfig{UpstreamHost: "upstream.internal"}, nil) require.NoError(t, err) assert.Equal(t, r.In.Host, r.Out.Header.Get("X-Forwarded-Host")) @@ -192,7 +192,7 @@ func TestReqMiddleware(t *testing.T) { r.Out.Header.Set(headerRewrite, "http://evil.example") r.Out.Header.Set(headerNoCustom, "true") - _, err := reqMiddleware(conf, oryURL, apiKey)(r, &proxy.HostConfig{}, nil) + _, err := reqMiddleware(conf, oryURL, apiKey, "", "")(r, &proxy.HostConfig{}, nil) require.NoError(t, err) assert.Empty(t, r.Out.Header.Get(headerToken), "spoofed token must not be forwarded to the upstream app") @@ -205,7 +205,7 @@ func TestReqMiddleware(t *testing.T) { r := newRequest(t, oryURL.Host) r.Out.Header.Set(headerToken, "ory_apikey_spoofed") - _, err := reqMiddleware(conf, oryURL, "")(r, &proxy.HostConfig{}, nil) + _, err := reqMiddleware(conf, oryURL, "", "", "")(r, &proxy.HostConfig{}, nil) require.NoError(t, err) assert.Empty(t, r.Out.Header.Get(headerToken), "spoofed token must not be passed through to Ory when apiKey is empty") @@ -216,9 +216,56 @@ func TestReqMiddleware(t *testing.T) { r := newRequest(t, oryURL.Host) r.Out.Header.Set(headerToken, "ory_apikey_spoofed") - _, err := reqMiddleware(conf, oryURL, apiKey)(r, &proxy.HostConfig{}, nil) + _, err := reqMiddleware(conf, oryURL, apiKey, "", "")(r, &proxy.HostConfig{}, nil) require.NoError(t, err) assert.Equal(t, apiKey, r.Out.Header.Get(headerToken), "genuine key must overwrite any spoofed token") }) + + const ( + headerRateLimit = "Ory-RateLimit-Action" + rateLimitSecret = "rate-limit-secret-sentinel" + ) + + t.Run("case=ory-bound request receives the rate-limit exemption header when configured", func(t *testing.T) { + conf := &config{publicURL: publicURL} + r := newRequest(t, oryURL.Host) + + _, err := reqMiddleware(conf, oryURL, apiKey, headerRateLimit, rateLimitSecret)(r, &proxy.HostConfig{}, nil) + require.NoError(t, err) + + assert.Equal(t, rateLimitSecret, r.Out.Header.Get(headerRateLimit)) + }) + + t.Run("case=upstream-bound request does not receive the rate-limit exemption header", func(t *testing.T) { + conf := &config{publicURL: publicURL} + r := newRequest(t, "localhost:3000") + + _, err := reqMiddleware(conf, oryURL, apiKey, headerRateLimit, rateLimitSecret)(r, &proxy.HostConfig{}, nil) + require.NoError(t, err) + + assert.Empty(t, r.Out.Header.Get(headerRateLimit), "rate-limit secret must not leak to the upstream app") + }) + + t.Run("case=configured rate-limit header overwrites a client-supplied value on Ory-bound requests", func(t *testing.T) { + conf := &config{publicURL: publicURL} + r := newRequest(t, oryURL.Host) + r.Out.Header.Set(headerRateLimit, "client-supplied") + + _, err := reqMiddleware(conf, oryURL, apiKey, headerRateLimit, rateLimitSecret)(r, &proxy.HostConfig{}, nil) + require.NoError(t, err) + + assert.Equal(t, rateLimitSecret, r.Out.Header.Get(headerRateLimit), "configured secret must win over a client-supplied value") + }) + + t.Run("case=client-supplied rate-limit header passes through when none is configured", func(t *testing.T) { + conf := &config{publicURL: publicURL} + r := newRequest(t, oryURL.Host) + r.Out.Header.Set(headerRateLimit, "client-supplied") + + _, err := reqMiddleware(conf, oryURL, apiKey, "", "")(r, &proxy.HostConfig{}, nil) + require.NoError(t, err) + + assert.Equal(t, "client-supplied", r.Out.Header.Get(headerRateLimit), "callers may still send the header themselves") + }) } From ee65afabaab489df9b0a27c0f02cdf08690963dc Mon Sep 17 00:00:00 2001 From: Arne Luenser Date: Wed, 19 Aug 2026 16:03:21 +0200 Subject: [PATCH 2/2] fix: strip the rate-limit exemption header from upstream-bound requests The header is meaningful only to Ory Network. Remove it from requests forwarded to the developer's upstream application regardless of whether a value is configured, so client-supplied values never reach the app. Ory-bound passthrough of a client-supplied header when no value is configured is unchanged. Co-Authored-By: Claude Fable 5 --- cmd/cloudx/proxy/helpers.go | 11 ++++++++--- cmd/cloudx/proxy/helpers_test.go | 15 ++++++++++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/cmd/cloudx/proxy/helpers.go b/cmd/cloudx/proxy/helpers.go index b8ccf219..4752ebc8 100644 --- a/cmd/cloudx/proxy/helpers.go +++ b/cmd/cloudx/proxy/helpers.go @@ -306,9 +306,14 @@ func reqMiddleware(conf *config, oryURL *url.URL, apiKey, rateLimitName, rateLim if rateLimitValue != "" { r.Out.Header.Set(rateLimitName, rateLimitValue) } - } else if conf.rewriteHost { - r.Out.Header.Set("X-Forwarded-Host", r.In.Host) - r.Out.Host = c.UpstreamHost + } else { + // The rate-limit exemption header is meaningful only to Ory Network + // and must never be forwarded to the developer's upstream application. + r.Out.Header.Del(rateLimitName) + if conf.rewriteHost { + r.Out.Header.Set("X-Forwarded-Host", r.In.Host) + r.Out.Host = c.UpstreamHost + } } return body, nil diff --git a/cmd/cloudx/proxy/helpers_test.go b/cmd/cloudx/proxy/helpers_test.go index 7781d44e..7544b492 100644 --- a/cmd/cloudx/proxy/helpers_test.go +++ b/cmd/cloudx/proxy/helpers_test.go @@ -263,9 +263,22 @@ func TestReqMiddleware(t *testing.T) { r := newRequest(t, oryURL.Host) r.Out.Header.Set(headerRateLimit, "client-supplied") - _, err := reqMiddleware(conf, oryURL, apiKey, "", "")(r, &proxy.HostConfig{}, nil) + _, err := reqMiddleware(conf, oryURL, apiKey, headerRateLimit, "")(r, &proxy.HostConfig{}, nil) require.NoError(t, err) assert.Equal(t, "client-supplied", r.Out.Header.Get(headerRateLimit), "callers may still send the header themselves") }) + + t.Run("case=client-supplied rate-limit header is stripped from upstream-bound requests", func(t *testing.T) { + for _, value := range []string{rateLimitSecret, ""} { + conf := &config{publicURL: publicURL} + r := newRequest(t, "localhost:3000") + r.Out.Header.Set(headerRateLimit, "client-supplied") + + _, err := reqMiddleware(conf, oryURL, apiKey, headerRateLimit, value)(r, &proxy.HostConfig{}, nil) + require.NoError(t, err) + + assert.Empty(t, r.Out.Header.Get(headerRateLimit), "the header is meaningful only to Ory Network and must not reach the upstream app (configured value: %q)", value) + } + }) }