element-desktop icon indicating copy to clipboard operation
element-desktop copied to clipboard

Security: Windows re-install auto-logs into previous session after uninstall (credentials not purged from OS keychain)

Open SI3BENHUND3RTZ3HN opened this issue 3 months ago • 16 comments

Steps to reproduce

  1. Windows 11 x64, fresh user profile (no other Matrix apps running).
  2. Install Element Desktop 1.11.112 (official x64 installer).
  3. Start Element, log in with @user:server (E2EE on; Secure Backup/Recovery Key set).
  4. Close Element.
  5. Uninstall Element Desktop via “Apps & Features” (no special options). Do NOT touch Windows Credential Manager.
  6. (Optional) Delete %APPDATA%\Element — not required to reproduce.
  7. Re-download and install Element Desktop 1.11.112 again.
  8. Launch Element Desktop.

Outcome

What did you expect?

  • After uninstall, all local secrets (tokens/keys) are purged.
  • A fresh install should require full authentication (password + device verification or recovery key).
  • No automatic access to the previous user’s E2EE chats.

What happened instead?

  • Element Desktop opens already logged in to the previous session immediately after re-install.
  • Full room list and encrypted message history are accessible without any authentication.
  • No warning during uninstall that OS keychain/DPAPI credentials will remain on the machine.

Environment

  • Element Desktop: 1.11.112 (Windows x64)
  • Crypto stack: Rust SDK 0.13.0 (f64839e), Vodozemac 0.9.0
  • OS: Windows 11 x64 (also reproducible on Windows 10)
  • Homeserver: self-hosted Synapse
  • E2EE: enabled (Secure Backup + Recovery Key)

Security impact

  • On shared Windows accounts (internet cafés, labs, VDI), the next user can read/send as the prior user after a simple re-install.
  • Severity: High (local).

Notes

  • Element Desktop appears to keep credentials/keys in Windows Credential Manager (DPAPI) via keytar.
  • Uninstaller does not purge those entries; clearing %APPDATA% alone is insufficient.

Proposed fix

  • Uninstaller should prompt “Remove all local secrets (including OS keychain)?” — default ON.
  • Provide an in-app “Wipe local secrets (incl. OS keychain + IndexedDB)

Operating system

Windows 11 Pro 24H2

Application version

Element Desktop: 1.11.112

How did you install the app?

https://packages.element.io/desktop/install/win32/x64/Element%20Setup.exe

Homeserver

No response

Will you send logs?

No

SI3BENHUND3RTZ3HN avatar Sep 18 '25 08:09 SI3BENHUND3RTZ3HN

Element Desktop appears to keep credentials/keys in Windows Credential Manager (DPAPI) via keytar.

We don't use keytar anymore, we use https://www.electronjs.org/docs/latest/api/safe-storage keytar data may have been left present to enable downgrading Element/Electron.

I'm not sure that during the Squirrel.Windows uninstall process we're able to invoke safeStorage to clean up the credentials store due to the APIs not being ready until Electron is ready and the squirrel hooks have to run before that point.

t3chguy avatar Sep 18 '25 09:09 t3chguy

Thanks for the quick reply!

Acknowledged re: safeStorage (not keytar) — I’ve updated the wording on my side. The core problem remains: secrets persisted in Electron safeStorage survive uninstall, so a fresh install auto-restores the previous session on the same Windows profile.

If Squirrel hooks run before Electron is ready (so safeStorage isn’t accessible), could we address this in one of these ways?

  1. First-run detection: If Element detects existing safeStorage entries but no app data (fresh install), prompt: “Wipe old credentials from this Windows profile?” (default YES).

  2. In-app control: Prominent “Wipe local secrets (incl. Windows safeStorage)” button in Settings.

  3. Installer/Uninstaller flag: Provide a CLI switch (e.g. --wipe-secrets) or a small post-uninstall helper to clear safeStorage entries when the user chooses to remove all data.

  4. Shared-machine mode: Optional setting to avoid storing secrets in safeStorage entirely (require passcode/verification on each launch).

This behavior is dangerous on shared/public Windows profiles (internet cafés, labs), as it enables trivial session resurrection after uninstall. Thanks for considering.

SI3BENHUND3RTZ3HN avatar Sep 18 '25 09:09 SI3BENHUND3RTZ3HN

Actually, if you look at the shape of the safeStorage API, there is no method to delete anything at all.

t3chguy avatar Sep 18 '25 09:09 t3chguy

