From f59f9a259bc0dd6b22c4e9562b7c3f4bebaf612e Mon Sep 17 00:00:00 2001 From: yekkhan-liftoff Date: Mon, 22 Jun 2026 11:08:57 +0800 Subject: [PATCH 1/7] fix(oauth): allow configured native client callbacks --- config.go | 11 ++++ config_test.go | 43 ++++++++++++++ docs/CONFIGURATION.md | 13 +++++ fixed_redirect_test.go | 129 +++++++++++++++++++++++++++++++++++++++++ handlers.go | 112 +++++++++++++++++++++-------------- 5 files changed, 266 insertions(+), 42 deletions(-) diff --git a/config.go b/config.go index ef67b30..89f3745 100644 --- a/config.go +++ b/config.go @@ -12,6 +12,9 @@ type Config struct { Mode string // "native" or "proxy" Provider string // "hmac", "okta", "google", "azure" RedirectURIs string // Redirect URIs (single or comma-separated) + // AllowedClientRedirectURIs are exact client callback URIs allowed in fixed + // redirect mode in addition to localhost/loopback callbacks. + AllowedClientRedirectURIs string // OIDC configuration Issuer string @@ -187,6 +190,13 @@ func (b *ConfigBuilder) WithRedirectURIs(uris string) *ConfigBuilder { return b } +// WithAllowedClientRedirectURIs sets exact client callback URIs allowed in +// fixed redirect mode in addition to localhost/loopback callbacks. +func (b *ConfigBuilder) WithAllowedClientRedirectURIs(uris string) *ConfigBuilder { + b.config.AllowedClientRedirectURIs = uris + return b +} + // WithIssuer sets the OIDC issuer func (b *ConfigBuilder) WithIssuer(issuer string) *ConfigBuilder { b.config.Issuer = issuer @@ -285,6 +295,7 @@ func FromEnv() (*Config, error) { WithMode(getEnv("OAUTH_MODE", "")). WithProvider(getEnv("OAUTH_PROVIDER", "")). WithRedirectURIs(getEnv("OAUTH_REDIRECT_URIS", "")). + WithAllowedClientRedirectURIs(getEnv("OAUTH_ALLOWED_CLIENT_REDIRECT_URIS", "")). WithIssuer(getEnv("OIDC_ISSUER", "")). WithAudience(getEnv("OIDC_AUDIENCE", "")). WithClientID(getEnv("OIDC_CLIENT_ID", "")). diff --git a/config_test.go b/config_test.go index 2217d04..c6b47e9 100644 --- a/config_test.go +++ b/config_test.go @@ -115,6 +115,49 @@ func TestConfigBuilder(t *testing.T) { } } +func TestAllowedClientRedirectURIsConfig(t *testing.T) { + cfg, err := NewConfigBuilder(). + WithMode("proxy"). + WithProvider("okta"). + WithIssuer("https://okta.example.com"). + WithAudience("test-audience"). + WithClientID("client-123"). + WithClientSecret("secret-456"). + WithRedirectURIs("https://mcp-server.com/oauth/callback"). + WithAllowedClientRedirectURIs("cursor://anysphere.cursor-mcp/oauth/callback"). + Build() + if err != nil { + t.Fatalf("Build() error = %v", err) + } + + oauth2Config := NewOAuth2ConfigFromConfig(cfg, "test") + if got, want := oauth2Config.AllowedClientRedirectURIs, "cursor://anysphere.cursor-mcp/oauth/callback"; got != want { + t.Fatalf("AllowedClientRedirectURIs = %q, want %q", got, want) + } +} + +func TestAllowedClientRedirectURIsEnvFallback(t *testing.T) { + t.Setenv("OAUTH_ALLOWED_CLIENT_REDIRECT_URIS", "cursor://anysphere.cursor-mcp/oauth/callback") + + cfg, err := NewConfigBuilder(). + WithMode("proxy"). + WithProvider("okta"). + WithIssuer("https://okta.example.com"). + WithAudience("test-audience"). + WithClientID("client-123"). + WithClientSecret("secret-456"). + WithRedirectURIs("https://mcp-server.com/oauth/callback"). + Build() + if err != nil { + t.Fatalf("Build() error = %v", err) + } + + oauth2Config := NewOAuth2ConfigFromConfig(cfg, "test") + if got, want := oauth2Config.AllowedClientRedirectURIs, "cursor://anysphere.cursor-mcp/oauth/callback"; got != want { + t.Fatalf("AllowedClientRedirectURIs = %q, want %q", got, want) + } +} + func TestOAuth2HandlerRequestsProviderDefaultScopes(t *testing.T) { tests := []struct { name string diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 969120d..d67d5c6 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -360,6 +360,17 @@ RedirectURIs: "https://your-server.com/oauth/callback" Server uses this URI with provider. For security, client redirects must be localhost only. +To support a native client callback in fixed redirect mode, keep `RedirectURIs` +as the server callback and add exact client callbacks separately: + +```go +RedirectURIs: "https://your-server.com/oauth/callback" +AllowedClientRedirectURIs: "cursor://anysphere.cursor-mcp/oauth/callback" +``` + +This preserves dynamic localhost callbacks for development tools while allowing +only the configured native client callbacks. + **Multiple URIs (Allowlist):** ```go @@ -554,6 +565,7 @@ OAUTH_CLIENT_ID=your-client-id OAUTH_CLIENT_SECRET=your-client-secret OAUTH_SERVER_URL=https://your-server.com OAUTH_REDIRECT_URIS=https://your-server.com/oauth/callback +OAUTH_ALLOWED_CLIENT_REDIRECT_URIS=cursor://anysphere.cursor-mcp/oauth/callback ``` Load in code: @@ -572,6 +584,7 @@ func main() { ClientSecret: os.Getenv("OAUTH_CLIENT_SECRET"), ServerURL: os.Getenv("OAUTH_SERVER_URL"), RedirectURIs: os.Getenv("OAUTH_REDIRECT_URIS"), + AllowedClientRedirectURIs: os.Getenv("OAUTH_ALLOWED_CLIENT_REDIRECT_URIS"), JWTSecret: []byte(os.Getenv("JWT_SECRET")), }) } diff --git a/fixed_redirect_test.go b/fixed_redirect_test.go index a4ef20a..3a06aaa 100644 --- a/fixed_redirect_test.go +++ b/fixed_redirect_test.go @@ -2,7 +2,13 @@ package oauth import ( "crypto/rand" + "net/http" + "net/http/httptest" + "net/url" + "strings" "testing" + + "golang.org/x/oauth2" ) func TestFixedRedirectModeLocalhostOnly(t *testing.T) { @@ -100,3 +106,126 @@ func TestFixedRedirectModeSecurityModel(t *testing.T) { t.Log("Use Case: Development tools (MCP Inspector) running on localhost") t.Log("Production: Use allowlist mode instead") } + +func TestFixedRedirectModeAllowsConfiguredClientRedirectURI(t *testing.T) { + handler := newFixedRedirectTestHandler(t, "cursor://anysphere.cursor-mcp/oauth/callback") + + req := httptest.NewRequest(http.MethodGet, "/oauth/authorize?client_id=test-client&redirect_uri="+url.QueryEscape("cursor://anysphere.cursor-mcp/oauth/callback")+"&response_type=code&code_challenge=test&code_challenge_method=S256&state=test-state", nil) + recorder := httptest.NewRecorder() + + handler.HandleAuthorize(recorder, req) + + if recorder.Code != http.StatusTemporaryRedirect { + t.Fatalf("status = %d, expected %d, body: %s", recorder.Code, http.StatusTemporaryRedirect, recorder.Body.String()) + } + + location := recorder.Header().Get("Location") + if !strings.HasPrefix(location, "https://okta.example/authorize?") { + t.Fatalf("Location = %q, expected Okta authorize redirect", location) + } + if !strings.Contains(location, url.QueryEscape("https://mcp-server.com/oauth/callback")) { + t.Fatalf("Location = %q, expected provider redirect_uri to remain the fixed server callback", location) + } +} + +func TestFixedRedirectModeRejectsUnconfiguredCustomScheme(t *testing.T) { + handler := newFixedRedirectTestHandler(t, "") + + req := httptest.NewRequest(http.MethodGet, "/oauth/authorize?client_id=test-client&redirect_uri="+url.QueryEscape("cursor://anysphere.cursor-mcp/oauth/callback")+"&response_type=code&code_challenge=test&code_challenge_method=S256&state=test-state", nil) + recorder := httptest.NewRecorder() + + handler.HandleAuthorize(recorder, req) + + if recorder.Code != http.StatusBadRequest { + t.Fatalf("status = %d, expected %d", recorder.Code, http.StatusBadRequest) + } + if !strings.Contains(recorder.Body.String(), "Invalid redirect_uri scheme") { + t.Fatalf("body = %q, expected Invalid redirect_uri scheme", recorder.Body.String()) + } +} + +func TestFixedRedirectModeStillAllowsLocalhostRedirectURI(t *testing.T) { + handler := newFixedRedirectTestHandler(t, "") + + req := httptest.NewRequest(http.MethodGet, "/oauth/authorize?client_id=test-client&redirect_uri="+url.QueryEscape("http://127.0.0.1:3333/oauth/callback")+"&response_type=code&code_challenge=test&code_challenge_method=S256&state=test-state", nil) + recorder := httptest.NewRecorder() + + handler.HandleAuthorize(recorder, req) + + if recorder.Code != http.StatusTemporaryRedirect { + t.Fatalf("status = %d, expected %d, body: %s", recorder.Code, http.StatusTemporaryRedirect, recorder.Body.String()) + } +} + +func TestFixedRedirectCallbackAllowsConfiguredClientRedirectURI(t *testing.T) { + handler := newFixedRedirectTestHandler(t, "cursor://anysphere.cursor-mcp/oauth/callback") + signedState, err := handler.signState(map[string]string{ + "state": "client-state", + "redirect": "cursor://anysphere.cursor-mcp/oauth/callback", + }) + if err != nil { + t.Fatalf("sign state: %v", err) + } + + req := httptest.NewRequest(http.MethodGet, "/oauth/callback?code=auth-code&state="+url.QueryEscape(signedState), nil) + recorder := httptest.NewRecorder() + + handler.HandleCallback(recorder, req) + + if recorder.Code != http.StatusFound { + t.Fatalf("status = %d, expected %d, body: %s", recorder.Code, http.StatusFound, recorder.Body.String()) + } + location := recorder.Header().Get("Location") + if !strings.HasPrefix(location, "cursor://anysphere.cursor-mcp/oauth/callback?") { + t.Fatalf("Location = %q, expected Cursor callback redirect", location) + } + if !strings.Contains(location, "code=auth-code") || !strings.Contains(location, "state=client-state") { + t.Fatalf("Location = %q, expected code and original state", location) + } +} + +func TestFixedRedirectCallbackRejectsUnconfiguredCustomScheme(t *testing.T) { + handler := newFixedRedirectTestHandler(t, "") + signedState, err := handler.signState(map[string]string{ + "state": "client-state", + "redirect": "cursor://anysphere.cursor-mcp/oauth/callback", + }) + if err != nil { + t.Fatalf("sign state: %v", err) + } + + req := httptest.NewRequest(http.MethodGet, "/oauth/callback?code=auth-code&state="+url.QueryEscape(signedState), nil) + recorder := httptest.NewRecorder() + + handler.HandleCallback(recorder, req) + + if recorder.Code != http.StatusBadRequest { + t.Fatalf("status = %d, expected %d", recorder.Code, http.StatusBadRequest) + } + if !strings.Contains(recorder.Body.String(), "Invalid redirect URI in state") { + t.Fatalf("body = %q, expected Invalid redirect URI in state", recorder.Body.String()) + } +} + +func newFixedRedirectTestHandler(t *testing.T, allowedClientRedirectURIs string) *OAuth2Handler { + t.Helper() + + key := make([]byte, 32) + _, _ = rand.Read(key) + + return &OAuth2Handler{ + config: &OAuth2Config{ + RedirectURIs: "https://mcp-server.com/oauth/callback", + AllowedClientRedirectURIs: allowedClientRedirectURIs, + stateSigningKey: key, + }, + oauth2Config: &oauth2.Config{ + ClientID: "test-client", + RedirectURL: "https://mcp-server.com/oauth/callback", + Endpoint: oauth2.Endpoint{ + AuthURL: "https://okta.example/authorize", + }, + }, + logger: &defaultLogger{}, + } +} diff --git a/handlers.go b/handlers.go index fd7dd10..a464786 100644 --- a/handlers.go +++ b/handlers.go @@ -36,10 +36,11 @@ func (h *OAuth2Handler) GetConfig() *OAuth2Config { // OAuth2Config holds OAuth2 configuration type OAuth2Config struct { - Enabled bool - Mode string // "native" or "proxy" - Provider string - RedirectURIs string + Enabled bool + Mode string // "native" or "proxy" + Provider string + RedirectURIs string + AllowedClientRedirectURIs string // OIDC configuration Issuer string @@ -186,21 +187,27 @@ func NewOAuth2ConfigFromConfig(cfg *Config, version string) *OAuth2Config { mcpURL = getEnv("MCP_URL", fmt.Sprintf("%s://%s:%s", scheme, mcpHost, mcpPort)) } + allowedClientRedirectURIs := cfg.AllowedClientRedirectURIs + if allowedClientRedirectURIs == "" { + allowedClientRedirectURIs = getEnv("OAUTH_ALLOWED_CLIENT_REDIRECT_URIS", "") + } + return &OAuth2Config{ - Enabled: true, - Mode: cfg.Mode, - Provider: cfg.Provider, - RedirectURIs: cfg.RedirectURIs, - Issuer: cfg.Issuer, - Audience: cfg.Audience, - ClientID: cfg.ClientID, - ClientSecret: cfg.ClientSecret, - MCPHost: mcpHost, - MCPPort: mcpPort, - MCPURL: mcpURL, - Scheme: scheme, - Version: version, - stateSigningKey: cfg.JWTSecret, + Enabled: true, + Mode: cfg.Mode, + Provider: cfg.Provider, + RedirectURIs: cfg.RedirectURIs, + AllowedClientRedirectURIs: allowedClientRedirectURIs, + Issuer: cfg.Issuer, + Audience: cfg.Audience, + ClientID: cfg.ClientID, + ClientSecret: cfg.ClientSecret, + MCPHost: mcpHost, + MCPPort: mcpPort, + MCPURL: mcpURL, + Scheme: scheme, + Version: version, + stateSigningKey: cfg.JWTSecret, } } @@ -321,20 +328,6 @@ func (h *OAuth2Handler) HandleAuthorize(w http.ResponseWriter, r *http.Request) return } - // Additional security checks for client redirect URI - if parsedURI.Scheme != "http" && parsedURI.Scheme != "https" { - h.logger.Warn("SECURITY: Invalid redirect URI scheme: %s (must be http or https)", parsedURI.Scheme) - http.Error(w, "Invalid redirect_uri scheme", http.StatusBadRequest) - return - } - - // Enforce HTTPS for non-localhost URIs - if parsedURI.Scheme == "http" && !isLocalhostURI(clientRedirectURI) { - h.logger.Warn("SECURITY: HTTP redirect URI not allowed for non-localhost: %s", clientRedirectURI) - http.Error(w, "HTTPS required for non-localhost redirect_uri", http.StatusBadRequest) - return - } - // Prevent fragment in redirect URI (OAuth 2.0 spec) if parsedURI.Fragment != "" { h.logger.Warn("SECURITY: Redirect URI contains fragment: %s", clientRedirectURI) @@ -342,15 +335,13 @@ func (h *OAuth2Handler) HandleAuthorize(w http.ResponseWriter, r *http.Request) return } - // Security: For fixed redirect mode, only allow localhost or loopback addresses - // This prevents open redirect attacks while still supporting development tools - if !isLocalhostURI(clientRedirectURI) { - h.logger.Warn("SECURITY: Fixed redirect mode only allows localhost URIs, rejecting: %s from %s", clientRedirectURI, r.RemoteAddr) - http.Error(w, "Fixed redirect mode only allows localhost redirect URIs for security. Use allowlist mode for production.", http.StatusBadRequest) + if err := h.validateFixedModeClientRedirectURI(clientRedirectURI, parsedURI); err != nil { + h.logger.Warn("SECURITY: Fixed redirect mode rejected client redirect URI %s from %s: %v", clientRedirectURI, r.RemoteAddr, err) + http.Error(w, err.Error(), http.StatusBadRequest) return } - h.logger.Info("OAuth2: Validated localhost redirect URI for proxy: %s", clientRedirectURI) + h.logger.Info("OAuth2: Validated client redirect URI for proxy: %s", clientRedirectURI) } else if h.config.RedirectURIs != "" { // Allowlist mode: Client's URI must be in allowlist, used directly (no proxy) if !h.isValidRedirectURI(clientRedirectURI) { @@ -465,10 +456,15 @@ func (h *OAuth2Handler) HandleCallback(w http.ResponseWriter, r *http.Request) { originalRedirectURI, hasRedirect := stateData["redirect"] if hasState && hasRedirect { - // Re-validate redirect URI for defense in depth - // Even though state is HMAC-signed, validate the redirect URI is localhost - if !isLocalhostURI(originalRedirectURI) { - h.logger.Warn("SECURITY: Callback redirect URI is not localhost (possible key compromise): %s", originalRedirectURI) + parsedURI, err := url.Parse(originalRedirectURI) + if err != nil { + h.logger.Warn("SECURITY: Invalid callback redirect URI format in state: %s", originalRedirectURI) + http.Error(w, "Invalid redirect URI in state", http.StatusBadRequest) + return + } + + if err := h.validateFixedModeClientRedirectURI(originalRedirectURI, parsedURI); err != nil { + h.logger.Warn("SECURITY: Callback redirect URI rejected during state revalidation: %s", originalRedirectURI) http.Error(w, "Invalid redirect URI in state", http.StatusBadRequest) return } @@ -796,6 +792,38 @@ func isLocalhostURI(uri string) bool { return hostname == "localhost" || hostname == "127.0.0.1" || hostname == "::1" } +func (h *OAuth2Handler) validateFixedModeClientRedirectURI(uri string, parsedURI *url.URL) error { + if parsedURI.Scheme == "http" || parsedURI.Scheme == "https" { + if parsedURI.Scheme == "http" && !isLocalhostURI(uri) { + return fmt.Errorf("HTTPS required for non-localhost redirect_uri") + } + if !isLocalhostURI(uri) { + return fmt.Errorf("Fixed redirect mode only allows localhost redirect URIs for security. Use allowlist mode for production.") + } + return nil + } + + if h.isAllowedClientRedirectURI(uri) { + return nil + } + + return fmt.Errorf("Invalid redirect_uri scheme") +} + +func (h *OAuth2Handler) isAllowedClientRedirectURI(uri string) bool { + if h.config.AllowedClientRedirectURIs == "" { + return false + } + + for _, allowed := range strings.Split(h.config.AllowedClientRedirectURIs, ",") { + if strings.TrimSpace(allowed) == uri { + return true + } + } + + return false +} + // isValidRedirectURI validates redirect URI against allowlist for security func (h *OAuth2Handler) isValidRedirectURI(uri string) bool { if h.config.RedirectURIs == "" { From db0858bf9a1d15cebb5fe473c9766eb735da59b3 Mon Sep 17 00:00:00 2001 From: yekkhan-liftoff Date: Mon, 22 Jun 2026 11:51:19 +0800 Subject: [PATCH 2/7] fix(oauth): use lint-compliant redirect errors --- fixed_redirect_test.go | 14 +++++++------- handlers.go | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/fixed_redirect_test.go b/fixed_redirect_test.go index 3a06aaa..e3a5bb6 100644 --- a/fixed_redirect_test.go +++ b/fixed_redirect_test.go @@ -45,19 +45,19 @@ func TestFixedRedirectModeLocalhostOnly(t *testing.T) { name: "HTTPS production domain rejected", clientURI: "https://evil.com/callback", shouldPass: false, - expectedError: "Fixed redirect mode only allows localhost", + expectedError: "fixed redirect mode only allows localhost", }, { name: "HTTP production domain rejected", clientURI: "http://evil.com/callback", shouldPass: false, - expectedError: "HTTPS required for non-localhost", + expectedError: "https required for non-localhost", }, { name: "localhost subdomain rejected", clientURI: "https://localhost.evil.com/callback", shouldPass: false, - expectedError: "Fixed redirect mode only allows localhost", + expectedError: "fixed redirect mode only allows localhost", }, { name: "URI with fragment rejected", @@ -69,7 +69,7 @@ func TestFixedRedirectModeLocalhostOnly(t *testing.T) { name: "Custom scheme rejected", clientURI: "custom://localhost:8080/callback", shouldPass: false, - expectedError: "Invalid redirect_uri scheme", + expectedError: "invalid redirect_uri scheme", }, } @@ -81,7 +81,7 @@ func TestFixedRedirectModeLocalhostOnly(t *testing.T) { t.Errorf("Expected localhost detection to pass for %s", tt.clientURI) } - if !tt.shouldPass && isLocalhost && tt.expectedError != "must not contain fragment" && tt.expectedError != "Invalid redirect_uri scheme" { + if !tt.shouldPass && isLocalhost && tt.expectedError != "must not contain fragment" && tt.expectedError != "invalid redirect_uri scheme" { t.Errorf("Expected localhost detection to fail for %s", tt.clientURI) } @@ -139,8 +139,8 @@ func TestFixedRedirectModeRejectsUnconfiguredCustomScheme(t *testing.T) { if recorder.Code != http.StatusBadRequest { t.Fatalf("status = %d, expected %d", recorder.Code, http.StatusBadRequest) } - if !strings.Contains(recorder.Body.String(), "Invalid redirect_uri scheme") { - t.Fatalf("body = %q, expected Invalid redirect_uri scheme", recorder.Body.String()) + if !strings.Contains(recorder.Body.String(), "invalid redirect_uri scheme") { + t.Fatalf("body = %q, expected invalid redirect_uri scheme", recorder.Body.String()) } } diff --git a/handlers.go b/handlers.go index a464786..da5f7cb 100644 --- a/handlers.go +++ b/handlers.go @@ -795,10 +795,10 @@ func isLocalhostURI(uri string) bool { func (h *OAuth2Handler) validateFixedModeClientRedirectURI(uri string, parsedURI *url.URL) error { if parsedURI.Scheme == "http" || parsedURI.Scheme == "https" { if parsedURI.Scheme == "http" && !isLocalhostURI(uri) { - return fmt.Errorf("HTTPS required for non-localhost redirect_uri") + return fmt.Errorf("https required for non-localhost redirect_uri") } if !isLocalhostURI(uri) { - return fmt.Errorf("Fixed redirect mode only allows localhost redirect URIs for security. Use allowlist mode for production.") + return fmt.Errorf("fixed redirect mode only allows localhost redirect URIs for security; use allowlist mode for production") } return nil } @@ -807,7 +807,7 @@ func (h *OAuth2Handler) validateFixedModeClientRedirectURI(uri string, parsedURI return nil } - return fmt.Errorf("Invalid redirect_uri scheme") + return fmt.Errorf("invalid redirect_uri scheme") } func (h *OAuth2Handler) isAllowedClientRedirectURI(uri string) bool { From ba807dc62706d4e01a332bf1bf83ccf3454d1ef5 Mon Sep 17 00:00:00 2001 From: yekkhan-liftoff Date: Mon, 22 Jun 2026 11:54:16 +0800 Subject: [PATCH 3/7] ci: fix trivy action tag --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0601f02..5771ca3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -92,7 +92,7 @@ jobs: govulncheck ./... - name: Run dependency scan - uses: aquasecurity/trivy-action@0.33.1 + uses: aquasecurity/trivy-action@v0.33.1 with: scan-type: "fs" scan-ref: "." From 17e6dc96ab3235139e33c68983892a98a6efac34 Mon Sep 17 00:00:00 2001 From: yekkhan-liftoff Date: Mon, 22 Jun 2026 11:58:36 +0800 Subject: [PATCH 4/7] ci: pin govulncheck for go 1.24 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5771ca3..7b996fe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -88,7 +88,7 @@ jobs: - name: Run Go Vulnerability Check run: | - go install golang.org/x/vuln/cmd/govulncheck@latest + go install golang.org/x/vuln/cmd/govulncheck@v1.1.4 govulncheck ./... - name: Run dependency scan From 3045aabd2d142f199d3f42845c8b5bb2122eba37 Mon Sep 17 00:00:00 2001 From: yekkhan-liftoff Date: Mon, 22 Jun 2026 12:06:06 +0800 Subject: [PATCH 5/7] ci: update security scan dependencies --- .github/workflows/build.yml | 4 ++-- go.mod | 13 ++++++++----- go.sum | 26 ++++++++++++++++---------- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7b996fe..46998f1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ permissions: checks: write env: - GO_VERSION: "1.24.9" + GO_VERSION: "1.25.11" jobs: # Static analysis and code quality check @@ -88,7 +88,7 @@ jobs: - name: Run Go Vulnerability Check run: | - go install golang.org/x/vuln/cmd/govulncheck@v1.1.4 + go install golang.org/x/vuln/cmd/govulncheck@v1.3.0 govulncheck ./... - name: Run dependency scan diff --git a/go.mod b/go.mod index 3e801e3..59738ea 100644 --- a/go.mod +++ b/go.mod @@ -1,25 +1,28 @@ module github.com/Vungle/oauth-mcp-proxy -go 1.24.9 +go 1.25.11 require ( github.com/coreos/go-oidc/v3 v3.16.0 github.com/golang-jwt/jwt/v5 v5.3.0 github.com/mark3labs/mcp-go v0.41.1 - github.com/modelcontextprotocol/go-sdk v1.0.0 - golang.org/x/oauth2 v0.32.0 + github.com/modelcontextprotocol/go-sdk v1.4.1 + golang.org/x/oauth2 v0.34.0 ) require ( github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect - github.com/go-jose/go-jose/v4 v4.1.3 // indirect - github.com/google/jsonschema-go v0.3.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.4 // indirect + github.com/google/jsonschema-go v0.4.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/invopop/jsonschema v0.13.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/segmentio/asm v1.1.3 // indirect + github.com/segmentio/encoding v0.5.4 // indirect github.com/spf13/cast v1.8.0 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect + golang.org/x/sys v0.40.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index eb5198f..505207e 100644 --- a/go.sum +++ b/go.sum @@ -8,14 +8,14 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= -github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q= -github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= +github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= +github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= @@ -29,12 +29,16 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mark3labs/mcp-go v0.41.1 h1:w78eWfiQam2i8ICL7AL0WFiq7KHNJQ6UB53ZVtH4KGA= github.com/mark3labs/mcp-go v0.41.1/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZHRymCn39g= -github.com/modelcontextprotocol/go-sdk v1.0.0 h1:Z4MSjLi38bTgLrd/LjSmofqRqyBiVKRyQSJgw8q8V74= -github.com/modelcontextprotocol/go-sdk v1.0.0/go.mod h1:nYtYQroQ2KQiM0/SbyEPUWQ6xs4B95gJjEalc9AQyOs= +github.com/modelcontextprotocol/go-sdk v1.4.1 h1:M4x9GyIPj+HoIlHNGpK2hq5o3BFhC+78PkEaldQRphc= +github.com/modelcontextprotocol/go-sdk v1.4.1/go.mod h1:Bo/mS87hPQqHSRkMv4dQq1XCu6zv4INdXnFZabkNU6s= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= +github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= +github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= +github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk= github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= @@ -43,10 +47,12 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/ github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= -golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= -golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= -golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= -golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= From ab0432299e23de6809b5ab5e96a15c7b18245ee5 Mon Sep 17 00:00:00 2001 From: yekkhan-liftoff Date: Mon, 22 Jun 2026 12:24:34 +0800 Subject: [PATCH 6/7] ci: retrigger checks From e3d171e3161fc18f3f1cedab7348358be2b443e0 Mon Sep 17 00:00:00 2001 From: yekkhan-liftoff Date: Mon, 22 Jun 2026 12:33:25 +0800 Subject: [PATCH 7/7] ci: update trivy scanner --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 46998f1..ba508db 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -92,7 +92,7 @@ jobs: govulncheck ./... - name: Run dependency scan - uses: aquasecurity/trivy-action@v0.33.1 + uses: aquasecurity/trivy-action@v0.36.0 with: scan-type: "fs" scan-ref: "." @@ -100,6 +100,7 @@ jobs: output: "trivy-results.sarif" severity: "CRITICAL,HIGH,MEDIUM" timeout: "10m" + version: "v0.71.2" - name: Upload security scan results uses: github/codeql-action/upload-sarif@v4