CVE-2026-77439
Published:October 19, 2026
Updated:October 20, 2026
Summary Two related broken access control vulnerabilities in album member management allow any album Editor to: 1. Privilege escalation — promote themselves (or any member) to the Owner role via "PATCH /api/albums/:albumId/user/:userId" with "{"role": "owner"}" 2. Owner removal — delete any Owner-role member from an album (when ≥2 owners exist) via "DELETE /api/albums/:albumId/user/:ownerId" Combined, these allow a complete album takeover: an editor can remove other owners, then promote themselves to Owner, gaining full control over an album they were only invited to edit. --- ## Vulnerability 1 — Editor → Owner Privilege Escalation File: "server/src/services/album.service.ts:343" CWE: CWE-269 (Improper Privilege Management) "typescript async updateUser(auth: AuthDto, id: string, userId: string, dto: UpdateAlbumUserDto): Promise<void> { await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] }); await this.albumUserRepository.update({ albumId: id, userId }, { role: dto.role }); // ← no guard against dto.role === AlbumUserRole.Owner } " "Permission.AlbumShare" is satisfied by the Editor role. The DTO schema ("album.dto.ts:87") accepts "owner" as a valid role value. The "addUsers" endpoint at line 294 correctly blocks this with "if (role === AlbumUserRole.Owner) throw new BadRequestException(...)", but "updateUser" has no equivalent guard. Proof of Concept: "http PATCH /api/albums/<albumId>/user/<attackerUserId> Authorization: Bearer <editor_jwt> Content-Type: application/json {"role": "owner"} " Response: "200 OK" — attacker is now an album Owner. --- ## Vulnerability 2 — Editor Can Remove Owner-Role Members File: "server/src/services/album.service.ts:316-341" CWE: CWE-639 (Authorization Bypass Through User-Controlled Key) "typescript async removeUser(auth: AuthDto, id: string, userId: string | 'me'): Promise<void> { // Only guards against removing the LAST owner, not against Editors targeting Owners if ( exists.role === AlbumUserRole.Owner && album.albumUsers.filter(({ role }) => role === AlbumUserRole.Owner).length === 1 ) { throw new BadRequestException('Cannot remove the last album owner'); } if (auth.user.id !== userId) { await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] }); // ← Editor passes, no check on the role of the user being removed } await this.albumUserRepository.delete({ albumId: id, userId }); } " Proof of Concept (album with Owner-A, Owner-B, Attacker=Editor): "http DELETE /api/albums/<albumId>/user/<ownerB_userId> Authorization: Bearer <editor_jwt> " Response: "200 OK" — Owner-B removed from album. --- ## Combined Attack Chain 1. Album has Owner-A, Owner-B, and Attacker (Editor) 2. Attacker calls "DELETE /api/albums/:id/user/:ownerB" → removes Owner-B 3. Attacker calls "PATCH /api/albums/:id/user/:self {"role":"owner"}" → becomes Owner 4. Attacker now co-owns album with Owner-A, can delete album or remove Owner-A --- ## Impact - Any album editor can take full administrative control of any shared album - Editors can lock out the original album owners - Album deletion possible (permanent loss of album organization) - Affects any multi-user Immich instance where album sharing is used --- ## Fix For "updateUser" — add the same guard already present in "addUsers": "typescript if (dto.role === AlbumUserRole.Owner) { throw new BadRequestException('Cannot set role to owner'); } " Also restrict the DTO schema: "role: z.enum([AlbumUserRole.Editor, AlbumUserRole.Viewer])" For "removeUser" — add role check before the access check: "typescript if (auth.user.id !== userId && exists.role === AlbumUserRole.Owner) { await this.requireAccess({ auth, permission: Permission.AlbumDelete, ids: [id] }); } " ## Affected Version Commit "d4808fd" — current "main" as of 2026-05-19
Related Resources (3)
Do you need more information?
Contact UsCVSS v4
Base Score:
8.7
Attack Vector
NETWORK
Attack Complexity
LOW
Attack Requirements
NONE
Privileges Required
LOW
User Interaction
NONE
Vulnerable System Confidentiality
HIGH
Vulnerable System Integrity
HIGH
Vulnerable System Availability
HIGH
Subsequent System Confidentiality
NONE
Subsequent System Integrity
NONE
Subsequent System Availability
NONE
CVSS v3
Base Score:
8.8
Attack Vector
NETWORK
Attack Complexity
LOW
Privileges Required
LOW
User Interaction
NONE
Scope
UNCHANGED
Confidentiality
HIGH
Integrity
HIGH
Availability
HIGH