diff --git a/workspace-server/internal/handlers/a2a_proxy_helpers.go b/workspace-server/internal/handlers/a2a_proxy_helpers.go index 98c51bb7d..11916e6b1 100644 --- a/workspace-server/internal/handlers/a2a_proxy_helpers.go +++ b/workspace-server/internal/handlers/a2a_proxy_helpers.go @@ -407,15 +407,6 @@ func validateCallerToken(ctx context.Context, c *gin.Context, callerID string) e // matching (the wsauth errors are typed for the invalid case). var errInvalidCallerToken = errors.New("missing caller auth token") -// canvasUserMessage holds the extracted user message extracted from an -// A2A canvas request body for broadcasting to other sessions. -type canvasUserMessage struct { - Message string `json:"message,omitempty"` - Parts []map[string]interface{} `json:"parts,omitempty"` - MessageID string `json:"messageId,omitempty"` - Attachments []map[string]interface{} `json:"attachments,omitempty"` -} - // extractCanvasUserMessage parses an A2A JSON-RPC request body and extracts // the user-authored text and attachments from a canvas-initiated message/send. // Returns nil when the body is not a canvas user message (empty, malformed, diff --git a/workspace-server/internal/handlers/admin_test_token_test.go b/workspace-server/internal/handlers/admin_test_token_test.go index 62d3f2b6e..3097aea79 100644 --- a/workspace-server/internal/handlers/admin_test_token_test.go +++ b/workspace-server/internal/handlers/admin_test_token_test.go @@ -39,6 +39,7 @@ func TestAdminTestToken_EnabledViaFlagEvenInProd(t *testing.T) { mock := setupTestDB(t) t.Setenv("MOLECULE_ENV", "production") t.Setenv("MOLECULE_ENABLE_TEST_TOKENS", "1") + t.Setenv("ADMIN_TOKEN", "") mock.ExpectQuery("SELECT id FROM workspaces WHERE id ="). WithArgs("ws-1"). @@ -58,6 +59,7 @@ func TestAdminTestToken_EnabledViaFlagEvenInProd(t *testing.T) { func TestAdminTestToken_WorkspaceNotFound(t *testing.T) { mock := setupTestDB(t) t.Setenv("MOLECULE_ENV", "development") + t.Setenv("ADMIN_TOKEN", "") mock.ExpectQuery("SELECT id FROM workspaces WHERE id ="). WithArgs("missing"). @@ -75,6 +77,7 @@ func TestAdminTestToken_WorkspaceNotFound(t *testing.T) { func TestAdminTestToken_HappyPath_TokenValidates(t *testing.T) { mock := setupTestDB(t) t.Setenv("MOLECULE_ENV", "development") + t.Setenv("ADMIN_TOKEN", "") mock.ExpectQuery("SELECT id FROM workspaces WHERE id ="). WithArgs("ws-1"). diff --git a/workspace-server/internal/handlers/mcp_test.go b/workspace-server/internal/handlers/mcp_test.go index 3a274fbf2..b458f2f15 100644 --- a/workspace-server/internal/handlers/mcp_test.go +++ b/workspace-server/internal/handlers/mcp_test.go @@ -1040,6 +1040,7 @@ func TestIsSafeURL_Blocks169_254_Metadata(t *testing.T) { } func TestIsSafeURL_Blocks10xPrivate(t *testing.T) { + t.Setenv("MOLECULE_ORG_ID", "") err := isSafeURL("http://10.0.0.1/agent") if err == nil { t.Errorf("isSafeURL: expected 10.x.x.x to be blocked, got nil") @@ -1047,6 +1048,7 @@ func TestIsSafeURL_Blocks10xPrivate(t *testing.T) { } func TestIsSafeURL_Blocks172Private(t *testing.T) { + t.Setenv("MOLECULE_ORG_ID", "") err := isSafeURL("http://172.16.0.1/agent") if err == nil { t.Errorf("isSafeURL: expected 172.16.0.0/12 to be blocked, got nil") @@ -1054,6 +1056,7 @@ func TestIsSafeURL_Blocks172Private(t *testing.T) { } func TestIsSafeURL_Blocks192_168Private(t *testing.T) { + t.Setenv("MOLECULE_ORG_ID", "") err := isSafeURL("http://192.168.1.100/agent") if err == nil { t.Errorf("isSafeURL: expected 192.168.x.x to be blocked, got nil") @@ -1077,6 +1080,7 @@ func TestIsSafeURL_BlocksInvalidURL(t *testing.T) { // ==================== SSRF Defence — isPrivateOrMetadataIP ==================== func TestIsPrivateOrMetadataIP_10Range(t *testing.T) { + t.Setenv("MOLECULE_ORG_ID", "") tests := []string{"10.0.0.0", "10.255.255.255", "10.1.2.3"} for _, ip := range tests { if !isPrivateOrMetadataIP(net.ParseIP(ip)) { @@ -1086,6 +1090,7 @@ func TestIsPrivateOrMetadataIP_10Range(t *testing.T) { } func TestIsPrivateOrMetadataIP_172Range(t *testing.T) { + t.Setenv("MOLECULE_ORG_ID", "") tests := []string{"172.16.0.0", "172.31.255.255", "172.20.1.1"} for _, ip := range tests { if !isPrivateOrMetadataIP(net.ParseIP(ip)) { @@ -1095,6 +1100,7 @@ func TestIsPrivateOrMetadataIP_172Range(t *testing.T) { } func TestIsPrivateOrMetadataIP_192_168Range(t *testing.T) { + t.Setenv("MOLECULE_ORG_ID", "") tests := []string{"192.168.0.0", "192.168.255.255", "192.168.1.1"} for _, ip := range tests { if !isPrivateOrMetadataIP(net.ParseIP(ip)) { diff --git a/workspace-server/internal/handlers/registry_test.go b/workspace-server/internal/handlers/registry_test.go index 7ad1dbbc6..adaafacf5 100644 --- a/workspace-server/internal/handlers/registry_test.go +++ b/workspace-server/internal/handlers/registry_test.go @@ -712,6 +712,10 @@ func TestHeartbeat_SkipsRemovedRows(t *testing.T) { // ------------------------------------------------------------ func TestValidateAgentURL(t *testing.T) { + // Ensure strict mode — MOLECULE_ORG_ID in the runner env would flip + // saasMode() on and allow RFC-1918, breaking the blocked-range checks. + t.Setenv("MOLECULE_ORG_ID", "") + t.Setenv("MOLECULE_DEPLOY_MODE", "") cases := []struct { name string url string diff --git a/workspace-server/internal/handlers/security_regression_685_686_687_688_test.go b/workspace-server/internal/handlers/security_regression_685_686_687_688_test.go index aa35a5172..a072ecee5 100644 --- a/workspace-server/internal/handlers/security_regression_685_686_687_688_test.go +++ b/workspace-server/internal/handlers/security_regression_685_686_687_688_test.go @@ -95,6 +95,7 @@ func TestSecurity_GetTemplates_NoAuth_Returns401(t *testing.T) { func TestSecurity_GetTemplates_FreshInstall_FailsOpen(t *testing.T) { setupTestDB(t) setupTestRedis(t) + t.Setenv("ADMIN_TOKEN", "") authDB, authMock := newFreshInstallAuthDB(t) tmpDir := t.TempDir() @@ -152,6 +153,7 @@ func TestSecurity_GetOrgTemplates_NoAuth_Returns401(t *testing.T) { func TestSecurity_GetOrgTemplates_FreshInstall_FailsOpen(t *testing.T) { setupTestDB(t) setupTestRedis(t) + t.Setenv("ADMIN_TOKEN", "") authDB, authMock := newFreshInstallAuthDB(t) tmpDir := t.TempDir() diff --git a/workspace-server/internal/handlers/workspace_provision_test.go b/workspace-server/internal/handlers/workspace_provision_test.go index 9e783814c..f4caa3793 100644 --- a/workspace-server/internal/handlers/workspace_provision_test.go +++ b/workspace-server/internal/handlers/workspace_provision_test.go @@ -787,6 +787,7 @@ func TestBuildProvisionerConfig_WorkspacePathFromEnv(t *testing.T) { // into cfg.ConfigFiles[".auth_token"]. func TestIssueAndInjectToken_HappyPath(t *testing.T) { mock := setupTestDB(t) + t.Setenv("MOLECULE_ORG_ID", "") broadcaster := newTestBroadcaster() handler := NewWorkspaceHandler(broadcaster, nil, "http://localhost:8080", t.TempDir()) @@ -824,6 +825,7 @@ func TestIssueAndInjectToken_HappyPath(t *testing.T) { // issuing a fresh one so we never accumulate stale live tokens in the DB. func TestIssueAndInjectToken_RotatesExistingToken(t *testing.T) { mock := setupTestDB(t) + t.Setenv("MOLECULE_ORG_ID", "") broadcaster := newTestBroadcaster() handler := NewWorkspaceHandler(broadcaster, nil, "http://localhost:8080", t.TempDir()) @@ -890,6 +892,7 @@ func TestIssueAndInjectToken_RevokeFailSkipsInjection(t *testing.T) { // IssueToken also skips injection without panicking. func TestIssueAndInjectToken_IssueFailSkipsInjection(t *testing.T) { mock := setupTestDB(t) + t.Setenv("MOLECULE_ORG_ID", "") broadcaster := newTestBroadcaster() handler := NewWorkspaceHandler(broadcaster, nil, "http://localhost:8080", t.TempDir()) @@ -916,6 +919,7 @@ func TestIssueAndInjectToken_IssueFailSkipsInjection(t *testing.T) { // ConfigFiles map is allocated before the token is written. func TestIssueAndInjectToken_NilConfigFilesAllocated(t *testing.T) { mock := setupTestDB(t) + t.Setenv("MOLECULE_ORG_ID", "") broadcaster := newTestBroadcaster() handler := NewWorkspaceHandler(broadcaster, nil, "http://localhost:8080", t.TempDir()) diff --git a/workspace-server/internal/middleware/wsauth_middleware_test.go b/workspace-server/internal/middleware/wsauth_middleware_test.go index b42209b3a..ca531676d 100644 --- a/workspace-server/internal/middleware/wsauth_middleware_test.go +++ b/workspace-server/internal/middleware/wsauth_middleware_test.go @@ -256,6 +256,10 @@ func TestWorkspaceAuth_WrongWorkspace_Returns401(t *testing.T) { // live tokens anywhere) the middleware must let the request through so existing // deployments keep working during the Phase-30 rollout. func TestAdminAuth_FailOpen_NoTokensGlobally(t *testing.T) { + // Clear any ADMIN_TOKEN from the runner env so the no-tokens tier-1 + // fail-open branch actually fires. + t.Setenv("ADMIN_TOKEN", "") + mockDB, mock, err := sqlmock.New() if err != nil { t.Fatalf("sqlmock.New: %v", err) @@ -375,6 +379,9 @@ func TestAdminAuth_C11_DeleteNoBearer_Returns401(t *testing.T) { // TestAdminAuth_ValidBearer_Passes — a valid bearer token (from any workspace) // must be accepted for admin routes. func TestAdminAuth_ValidBearer_Passes(t *testing.T) { + // Force tier-3 fallback (ValidateAnyToken) by unsetting ADMIN_TOKEN. + t.Setenv("ADMIN_TOKEN", "") + mockDB, mock, err := sqlmock.New() if err != nil { t.Fatalf("sqlmock.New: %v", err) @@ -418,6 +425,9 @@ func TestAdminAuth_ValidBearer_Passes(t *testing.T) { // TestAdminAuth_InvalidBearer_Returns401 — wrong token must not grant admin access. func TestAdminAuth_InvalidBearer_Returns401(t *testing.T) { + // Unset ADMIN_TOKEN so the tier-3 ValidateAnyToken fallback is exercised. + t.Setenv("ADMIN_TOKEN", "") + mockDB, mock, err := sqlmock.New() if err != nil { t.Fatalf("sqlmock.New: %v", err) @@ -700,6 +710,9 @@ func TestAdminAuth_Issue180_ApprovalsListing_NoBearer_Returns401(t *testing.T) { // fail-open contract: on a fresh install (no tokens anywhere), the middleware // must not block the canvas from polling /approvals/pending. func TestAdminAuth_Issue180_ApprovalsListing_FailOpen_NoTokens(t *testing.T) { + // Clear ADMIN_TOKEN so the no-tokens fail-open path works. + t.Setenv("ADMIN_TOKEN", "") + mockDB, mock, err := sqlmock.New() if err != nil { t.Fatalf("sqlmock.New: %v", err) @@ -1098,6 +1111,9 @@ func TestCanvasOrBearer_TokensExist_CanvasOrigin_Passes(t *testing.T) { // issuing workspace has status='removed' must not grant admin access. // The JOIN in ValidateAnyToken filters the row out, resulting in ErrNoRows. func TestAdminAuth_RemovedWorkspaceToken_Returns401(t *testing.T) { + // Unset ADMIN_TOKEN so the tier-3 ValidateAnyToken path is exercised. + t.Setenv("ADMIN_TOKEN", "") + mockDB, mock, err := sqlmock.New() if err != nil { t.Fatalf("sqlmock.New: %v", err) @@ -1251,6 +1267,9 @@ func TestAdminAuth_623_ForgedCORSOrigin_Returns401(t *testing.T) { // TestAdminAuth_623_ValidBearer_WithOrigin_Passes — bearer + matching Origin // should still work (the Origin is irrelevant once the bearer validates). func TestAdminAuth_623_ValidBearer_WithOrigin_Passes(t *testing.T) { + // Unset ADMIN_TOKEN so tier-3 ValidateAnyToken fallback is exercised. + t.Setenv("ADMIN_TOKEN", "") + mockDB, mock, err := sqlmock.New() if err != nil { t.Fatalf("sqlmock: %v", err) diff --git a/workspace-server/internal/router/admin_test_token_route_test.go b/workspace-server/internal/router/admin_test_token_route_test.go index 8f59250bd..3536bd1e0 100644 --- a/workspace-server/internal/router/admin_test_token_route_test.go +++ b/workspace-server/internal/router/admin_test_token_route_test.go @@ -81,6 +81,8 @@ func TestTestTokenRoute_RequiresAdminAuth_WhenTokensExist(t *testing.T) { // bootstrap path still works before the first workspace has registered. func TestTestTokenRoute_FailOpenOnFreshInstall(t *testing.T) { t.Setenv("MOLECULE_ENV", "development") + // Clear ADMIN_TOKEN so AdminAuth's no-tokens tier-1 fail-open fires. + t.Setenv("ADMIN_TOKEN", "") mock := setupRouterTestDB(t) // HasAnyLiveTokenGlobal: no tokens yet — fresh install.