I already purged the Windows Credential Manager entries on this machine to secure it, so I can’t attach screenshots from before the cleanup. Prior to deletion there were multiple element.io/... items under Credential Manager → Windows / Generic Credentials.

Behaviour observed:

  • Before deleting those entries: a fresh install of Element Desktop 1.11.112 auto-logged into the previous session on this Windows profile.
  • After deleting those Credential Manager entries: a fresh install shows the login screen (no auto-login).

This strongly indicates that legacy keytar/DPAPI credentials surviving uninstall are what enable session resurrection on re-install, regardless of safeStorage (which, as noted, doesn’t itself persist).

If you need it, I can re-produce on a clean profile to capture fresh screenshots, but the above demonstrates the dependency: presence of Credential Manager items ⇒ auto-login; absence ⇒ no auto-login.

Env:

  • Element Desktop: 1.11.112 (Win x64)
  • Crypto: Rust SDK 0.13.0 (f64839e), Vodozemac 0.9.0
  • OS: Windows 11 x64

SI3BENHUND3RTZ3HN avatar Sep 18 '25 09:09 SI3BENHUND3RTZ3HN

I believe you, but as you can see on https://www.electronjs.org/docs/latest/api/safe-storage there is no delete API, so we cannot clean up the Credential Manager.

t3chguy avatar Sep 18 '25 09:09 t3chguy

Understood re: safeStorage lacking a delete API — but the auto-login I’m seeing is caused by legacy keytar/DPAPI entries in the Windows Credential Manager, not by safeStorage.

That means you don’t need a safeStorage.delete() to mitigate. There are multiple viable fixes that do not require Electron to be “ready”:

A) Don’t consume legacy creds when no app data exists

  • On first-run after a fresh install (no %APPDATA%/Element), ignore legacy keytar creds by default (require explicit user opt-in to migrate). This alone prevents auto-login while still keeping downgrade/migration possible.

B) Invalidate on first run

  • If legacy creds are detected without app data, prompt: “Found legacy Windows credentials from a previous install. Use them (migrate) or wipe/ignore?” Default = Ignore/Wipe ⇒ no auto-login.

