From bb11946bd4c193a81450036b7d409ef3055aa0f2 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Thu, 28 May 2026 15:34:36 -0500 Subject: [PATCH] fix: require update permission to recreate devcontainers (#25812) - The httpmw upstream from this endpoint only checks for read perms to the workspace agent. Recreating a dev container should require `update` perms since it mutates state. This also matches the behavior of the `DELETE` endpoint --- coderd/workspaceagents.go | 5 ++++ coderd/workspaceagents_test.go | 45 ++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 097d4ff41b..074ca687b1 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -1096,6 +1096,11 @@ func (api *API) workspaceAgentRecreateDevcontainer(rw http.ResponseWriter, r *ht ctx := r.Context() waws := httpmw.WorkspaceAgentAndWorkspaceParam(r) + if !api.Authorize(r, policy.ActionUpdate, waws.WorkspaceTable) { + httpapi.Forbidden(rw) + return + } + devcontainer := chi.URLParam(r, "devcontainer") if devcontainer == "" { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 0fff131f5a..9b36d11c27 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -1876,6 +1876,51 @@ func TestWorkspaceAgentRecreateDevcontainer(t *testing.T) { }) } +func TestWorkspaceAgentRecreateDevcontainerAuthorization(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + name string + role func(uuid.UUID) rbac.RoleIdentifier + }{ + { + name: "TemplateAdmin", + role: func(uuid.UUID) rbac.RoleIdentifier { + return rbac.RoleTemplateAdmin() + }, + }, + { + name: "OrgTemplateAdmin", + role: rbac.ScopedRoleOrgTemplateAdmin, + }, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + var ( + ctx = testutil.Context(t, testutil.WaitMedium) + client, db = coderdtest.NewWithDatabase(t, nil) + admin = coderdtest.CreateFirstUser(t, client) + _, workspaceOwner = coderdtest.CreateAnotherUser(t, client, admin.OrganizationID) + templateAdminClient, _ = coderdtest.CreateAnotherUser(t, client, admin.OrganizationID, tc.role(admin.OrganizationID)) + workspace = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ + OrganizationID: admin.OrganizationID, + OwnerID: workspaceOwner.ID, + }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { + return agents + }).Do() + ) + + _, err := templateAdminClient.WorkspaceAgentRecreateDevcontainer(ctx, workspace.Agents[0].ID, uuid.NewString()) + require.Error(t, err) + + var sdkErr *codersdk.Error + require.ErrorAs(t, err, &sdkErr) + require.Equal(t, http.StatusForbidden, sdkErr.StatusCode()) + }) + } +} + func TestWorkspaceAgentDeleteDevcontainer(t *testing.T) { t.Parallel()