fix(security): enforce mandatory authentication and update deployment

Enforce strict Bearer token validation even when the bridge auth token is not explicitly configured in the environment. This ensures unauthenticated requests are rejected with a 401 status code by default. Updated deployment scripts to pass the required auth token and adjusted the test suite to align with the new security requirements.
This commit is contained in:
Haitao Pan 2026-04-16 18:50:47 +08:00
parent 75c940fba5
commit f30c8d4816
6 changed files with 26 additions and 16 deletions

View File

@ -568,6 +568,7 @@ func TestHandleRPCRequiresExplicitBearerForExternalProvider(t *testing.T) {
defer externalServer.Close()
t.Setenv("INTERNAL_SERVICE_TOKEN", "synced-provider-token")
t.Setenv("BRIDGE_AUTH_TOKEN", "")
server := NewServer()
setTestBridgeProvider(server, syncedProvider{
ProviderID: "codex",

View File

@ -305,10 +305,6 @@ func (s *Server) authorized(r *http.Request) bool {
if s == nil {
return false
}
expected := strings.TrimSpace(shared.EnvOrDefault("BRIDGE_AUTH_TOKEN", ""))
if expected == "" {
return true
}
if s.authService == nil {
return false
}

View File

@ -11,6 +11,7 @@ import (
func TestHTTPHandlerRootAndPingExposeRuntimeVersionInfo(t *testing.T) {
t.Setenv("IMAGE", "ghcr.io/x-evor/xworkmate-bridge:0123456789abcdef0123456789abcdef01234567")
t.Setenv("BRIDGE_AUTH_TOKEN", "")
server := NewServer()
handler := server.Handler()
@ -75,6 +76,7 @@ func TestParseImageVersionInfoHandlesTaggedImageRef(t *testing.T) {
func TestHandleWebSocketRejectsUnknownOrigin(t *testing.T) {
t.Setenv("ACP_ALLOWED_ORIGINS", "https://xworkmate.svc.plus")
t.Setenv("BRIDGE_AUTH_TOKEN", "")
server := NewServer()
recorder := httptest.NewRecorder()
@ -93,6 +95,7 @@ func TestHandleWebSocketRejectsUnknownOrigin(t *testing.T) {
func TestHandleRPCAllowsPreflightForConfiguredOrigin(t *testing.T) {
t.Setenv("ACP_ALLOWED_ORIGINS", "https://xworkmate.svc.plus,http://localhost:*")
t.Setenv("BRIDGE_AUTH_TOKEN", "")
server := NewServer()
recorder := httptest.NewRecorder()
@ -110,7 +113,8 @@ func TestHandleRPCAllowsPreflightForConfiguredOrigin(t *testing.T) {
}
}
func TestHandleRPCAllowsRequestsWhenBridgeAuthTokenUnset(t *testing.T) {
func TestHandleRPCRequiresAuthorizationEvenWhenBridgeAuthTokenUnset(t *testing.T) {
t.Setenv("BRIDGE_AUTH_TOKEN", "")
server := NewServer()
recorder := httptest.NewRecorder()
request := httptest.NewRequest(
@ -122,8 +126,8 @@ func TestHandleRPCAllowsRequestsWhenBridgeAuthTokenUnset(t *testing.T) {
server.HandleRPC(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("expected 200 when BRIDGE_AUTH_TOKEN is unset, got %d", recorder.Code)
if recorder.Code != http.StatusUnauthorized {
t.Fatalf("expected 401 when BRIDGE_AUTH_TOKEN is unset but no header provided, got %d", recorder.Code)
}
}
@ -148,6 +152,7 @@ func TestHandleRPCRequiresBearerAuthorizationWhenBridgeAuthTokenConfigured(t *te
func TestHandleRPCRejectsUnknownOrigin(t *testing.T) {
t.Setenv("ACP_ALLOWED_ORIGINS", "https://xworkmate.svc.plus")
t.Setenv("BRIDGE_AUTH_TOKEN", "")
server := NewServer()
recorder := httptest.NewRecorder()
@ -175,6 +180,7 @@ func TestHandleRPCRejectsUnknownOrigin(t *testing.T) {
}
func TestHandleRPCMethodErrorUsesJSONEnvelope(t *testing.T) {
t.Setenv("BRIDGE_AUTH_TOKEN", "")
server := NewServer()
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, "http://127.0.0.1/acp/rpc", nil)
@ -191,6 +197,7 @@ func TestHandleRPCMethodErrorUsesJSONEnvelope(t *testing.T) {
}
func TestHandleRPCCapabilitiesStillReturnsJSONResult(t *testing.T) {
t.Setenv("BRIDGE_AUTH_TOKEN", "")
server := NewServer()
recorder := httptest.NewRecorder()
request := httptest.NewRequest(
@ -214,15 +221,17 @@ func TestHandleRPCCapabilitiesStillReturnsJSONResult(t *testing.T) {
}
}
func TestAuthorizedAllowsRequestsWhenBridgeAuthTokenUnset(t *testing.T) {
func TestAuthorizedRejectsUnauthenticatedRequestsWhenBridgeAuthTokenUnset(t *testing.T) {
t.Setenv("BRIDGE_AUTH_TOKEN", "")
server := NewServer()
request := httptest.NewRequest(http.MethodGet, "http://127.0.0.1/acp", nil)
if !server.authorized(request) {
t.Fatal("expected requests to be authorized when BRIDGE_AUTH_TOKEN is unset")
if server.authorized(request) {
t.Fatal("expected unauthenticated request to be rejected even if BRIDGE_AUTH_TOKEN is unset")
}
}
func TestHandleRPCCapabilitiesReturnsCanonicalProviderContract(t *testing.T) {
t.Setenv("BRIDGE_AUTH_TOKEN", "")
server := NewServer()
recorder := httptest.NewRecorder()
request := httptest.NewRequest(
@ -301,6 +310,7 @@ func TestHandleRPCSessionStartSucceedsWithExplicitProvider(t *testing.T) {
defer externalServer.Close()
t.Setenv("INTERNAL_SERVICE_TOKEN", "internal-test-token")
t.Setenv("BRIDGE_AUTH_TOKEN", "")
server := NewServer()
setTestBridgeProvider(server, syncedProvider{

View File

@ -523,12 +523,11 @@ func (s *Server) applyCORS(w http.ResponseWriter, r *http.Request) {
}
func (s *Server) authorized(r *http.Request) bool {
if s == nil || s.authService == nil {
return true
if s == nil {
return false
}
expected := strings.TrimSpace(shared.EnvOrDefault("GEMINI_ADAPTER_AUTH_TOKEN", ""))
if expected == "" {
return true
if s.authService == nil {
return false
}
return s.authService.ValidateAuthorizationHeader(r.Header.Get("Authorization"))
}

View File

@ -85,6 +85,7 @@ func TestHandleRPCSessionStartReturnsUpstreamResult(t *testing.T) {
},
})
request := httptest.NewRequest(http.MethodPost, "http://127.0.0.1/acp/rpc", bytes.NewReader(body))
request.Header.Set("Authorization", "Bearer test-token")
recorder := httptest.NewRecorder()
server.HandleRPC(recorder, request)
@ -223,7 +224,9 @@ func TestHandleWebSocketCapabilities(t *testing.T) {
defer httpServer.Close()
wsURL := "ws" + httpServer.URL[len("http"):]
conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
header := http.Header{}
header.Set("Authorization", "Bearer test-token")
conn, _, err := websocket.DefaultDialer.Dial(wsURL, header)
if err != nil {
t.Fatalf("dial websocket: %v", err)
}

View File

@ -41,4 +41,5 @@ ANSIBLE_CONFIG="${PWD}/ansible.cfg" \
SERVICE_COMPOSE_IMAGE="${SERVICE_COMPOSE_IMAGE}" \
GHCR_USERNAME="${GHCR_USERNAME:-}" \
GHCR_PASSWORD="${GHCR_PASSWORD:-}" \
BRIDGE_AUTH_TOKEN="${INTERNAL_SERVICE_TOKEN:-}" \
"${args[@]}"