{"id":"CVE-2026-73293","aliases":["GHSA-cxvf-gvfq-36w2","GO-2026-6371"],"url":"https://o3.security/vulnerability/CVE-2026-73293","summary":"Semaphore UI: Manager-to-owner privilege escalation via custom-role slug collision","details":"## Summary\n\nSemaphore resolves a project member's effective permissions in `ProjectMiddleware` by looking up a role row whose slug matches the member's assigned role, and overwrites the built-in permission bitmask with that row's value. A member holding the built-in `manager` role creates a custom project role through `POST /api/project/{id}/roles`, a route gated only by the `CanManageProjectResources` permission that `manager` already holds.\n\nThe role-creation validator does not reserve the built-in slug names (`owner`, `manager`, `task_runner`, `guest`) and sets no ceiling on the permission bits a caller may grant. A manager creates a role with slug `manager` carrying the full bitmask `15`, including the two owner-only bits `CanUpdateProject` and `CanManageProjectUsers` that `manager` does not hold. The slug lookup that backs permission resolution ignores project scope, so this attacker-created row is returned on the next request and the manager's effective permissions become owner-equivalent.\n\nResult: a project manager escalates to full owner of the project with one authenticated request, then adds or removes members, changes project settings, deletes the project, or demotes the legitimate owner.\n\n## Affected\n\nsemaphoreui/semaphore v2.18.12 (current latest as of 2026-06-09) and `develop` HEAD. Confirmed live-exploitable on v2.18.12 (commit 8a4dcf0); the affected files are unchanged on `develop` with no fix commit since the tag. Earlier releases that ship PRO custom project roles are likely affected but were not tested.\n\nRequires the PRO build (custom project roles). The official `semaphoreui/semaphore` Docker image ships this feature active by default, with no subscription or configuration flag.\n\nPure community builds without custom project roles are not affected: the role-creation endpoint returns 404 there.\n\n## Root cause\n\n`ProjectMiddleware` reads the member's built-in role permissions, then calls `GetProjectOrGlobalRoleBySlug` and, when a role row shares the member's role slug, replaces the effective permission bitmask with the database value (`api/projects/project.go:49-53`). `GetProjectOrGlobalRoleBySlug(projectID, slug)` accepts a project id but runs `select * from role where slug=?`, ignoring project scope (`db/sql/role.go:59-62`). `ValidateRole` checks only that the role name is non-empty, and enforces neither a reserved-slug list nor a permission ceiling (`db/Role.go:10-15`). The role-creation route `POST /api/project/{id}/roles` is gated by `CanManageProjectResources` (`api/router.go:294,354`), a bit the built-in `manager` role holds, while the actions it unlocks are gated by the owner-only `CanUpdateProject` and `CanManageProjectUsers` (`api/router.go:364,380`). A manager creates a row with slug `manager` and permissions `15`, and the next request resolves the manager's effective permissions to the owner bitmask.\n\n## Reproduction\n\nsemaphoreui/semaphore v2.18.12 Docker (official image, PRO build), default config, default roles.\n\n1. An admin or owner adds the attacker to a project with the built-in `manager` role; the attacker confirms the baseline, where owner-only endpoints are denied.\n\n```\nGET  /api/project/42/role     Cookie: semaphore=<manager-session>\n  -> {\"role\":\"manager\",\"permissions\":5}\nPUT  /api/project/42          Cookie: semaphore=<manager-session>\n  -> HTTP/1.1 403 Forbidden\n```\n\n2. The manager creates a custom role whose slug collides with the built-in `manager` and carries every permission bit.\n\n```\nPOST /api/project/42/roles    Cookie: semaphore=<manager-session>\nContent-Type: application/json\n{\"slug\":\"manager\",\"name\":\"pwn\",\"permissions\":15}\n  -> HTTP/1.1 201 Created\n     {\"slug\":\"manager\",\"name\":\"pwn\",\"permissions\":15,\"project_id\":42}\n```\n\n3. On the next request the manager holds the owner bitmask and performs owner-only actions.\n\n```\nGET  /api/project/42/role  -> {\"role\":\"manager\",\"permissions\":15}\nPUT  /api/project/42       {\"id\":42,\"name\":\"owned\",\"alert\":false}      -> 204 No Content\nPOST /api/project/42/users {\"user_id\":99,\"role\":\"task_runner\"}         -> 204 No Content\n```\n\nLive-verified: a manager with permissions `5` obtains permissions `15` with one POST, then changes project settings and adds project members, both of which return 403 before the role is created.\n\n## Impact\n\n- Full administrative control of the project from a non-owner role: change project settings, delete the project.\n- Add, remove, and re-role project members, including granting roles the attacker should not control.\n- Removal or demotion of the legitimate project owner.\n- Reached by the project `manager` role in default config with one request; not reachable by `task_runner`, `guest`, or unauthenticated callers.\n\n## Credit\n\nJan Kahmen, [turingpoint](https://www.turingpoint.de) (jan@turingpoint.de)","published":"2026-08-12T15:45:22.145Z","modified":"2026-09-25T18:26:52.028682913Z","cvss":{"score":8.8,"severity":"HIGH","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"},"epss":{"score":0.00498,"percentile":0.41569,"asOf":"2026-09-17"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Go","name":"github.com/semaphoreui/semaphore","fixedVersion":"0.0.0-20260705182501-bb2a4e1f08c8"}],"fix":{"url":"https://github.com/semaphoreui/semaphore/commit/1c4bb65df114962134f8829d4a03667106a01a68","label":"semaphoreui/semaphore@1c4bb65"},"references":[{"type":"WEB","url":"https://github.com/semaphoreui/semaphore/releases/tag/v2.18.19"},{"type":"WEB","url":"https://github.com/semaphoreui/semaphore/releases/tag/v2.19.5-beta5"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/73xxx/CVE-2026-73293.json"},{"type":"ADVISORY","url":"https://github.com/semaphoreui/semaphore/security/advisories/GHSA-cxvf-gvfq-36w2"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-73293"},{"type":"FIX","url":"https://github.com/semaphoreui/semaphore/commit/1c4bb65df114962134f8829d4a03667106a01a68"},{"type":"FIX","url":"https://github.com/semaphoreui/semaphore/commit/bb2a4e1f08c8023e618f8dd6eaca73554f2c33bb"},{"type":"PACKAGE","url":"https://github.com/semaphoreui/semaphore"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-09-25T18:26:52.028682913Z"}}