feat(desktop): check browser extension files against Web Store hashes before loading

Merged
KI
6d ago
+1581-38

Stacked on #9328. Checks each user extension's files before loading it at startup, as Chrome's content verification does.

Why

Installs and updates verify the CRX signature, but the unpacked folder used to be loaded as-is on every start. A file changed afterwards would run with the extension's full access, for example inside a password manager.

Changes

  • Signed hashes: at install and update, the CRX's Web Store-signed verified_contents.json and its original manifest.json are saved under userData/browser-extension-manifests/. They have to live outside the extension folder because Electron deletes _metadata on every loadExtension (ElectronExtensionLoader::LoadExtension).
  • At start: the signature is checked, then every file's tree hash. The manifest must be the package's key rewrite of the signed original.
  • Existing installs (no record): they load, and their files are recorded on first use and checked from then on. The next update brings signed hashes. They are never turned off for missing data. Only a positive mismatch (changed, added, missing or symlinked file) turns an extension off.
  • When turned off: the extension isn't loaded and its folder moves to browser-extensions-turned-off. The menu shows it greyed out, "Turned off because its files changed", with reinstall (keeps its data) and remove.
  • Cost: about 30 ms for 1Password.

Testing

  • Unit tests use the real signed JSON Formatter CRX through the install path, with _metadata deleted as Electron does:
    • an untouched extension loads; a tampered file, changed manifest or added file is turned off
    • a pre-existing install loads and then catches a later tamper
    • an update refreshes the record
  • Dev app: an earlier version of this branch turned off every existing extension, because _metadata was gone. That is fixed: all 8 real extensions load across several restarts and none are turned off.

Summary by cubic

Checks each browser extension's files against Web Store-signed hashes before loading it at startup. Installs and updates already verify the CRX signature, but the unpacked folder was loaded as-is, so a file changed afterwards would run with the extension's full access.

  • At install and update, the CRX's signed verified_contents.json and original manifest.json are saved under userData/browser-extension-manifests/, because Electron deletes _metadata from the extension folder on every load; a CRX without signed contents drops the stale record for that version.
  • At startup each file's tree hash is verified against the Web Store signature; the on-disk manifest must match the package's key rewrite of the signed original.
  • Existing installs with no record load and record their files on first use; only a positive mismatch (changed, added, missing, or symlinked file) turns an extension off, moving it to browser-extensions-turned-off.
  • Turned-off extensions stay in the menu greyed out, with a fresh verified reinstall that keeps local data and a remove action. The check runs before the store API is registered, so the install path cannot load an unchecked folder; removing a turned-off extension also deletes its saved rules.

Testing

  • Unit tests use the real signed JSON Formatter CRX through the install path, with _metadata deleted as Electron does; they cover tampered files, changed manifests, added files, symlinks, pre-existing installs, and updates replacing a trusted record with the signed one.
  • Startup survives an unreadable or malformed turned-off folder, a failed move keeps the earlier turned-off copy, and symlinked version folders are never loaded.

Summary by CodeRabbit

  • New Features
    • Browser extensions with changed, added, or missing files are turned off and identified in the extensions menu.
    • Reinstall turned-off extensions directly from the menu; their local data is retained.
  • Improvements
    • Extension files are checked when loading, and extensions that fail integrity checks are prevented from running.
    • Reinstall and status messages are available in supported languages.