From a3ab87fd9f44be20b9ad08199c9a7f7ab3ac91c4 Mon Sep 17 00:00:00 2001 From: Susana Cardoso Ferreira Date: Thu, 27 Aug 2026 16:04:49 +0000 Subject: [PATCH 1/3] fix(aibridge): allow Copilot BYOK passthrough --- aibridge/bridge_test.go | 55 ++++++++++++++++++++++++++++------- aibridge/provider/copilot.go | 13 +++------ aibridge/provider/provider.go | 5 ++-- 3 files changed, 51 insertions(+), 22 deletions(-) diff --git a/aibridge/bridge_test.go b/aibridge/bridge_test.go index b1339d89cdc..30e2b172d6b 100644 --- a/aibridge/bridge_test.go +++ b/aibridge/bridge_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" + "golang.org/x/xerrors" "cdr.dev/slog/v3/sloggers/slogtest" "github.com/coder/coder/v2/aibridge" @@ -248,21 +249,13 @@ func TestPassthroughRoutesForProviders(t *testing.T) { expectPath: "/v1/models", }, { - name: "copilot_ping", - requestPath: "/copilot/_ping", - provider: func(_ *testing.T, baseURL string) provider.Provider { - return aibridge.NewCopilotProvider(config.Copilot{BaseURL: baseURL}) - }, - expectPath: "/_ping", - }, - { - name: "copilot_auto", + name: "copilot_unknown_route", requestMethod: http.MethodPost, - requestPath: "/copilot/auto", + requestPath: "/copilot/future/endpoint", provider: func(_ *testing.T, baseURL string) provider.Provider { return aibridge.NewCopilotProvider(config.Copilot{BaseURL: baseURL}) }, - expectPath: "/auto", + expectPath: "/future/endpoint", }, } @@ -294,6 +287,46 @@ func TestPassthroughRoutesForProviders(t *testing.T) { } } +func TestBridgedRouteTakesPrecedenceOverPassthroughCatchAll(t *testing.T) { + t.Parallel() + + upstreamCalled := false + interceptorCalled := false + upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + upstreamCalled = true + w.WriteHeader(http.StatusOK) + })) + t.Cleanup(upstream.Close) + + // Given: a provider with a bridged route and a passthrough catch-all. + prov := &testutil.MockProvider{ + NameStr: "test", + URL: upstream.URL, + Bridged: []string{"/responses"}, + Passthrough: []string{"/"}, + InterceptorFunc: func(http.ResponseWriter, *http.Request, trace.Tracer) (intercept.Interceptor, error) { + interceptorCalled = true + return nil, xerrors.New("test interceptor error") + }, + } + bridge, err := aibridge.NewRequestBridge( + t.Context(), + []provider.Provider{prov}, + nil, nil, slogtest.Make(t, nil), nil, bridgeTestTracer, + ) + require.NoError(t, err) + + // When: a request targets the bridged route. + req := httptest.NewRequest(http.MethodPost, "/test/responses", nil) + resp := httptest.NewRecorder() + bridge.ServeHTTP(resp, req) + + // Then: the interceptor handles it, not the passthrough upstream. + assert.Equal(t, http.StatusInternalServerError, resp.Code) + assert.True(t, interceptorCalled) + assert.False(t, upstreamCalled) +} + func TestWebSocketUpgradeRejected(t *testing.T) { t.Parallel() diff --git a/aibridge/provider/copilot.go b/aibridge/provider/copilot.go index 10d634c7bdf..ff05cd0008d 100644 --- a/aibridge/provider/copilot.go +++ b/aibridge/provider/copilot.go @@ -89,16 +89,11 @@ func (*Copilot) BridgedRoutes() []string { } } +// PassthroughRoutes allows all non-bridged routes because Copilot is always +// BYOK. The upstream enforces the user's permissions, so an allowlist is not +// needed to prevent access to privileged operations using shared credentials. func (*Copilot) PassthroughRoutes() []string { - return []string{ - "/_ping", - "/auto", - "/models", - "/models/", - "/agents/", - "/mcp/", - "/.well-known/", - } + return []string{"/"} } func (*Copilot) AuthHeader() string { diff --git a/aibridge/provider/provider.go b/aibridge/provider/provider.go index 7706d83ab11..3935ad55e8d 100644 --- a/aibridge/provider/provider.go +++ b/aibridge/provider/provider.go @@ -75,8 +75,9 @@ type Provider interface { // BridgedRoutes returns a slice of [http.ServeMux]-compatible routes which will have special handling. // See https://pkg.go.dev/net/http#hdr-Patterns-ServeMux. BridgedRoutes() []string - // PassthroughRoutes returns a slice of whitelisted [http.ServeMux]-compatible* routes which are - // not currently intercepted and must be handled by the upstream directly. + // PassthroughRoutes returns a slice of [http.ServeMux]-compatible* routes which are + // not currently intercepted and must be handled by the upstream directly. Providers + // using centralized credentials should restrict these routes to known-safe operations. // // * only path routes can be specified, not ones containing HTTP methods. (i.e. GET /route). // By default, these passthrough routes will accept any HTTP method. From 3ec400fe2f9afbcc61c7aa49f6610726dd594859 Mon Sep 17 00:00:00 2001 From: Susana Cardoso Ferreira Date: Fri, 11 Sep 2026 16:14:49 +0000 Subject: [PATCH 2/3] docs: clarify Copilot passthrough routes --- docs/ai-coder/ai-gateway/reference.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/ai-coder/ai-gateway/reference.md b/docs/ai-coder/ai-gateway/reference.md index 75fc21c2486..25fb82ff111 100644 --- a/docs/ai-coder/ai-gateway/reference.md +++ b/docs/ai-coder/ai-gateway/reference.md @@ -112,12 +112,17 @@ The Anthropic provider also serves the AWS Bedrock provider type. #### Passthrough +All Copilot routes other than the intercepted routes listed above pass through to the configured upstream provider. +Examples include: + - `/models(/*)` +- `/_ping` +- `/auto` - `/agents/*` - `/mcp/*` - `/.well-known/*` -Any route that is not listed above returns `404`. +AI Gateway authentication is still required. ## Troubleshooting From 7f86b73b2c2b137cd3d09434efcc7abb0e152ab0 Mon Sep 17 00:00:00 2001 From: Susana Cardoso Ferreira Date: Fri, 11 Sep 2026 16:20:18 +0000 Subject: [PATCH 3/3] docs: simplify Copilot passthrough reference --- docs/ai-coder/ai-gateway/reference.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/docs/ai-coder/ai-gateway/reference.md b/docs/ai-coder/ai-gateway/reference.md index 25fb82ff111..aed6c19cdf2 100644 --- a/docs/ai-coder/ai-gateway/reference.md +++ b/docs/ai-coder/ai-gateway/reference.md @@ -113,16 +113,6 @@ The Anthropic provider also serves the AWS Bedrock provider type. #### Passthrough All Copilot routes other than the intercepted routes listed above pass through to the configured upstream provider. -Examples include: - -- `/models(/*)` -- `/_ping` -- `/auto` -- `/agents/*` -- `/mcp/*` -- `/.well-known/*` - -AI Gateway authentication is still required. ## Troubleshooting