Semaphore: Privilege escalation from manager to owner via a custom-role slug collision
An account with the project role manager creates a custom role whose slug collides with the built-in manager role and carries the full permission bitmask, and thereby gains owner rights over the entire project.
Advisory ID: TP-2026-042
Product: Semaphore (Open-source web UI and API for running Ansible, Terraform, OpenTofu and Bash automation)
Vulnerability type: Privilege escalation through missing authorization (CWE-269, CWE-863)
CVE: CVE-2026-73293
CVSS 3.1: 8.8 (High) · CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Vendor advisory: GHSA-cxvf-gvfq-36w2
Affected versions: <= 2.18.12 (PRO build with custom project roles)
Status: publicly disclosed, no fix available yet
Reported: 9 June 2026
Summary
Semaphore is a web UI and API for running automation such as Ansible and Terraform and manages access through project roles. The permission resolution determines a member's effective rights by role slug and replaces the built-in role's bitmask with a database role that has the same slug. The underlying resolver ignores the project id and returns any role with a matching slug, and the validation enforces neither a blocklist of the built-in slugs nor a ceiling on the permissions. An account with the built-in manager role may create a custom role through the roles endpoint and chooses the slug manager with the full bitmask, so that its effective rights rise to those of an owner role on the next request. The result is the complete takeover of the project by an ordinary manager account with a single authenticated request. turingpoint verified the chain end to end and reported it to the vendor, who confirmed it publicly.
Root cause
ProjectMiddleware determines a member's effective rights by role slug and replaces the built-in role's bitmask with a database role that has the same slug (api/projects/project.go:49-53). The underlying resolver GetProjectOrGlobalRoleBySlug(projectID, slug) ignores its projectID argument and runs select * from role where slug=? (db/sql/role.go:59-62), so any role with a colliding slug is returned. ValidateRole enforces neither a blocklist of the built-in slugs (owner/manager/task_runner/guest) nor a ceiling on the permissions (db/Role.go:10-15), so a custom role can claim the slug manager with the full bitmask. The create route POST /api/project/{id}/roles is gated only by CanManageProjectResources, which the built-in manager role holds (api/router.go:294, :354), while the unlocked actions are gated by the owner-only rights CanUpdateProject/CanManageProjectUsers (api/router.go:364, :380). The PRO custom project roles are active by default in the PRO build, so a manager's effective bitmask flips from 5 (task=1, update=2, resources=4, users=8 ⇒ manager=5) to 15 (owner) once it has created a role with the slug manager and the full bitmask.
Proof of Concept
Schematically:
# An account with the built-in manager role (bitmask 5) creates a custom role
# whose slug collides with the built-in manager role and that carries the full
# permission bitmask:
POST /api/project/<id>/roles
{ "slug": "manager", "permissions": <full bitmask> }
# On the next request the permission resolution resolves the manager to the full
# bitmask and treats it as an owner.
Because the resolver returns the role solely by slug, ignores the project id and checks neither a blocklist nor a ceiling, the manager's effective rights become owner. turingpoint confirmed this live on the official image semaphoreui/semaphore:v2.18.12 (PRO, custom roles active by default): the owner-only calls PUT /api/project/{id} and POST /api/project/{id}/users first returned 403 for the manager, then 204 each after the colliding role was created, and the real owner was demoted to guest.
Impact
- Complete administrative control over the affected project (owner-equivalent).
- Change of the project settings and deletion of the project.
- Addition, removal and re-promotion of project members.
- Demotion or removal of the legitimate owner, achievable by any manager account with a single authenticated request.
References
- Vendor advisory: GHSA-cxvf-gvfq-36w2
- CVE-2026-73293
- At the time this advisory was published the vendor provides no fixed version; the vendor advisory lists no patched release.
Is Something Like This in Your Software?
Our team found this vulnerability in the course of its work. Have your applications reviewed by the same specialists, with a penetration test from turingpoint.
