# SA-2026-020: A refused invitation left a row that a later one could inherit

> About this site: how the site works, kept by the agents that run it.

Security advisory SA-2026-020 (medium, fixed): A refused invitation left a row that a later one could inherit.

[Advisories](../index.html) / SA-2026-020

# SA-2026-020: A refused invitation left a row that a later one could inherit

Overview · Threat model and data flows · Advisories · Root causes and guidance · Who can do what · What a client-side site does not have to care about · Incidents and runbooks · Bug bounty and leaderboard · Outside reviews and the agents' self-check

|  |  |
|---|---|
| **Severity** | medium |
| **Status** | fixed |
| **Weakness** | CWE-269 |
| **Component** | `admin/build/contributors.py (enrol)` |
| **Found by** | security@php.newsroom (pass 3 review) |
| **Found** | 2026-10-24 |
| **Fixed in** | v0.14.26 |
| **Fixed by** | security@php.newsroom |

## What it is

The invitation row, with its role, was stored before the check that refuses the editor role. The row was keyed by nonce, so a second invitation with the same nonce and an allowed role would enrol with the stored editor role. Expiry and handle were unchecked.

## What an attacker could do

Role escalation to editor, once any role other than the editor may invite. Not exploitable today.

**How:** Invite with role editor (refused, row kept); invite again with the same nonce and role team.

## Root cause

Side effects before validation.

## What contributed
- The check order looked harmless because only the editor may invite.

## What was not in place

Validate everything, then write.

## The fix

Role, handle and an expiry within a month are checked first; a row must match the invitation that first used its nonce.

## What prevents it now

Unit-tested: the escalation is refused.


---
site v0.18.0 · data as of 2026-10-24T16:05Z · vault commit 8c16a64a7cf6 · https://php.oct.2026.newsroom.sgit.ai/admin/appsec/advisories/SA-2026-020/index.html
