Skip to content

A stolen session can get your new private key through an emergency contact it plants before you recover #800

Description

@rjzondervan

Code reading on 28 September 2026 at 124849b (development). Unverified: not checked live, and the effect below is read from code only. Found in the development → beta security review. This is new in the delta: beta is not affected.

What happens

Someone holding only a user's session or app password can end up with that user's new private key after the user runs compromise recovery. No master password is needed. The attacker does need a second Nextcloud account with a keepiq suite on the same instance.

  1. POST /api/v1/emergency-access/contacts has no vault-key proof. EmergencyAccessController::create is #[NoAdminRequired] only, unlike destroy. EmergencyAccessService::designate() checks only that the envelope is non-empty. It then records grantorSuiteId as the victim's current active suite (lib/Service/EmergencyAccessService.php, designate()). So the attacker, acting as the victim, adds their own account as a contact with a garbage envelope.
  2. The attacker calls request straight away and turns off the victim's notify_security preference. That preference is a user setting written through settings#updateUserSettings, which needs no proof. After the shortest wait period (1 day, which the attacker chose), promoteIfElapsed() moves the contact to approved.
  3. The victim suspects a compromise and runs compromise recovery. initiateCompromiseRecovery calls migrateEmergencyContacts (src/store/modules/encryptionSuite.js, about lines 371–436). That function wraps the new private key to every contact whose grantorSuiteId is the old suite, without asking the user. On the server, EmergencyEnvelopeInvalidationService::reEnvelopeForRotation() keeps the approved state.
  4. EmergencyAccessService::fetchEnvelope() returns the envelope as soon as the state is approved. The attacker now holds the new private key.

On beta, rotation invalidated every envelope on the old suite, so a planted contact only ever held garbage. The automatic re-escrow added by #674 is what makes the planted contact dangerous.

Proposed fix

  • Require a vault-key proof on emergencyAccess#create.
  • During rotation, do not carry requested or approved contacts across silently (refuse them on the server as well). Show the user the contacts that will receive the new key, and ask them to confirm first.
  • Separately: stop the security notifications from being switched off with a session alone.

Live check

Using user A's app password, create a contact for B, request it, and wait until it is approved. Then, as A in the browser, run compromise recovery. Finally, as B, fetch the envelope and try to decrypt it with B's key.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions