From d99343130006a6251de44dcfd99289143e944aa7 Mon Sep 17 00:00:00 2001 From: jmgasper Date: Mon, 4 May 2026 11:04:25 +1000 Subject: [PATCH] PM-4988: Restrict project edit permissions for copilots What was broken Copilot project members were treated as eligible for the EDIT_PROJECT permission, so users with copilot project access could update project details. Root cause The named EDIT_PROJECT permission explicitly allowed copilot project membership, and the legacy UPDATE_PROJECT policy metadata still listed copilot and customer project roles. What was changed Removed copilot membership from the EDIT_PROJECT permission check. Updated the legacy UPDATE_PROJECT policy and generated permission documentation summary so project detail edits are limited to management-level project roles, manager-tier platform roles, admins, and machine project-write tokens. Any added/updated tests Added PermissionService coverage that verifies a project copilot cannot edit project details through either the named permission path or the legacy UPDATE_PROJECT policy. --- src/shared/constants/permissions.constants.ts | 6 +---- .../services/permission.service.spec.ts | 26 +++++++++++++++++++ src/shared/services/permission.service.ts | 1 - src/shared/utils/permission-docs.utils.ts | 2 +- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/shared/constants/permissions.constants.ts b/src/shared/constants/permissions.constants.ts index 0dfff45..3beb9cd 100644 --- a/src/shared/constants/permissions.constants.ts +++ b/src/shared/constants/permissions.constants.ts @@ -265,11 +265,7 @@ export const PERMISSION = { 'There are additional limitations on editing some parts of the project.', }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS, - projectRoles: [ - ...PROJECT_ROLES_MANAGEMENT, - PROJECT_MEMBER_ROLE.COPILOT, - PROJECT_MEMBER_ROLE.CUSTOMER, - ], + projectRoles: PROJECT_ROLES_MANAGEMENT, scopes: SCOPES_PROJECTS_WRITE, }, diff --git a/src/shared/services/permission.service.spec.ts b/src/shared/services/permission.service.spec.ts index 747ffcb..ccd19ee 100644 --- a/src/shared/services/permission.service.spec.ts +++ b/src/shared/services/permission.service.spec.ts @@ -2,6 +2,7 @@ import { ProjectMemberRole } from '../enums/projectMemberRole.enum'; import { Scope } from '../enums/scopes.enum'; import { UserRole } from '../enums/userRole.enum'; import { Permission } from '../constants/permissions'; +import { PERMISSION } from '../constants/permissions.constants'; import { PermissionService } from './permission.service'; describe('PermissionService', () => { @@ -722,6 +723,31 @@ describe('PermissionService', () => { expect(allowed).toBe(true); }); + it('blocks project copilots from editing project details', () => { + const user = { + userId: '3001', + roles: [UserRole.TC_COPILOT], + isMachine: false, + }; + const projectMembers = [ + { + userId: '3001', + role: ProjectMemberRole.COPILOT, + }, + ]; + + expect( + service.hasNamedPermission( + Permission.EDIT_PROJECT, + user, + projectMembers, + ), + ).toBe(false); + expect( + service.matchPermissionRule(PERMISSION.UPDATE_PROJECT, user, projectMembers), + ).toBe(false); + }); + it('allows deleting project for machine token with project write scope', () => { const allowed = service.hasNamedPermission(Permission.DELETE_PROJECT, { scopes: [Scope.PROJECTS_ALL], diff --git a/src/shared/services/permission.service.ts b/src/shared/services/permission.service.ts index b2df6c1..a10308e 100644 --- a/src/shared/services/permission.service.ts +++ b/src/shared/services/permission.service.ts @@ -271,7 +271,6 @@ export class PermissionService { return ( isAdmin || isManagementMember || - this.isCopilot(member?.role) || this.hasProjectUpdateTopcoderRole(user) || hasMachineProjectWriteScope ); diff --git a/src/shared/utils/permission-docs.utils.ts b/src/shared/utils/permission-docs.utils.ts index 76202d0..b9ba79e 100644 --- a/src/shared/utils/permission-docs.utils.ts +++ b/src/shared/utils/permission-docs.utils.ts @@ -255,7 +255,7 @@ function getNamedPermissionDocumentation( case NamedPermission.EDIT_PROJECT: return createSummary({ userRoles: PROJECT_UPDATE_TOPCODER_ROLES, - projectRoles: PROJECT_MEMBER_MANAGEMENT_AND_COPILOT_ROLES, + projectRoles: PROJECT_MEMBER_MANAGEMENT_ROLES, scopes: PROJECT_WRITE_SCOPES, });