Code reading on 28 September 2026 at 124849b (development). Unverified: not checked live. Found in the development → beta security review. This is new in the delta: the route does not exist on beta.
What happens
POST /api/v1/migrations/{id}/emergency-contacts/{contactId} (MigrationController::reEnvelopeEmergencyContact, about line 514) is #[NoAdminRequired] only and has no vault-key proof. EmergencyEnvelopeInvalidationService::reEnvelopeForRotation() checks four things: the migration is the caller's, the contact is on the old suite, the migration is in_progress, and the envelope is well-formed JSON sealed to the grantee's active suite id. It cannot tell a real envelope from garbage.
While any migration is open, which is possible for a long time because migrations can be resumed, someone with only the session can overwrite every emergency contact's envelope with garbage. The contact stays granted and the audit log reads normally, but break-glass can no longer work. Once one contact has been re-enveloped, abort refuses as well, because countCommitted counts contacts.
This is a sibling of DELETE /emergency-access/contacts/{id}. The delta proof-gated that route precisely so that a session alone cannot destroy an envelope.
Proposed fix
Add #[VaultKeyProofRequired] to reEnvelopeEmergencyContact. Make the migration's old suite the proof subject, and bind the proof to the contact id and the envelope. Add the route to VaultKeyProofAttributesTest.
Code reading on 28 September 2026 at 124849b (development). Unverified: not checked live. Found in the development → beta security review. This is new in the delta: the route does not exist on beta.
What happens
POST /api/v1/migrations/{id}/emergency-contacts/{contactId}(MigrationController::reEnvelopeEmergencyContact, about line 514) is#[NoAdminRequired]only and has no vault-key proof.EmergencyEnvelopeInvalidationService::reEnvelopeForRotation()checks four things: the migration is the caller's, the contact is on the old suite, the migration isin_progress, and the envelope is well-formed JSON sealed to the grantee's active suite id. It cannot tell a real envelope from garbage.While any migration is open, which is possible for a long time because migrations can be resumed, someone with only the session can overwrite every emergency contact's envelope with garbage. The contact stays
grantedand the audit log reads normally, but break-glass can no longer work. Once one contact has been re-enveloped,abortrefuses as well, becausecountCommittedcounts contacts.This is a sibling of
DELETE /emergency-access/contacts/{id}. The delta proof-gated that route precisely so that a session alone cannot destroy an envelope.Proposed fix
Add
#[VaultKeyProofRequired]toreEnvelopeEmergencyContact. Make the migration's old suite the proof subject, and bind the proof to the contact id and the envelope. Add the route toVaultKeyProofAttributesTest.