C) Post-uninstall/first-run native helper (no Electron needed)

  • Ship a tiny Windows helper (run by Squirrel post-uninstall or pre-first-run) that calls the Win32 Credential Manager API (CredEnumerate* / CredDelete*) to remove entries matching the legacy service prefix (e.g. element.io/*). This does not rely on Electron safeStorage at all.

D) Installation-bound envelope key

  • Derive an extra local “install salt” (stored in app data). When absent (fresh install), previously stored creds become undecryptable/ignored → prevents auto-login without touching Credential Manager.

Any of A/B/D avoids the “no delete API” constraint and still fixes the security story on shared Windows profiles. C provides an actual cleanup path for users who do want a full wipe.

Happy to test a canary that ignores legacy creds when no app data is present and confirm that it eliminates auto-login on reinstall.

SI3BENHUND3RTZ3HN avatar Sep 18 '25 09:09 SI3BENHUND3RTZ3HN

Or if it only impacts the legacy keytar handling we could just get rid of that outright, given its only there transitionally and not needed permanently, after all that project is no longer maintained.

t3chguy avatar Sep 18 '25 09:09 t3chguy

Appreciate the engagement — but I want to stress how spooky this feels from a user’s perspective.

Multiple users flagged this to me independently, and I reproduced it myself: after uninstalling Element Desktop on a shared Windows profile, a fresh install auto-logs into the prior session with full E2EE history visible — no password, no verification. I honestly couldn’t believe what I was seeing the first time.

I fully accept that safeStorage has no delete API. The persistence here stems from legacy keytar/DPAPI credentials that remain on the Windows profile and are consumed on re-install. Deleting those Credential Manager items stops the auto-login; leaving them in place restores it — 100% reproducible on my end.

Given that, +1 to removing legacy keytar handling outright. To make this safe and smooth for users, here’s a pragmatic plan:

  • One-release grace period (opt-in migration)
    On first run, if legacy keytar creds are detected:

    • Prompt: “Legacy Windows credentials from a previous install found. Migrate or ignore/wipe?”
    • Default = Ignore/Wipe (prevents auto-login).
      If user chooses Migrate, import once into current storage and immediately delete the legacy items.
  • Next release: drop legacy entirely
    Stop consuming keytar creds at all. Result: fresh install + no app data ⇒ no auto-login even if Windows still has old creds.

  • Hygiene (recommended):

    • Uninstaller prompt: “Remove all local secrets (incl. legacy Windows credentials)?” — default YES.
    • First-run guard: if no %APPDATA%/Element, treat legacy creds as non-authoritative (ignore by default).
    • In-app button: “Wipe local secrets” (clears legacy creds + local stores).

Severity: High (local) on shared/public Windows profiles — this enables trivial session resurrection after uninstall without user awareness.

Happy to validate a canary that:

  1. ignores legacy creds when no app data is present,
  2. offers one-time opt-in migration,
  3. and subsequently drops legacy consumption entirely.

Thanks for considering — this change would meaningfully improve safety on shared machines.

SI3BENHUND3RTZ3HN avatar Sep 18 '25 09:09 SI3BENHUND3RTZ3HN

New finding after further testing:

The root cause of my “auto-login after reinstall” was that I did not log out before uninstalling. If the user explicitly logs out first, Element clears the local secrets (Electron safeStorage), and a fresh install correctly shows the login screen (no auto-login).
If the user does not log out and just uninstalls, those secrets remain and a fresh install auto-restores the session on the same Windows profile.

From a user-safety perspective this is not obvious at all. I only discovered it by accident.

Request (UX / safety):

  • On uninstall, show a clear prompt:

    “You are still signed in. To prevent automatic sign-in for the next user of this Windows profile, please Log out first or choose Remove all local secrets now.” Default = Remove all local secrets.

  • On first run after reinstall (no app data present), if a previous session is detected on this Windows profile, prompt:

    “Found local credentials from a prior install. Use (migrate) or Ignore/Wipe?” Default = Ignore/Wipe (no auto-login).

  • In-app add a visible “Wipe local secrets” action that clears all local credential stores (legacy keytar/DPAPI if present + current storage), so users don’t have to know OS internals.

Severity (unchanged): High (local) on shared/public Windows profiles — uninstalling an app is a reasonable expectation for “remove my access”. Without an explicit warning, users will assume they are safe, but a reinstall on the same profile resurrects the session.

Happy to validate a build that implements an uninstall warning and a first-run guard.

SI3BENHUND3RTZ3HN avatar Sep 18 '25 09:09 SI3BENHUND3RTZ3HN

Prompt: “Legacy Windows credentials from a previous install found. Migrate or ignore/wipe?”

They always got migrated already, they were just kept in case you rolled back.

Uninstaller prompt: “Remove all local secrets (incl. legacy Windows credentials)?” — default YES.

Which we wouldn't be able to do if it only affects keytar and we no longer ship the keytar binary


Worth noting we don't really consider this a supported usecase, shared accounts on machines are unsafe by design, there could be keyloggers and anything else which undermines the security of all applications on board. Additionally without dehydrated devices being supported in the spec yet, we don't support frequent-logout usecases as a whole.

t3chguy avatar Sep 18 '25 09:09 t3chguy

Thanks for the follow-up — a few clarifications and concrete proposals that don’t rely on keytar or a delete API.

“They always got migrated already, they were just kept in case you rolled back.” “We no longer ship the keytar binary.” “safeStorage has no delete API.”

All fair — and I’m not asking for a delete on safeStorage or keytar.

The user-safety problem is simpler: a fresh install on the same Windows profile can resurrect a signed-in session if the user didn’t explicitly log out before uninstall. From a user’s perspective, uninstall is a reasonable “remove my access” action — especially when selling or handing over a PC. Right now that expectation is violated silently.

Even if you don’t support shared Windows accounts (agreed, they’re risky), this still hits everyday scenarios (device transfer, resale, family PCs). We can fix it without deleting OS secrets or depending on keytar being present:

Pragmatic fixes (no delete API, no keytar dependency)

1) First-run guard (pure app logic)

  • Condition: No %APPDATA%/Element present (fresh install) and previous-session artifacts detected (whatever marker you already use to migrate).
  • Behavior: Do not auto-sign in by default. Instead show:

    “We found credentials from a previous install on this Windows profile. Use (migrate) or Ignore?” Default = Ignore (no auto-login).

  • This requires no deletion of anything. It just changes the default from “auto-resurrect” to “ask first”.

2) Install-bound salt / marker

  • Derive a local “install instance” marker (stored in app data). If it’s missing (fresh install), treat any found credentials as non-authoritative until the user opts in. Again, no delete needed.

3) Uninstall / rebootstrapping warning (UX only)

  • If the app is currently signed in and the user triggers uninstall (or closes the app before uninstall), prompt:

    “You are still signed in. To prevent automatic sign-in on this Windows profile after reinstall, please Log out or choose Do not automatically restore on next install.”

  • If you can’t hook Squirrel pre-uninstall reliably, show the same warning in-app as a “Sign out before uninstall” banner when the app detects it’s running on Windows.

4) Documentation

  • Explicitly document that uninstall alone does not guarantee sign-out on Windows profiles; users should log out first. This sets correct expectations immediately.

Why this matters (beyond “unsupported shared profiles”)

  • Users routinely resell or hand down PCs without reimaging Windows. They expect uninstall to remove access to private content.
  • The current behavior makes an already signed-in session silently persist across reinstalls on the same Windows profile — that’s unintuitive and, to many users, spooky.

I’m happy to validate a canary build with a first-run guard (no auto-login when %APPDATA% is absent unless the user opts in). That alone would eliminate the “reinstall resurrects session” surprise — without deleting any credentials or shipping keytar.

SI3BENHUND3RTZ3HN avatar Sep 18 '25 09:09 SI3BENHUND3RTZ3HN

On uninstall, show a clear prompt:

Don't think we can do this with how squirrelhooks work in Squirrel.Windows, we'd likely need to migrate to NSIS which would likely require every Element user to have to manually uninstall & reinstall to switch or at least require a large amount of work for us & ops to make work.

On first run after reinstall (no app data present), if a previous session is detected on this Windows profile, prompt:

This doesn't solve the security issue, as an attacker could just install an older version of the Element app to get around this, from before the "fix".

In-app add a visible “Wipe local secrets” action that clears all local credential stores (legacy keytar/DPAPI if present + current storage), so users don’t have to know OS internals.

Like the logout button..?

t3chguy avatar Sep 18 '25 09:09 t3chguy

Thanks for the details — replying point by point:

Uninstall auto-logout via Squirrel hooks isn’t feasible

Understood. If uninstall hooks can’t reliably clear secrets, we can still close the risk without uninstall logic by changing first-run behavior and storage semantics:

A) First-run default = no auto-restore On fresh install (no %APPDATA%/Element), treat any discovered credentials as non-authoritative unless the user explicitly opts in. Show: “Previous session artifacts were found on this Windows profile. Restore or Ignore?” Default = Ignore.
This requires no deletion and immediately prevents surprise auto-login.

An attacker could install an older version to bypass a first-run guard

Mitigations that make downgrade impractical:

B) Install-bound salt / credential envelope v2 Wrap the session secret with an install-instance salt stored in app data. If app data is missing (fresh install), the v2 envelope won’t open → no auto-login. Older builds (without the envelope) won’t find v2 secrets to auto-restore.

C) Ignore legacy auto-restore by default Keep legacy support only behind an explicit opt-in migration dialog on first run (“Restore previous session from this Windows profile?”). Default = No. After one release as a grace period, stop consuming legacy altogether.

D) Channel-based allowlist Only allow auto-restore if the credential format version ≥ fixed version and the app channel is trusted (e.g. official stable). Older binaries won’t satisfy the check.

“Wipe local secrets” in-app is just the logout button..?

Not quite. Logout requires the user to think of it before uninstall. A dedicated “Wipe local secrets” action should:

  • be discoverable in Settings (and ideally surfaced on the sign-in screen),
  • remove legacy (if any) and current storage artifacts,
  • be positioned specifically for device hand-off / resale scenarios.

It complements Logout and gives users a post-factum remedy when they forgot to log out prior to uninstall — which is a very common real-world scenario.

Severity / user expectation Even if shared Windows profiles are “unsupported”, users reasonably expect “uninstall ⇒ remove my access”. Today, a fresh install on the same profile resurrects a signed-in session unless the user had logged out beforehand. That’s unexpected and risky in hand-off/resale/family-PC situations.

Actionable minimal fix that avoids Squirrel + delete APIs

  1. First-run guard: default no auto-restore when %APPDATA% is absent.
  2. Opt-in migration (one-time) for legacy creds; then drop legacy consumption.
  3. Add a visible “Wipe local secrets (this Windows profile)” action.

Happy to validate a canary with (1)+(2). That alone removes the “reinstall resurrects session” surprise, without touching uninstall hooks or safeStorage internals.

SI3BENHUND3RTZ3HN avatar Sep 18 '25 09:09 SI3BENHUND3RTZ3HN

Contributions welcome, but given this only affects systems which are by design potentially compromised by keyloggers and similar it is unlikely to be scheduled by the product/management teams

t3chguy avatar Sep 18 '25 09:09 t3chguy

Thanks for the quick response :)

Totally understand:

  • keytar is legacy and no longer shipped,
  • safeStorage has no delete API,
  • Squirrel hooks limit what can be done at uninstall,
  • shared Windows profiles are risky overall.

Still, the current behavior is that a fresh install on the same Windows profile can resurrect a signed-in session unless the user explicitly logged out beforehand. That’s surprising and risky in very common hand-off scenarios (resale, family PCs, device transfer) – even if “shared accounts” are not supported.

This can be mitigated without uninstall hooks, without deleting anything, and without relying on keytar:

Minimal, practical fix (no delete APIs required)

  1. First-run guard (default = NO auto-restore):
    If %APPDATA%/Element is absent (fresh install) but prior session artifacts are detected on the Windows profile, do not auto-sign-in.
    Prompt: “Previous session artifacts found on this Windows profile. Restore or Ignore?”
    Default = Ignore.
    → Solves the “reinstall resurrects session” surprise immediately.

  2. Opt-in legacy migration (one-release grace):
    If legacy material is present, allow a one-time opt-in migration; otherwise ignore by default. In the next release, stop consuming legacy entirely.

  3. In-app ‘Wipe local secrets (this Windows profile)’ action:
    Different from “Logout” (which requires foresight). Provides a post-factum remedy for users who forgot to log out before uninstall / hand-off.

  4. Docs / UX hint:
    Make explicit that uninstall alone does not remove access on the same Windows profile; advise users to Log out or use Wipe local secrets before hand-off.

Re: “Older installer could bypass a first-run guard”

A first-run guard that ignores legacy artifacts by default already blocks surprise auto-restore for current & future builds.
To reduce downgrade value further:

  • Gate auto-restore on a credential format version (≥ fixed version).
  • Or wrap with an install-bound salt/envelope so that a fresh install without app data can’t open previous secrets by default.

Severity: High (local) for hand-off/resale/family-PC scenarios. Users reasonably expect “uninstall ⇒ my access is gone” unless they opt back in.

Happy to validate a canary that implements (1) First-run guard (no auto-restore by default) and (3) Wipe action.

SI3BENHUND3RTZ3HN avatar Sep 18 '25 09:09 SI3BENHUND3RTZ3HN

Thanks for the clarification ...

I understand:

  • legacy keytar creds were always migrated and only kept for rollback,
  • keytar isn’t shipped anymore (so uninstall can’t delete them),
  • Squirrel hooks limit uninstall-time behavior,
  • shared Windows profiles are risky overall; frequent-logout isn’t a primary target without dehydrated devices.

Still, the current behavior is that a fresh install on the same Windows profile can resurrect a signed-in session if the user didn’t log out before uninstall. That’s surprising in very common hand-off / resale / family-PC scenarios, even if “shared profiles” aren’t supported.

We can mitigate without uninstall hooks, without deleting anything, and without keytar:

Minimal, practical fix (no delete APIs, no keytar)

  1. First-run guard (default = NO auto-restore)
    If %APPDATA%/Element is absent (fresh install) and prior session artifacts are detected on the Windows profile, do not auto-sign-in.
    Prompt: “Previous session artifacts found on this Windows profile. Restore or Ignore?”
    Default = Ignore.
    → Removes the “reinstall resurrects session” surprise immediately.

  2. One-release, opt-in legacy migration → then stop consuming legacy
    If legacy material is present, allow a one-time opt-in migration; otherwise ignore by default. In the next release, drop legacy consumption entirely.

  3. In-app ‘Wipe local secrets (this Windows profile)’ action
    Different from Logout (which requires foresight). This gives users a post-factum remedy when they forgot to log out before uninstall/hand-off.

  4. Docs/UX hint
    Explicitly state that uninstall alone doesn’t remove access on the same Windows profile; advise users to Log out or use Wipe local secrets before hand-off.

Re: “Older installer could bypass a first-run guard”

A guard that ignores by default already blocks surprise auto-restore for current/future builds.
If you want extra hardness against downgrades:

  • Gate auto-restore on a credential format version (≥ fixed version), or
  • Wrap with an install-bound salt/envelope so a fresh install without app data can’t open prior secrets by default.

I’m happy to validate a canary with (1) first-run guard and (3) wipe action.
Severity remains High (local) for hand-off/resale cases where users reasonably expect “uninstall ⇒ access gone unless I opt back in”.

„I’ll keep the issue open until a first-run guard and/or a wipe action lands.“

SI3BENHUND3RTZ3HN avatar Sep 18 '25 10:09 SI3BENHUND3RTZ3HN