- TypeScript 74.9%
- HTML 20.9%
- JavaScript 4.2%
Add a second fill source alongside the appSecrets vault: a context-menu item that reads the
user's existing Trezor Suite password-manager entry for the page host via the new getPassword
Connect method and fills it. The appSecrets Save/Fill/Manage flow is unchanged.
- whitelist passwordManager: ['getPassword'] in the connect-common patch
- service-worker: MENU_FILL_LEGACY_ID -> callSuite('getPassword', { domain }) -> TREZOR_FILL
- README: document the two fill sources
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|---|---|---|
| .claude/skills | ||
| docs | ||
| patches | ||
| src | ||
| .gitignore | ||
| CLAUDE.md | ||
| DESIGN.md | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| webpack.config.js | ||
Trezor Suite Dark – Passwords (browser extension)
An MV3 browser extension that saves and fills passwords secured by your Trezor. You right-click a password field on any web page and either fill it from your Trezor-backed vault or save the current login into it. The encryption and the device confirmation stay inside Trezor Suite; the extension only ever handles the plaintext for the one field being filled or saved.
It talks to Suite through @trezor/connect-webextension
over the local suite-desktop app (WebSocket ws://127.0.0.1:21335/connect-ws), using the
fork's appSecrets Connect API (getAppSecret / setAppSecret / listAppSecrets /
revokeAppSecret).
What it is (and isn't)
appSecrets is a per-origin key→value secret store. Suite scopes every secret to the
extension's own verified origin (chrome-extension://<id>, taken from the WebSocket Origin
header) — not to the web page. So this extension owns its own Trezor-backed vault:
- It does not read Suite's built-in password-manager (Dropbox) entries — it can only see secrets it saved here.
- The vault starts empty. You must save a login before you can fill it.
- Each secret is
name → value; we usename = <site host>and pack{ username, password }into thevalue. The device encrypts it (Suite Sync stores only ciphertext), and every fill requires a physical Trezor confirmation (saving does not).
Two fill sources
The right-click menu offers two ways to fill, from two different stores:
- Fill password with Trezor Suite → the extension's own appSecrets vault (what you saved via Save password to Trezor Suite). Origin-scoped to this extension.
- Fill from Trezor Suite password manager → your existing Trezor Suite password-manager
entries (the
getPasswordmethod, matched by the page host). This reads your real vault, so it works with logins you already have in Suite — no per-extension saving needed. It unlocks the password store on device, then confirms the specific entry.
Status: work in progress. The extension is complete and the Suite side (the appSecrets API, the
export_secretconsent, the device-confirm read) is implemented on thesuite-dark/suite-darkfork. It works end-to-end against a suite-desktop build that includes appSecrets, with a wallet open. Against a Suite build without it, Suite answersMethod_Unsupportedand the extension shows a note. See DESIGN.md.
How it works
FILL SAVE
Right-click a password field Right-click a password field
│ "Fill password with Trezor Suite" │ "Save password to Trezor Suite"
▼ (service worker) ▼ (service worker asks the frame to
TrezorConnect.call({ getAppSecret, name }) capture the current username+password)
│ connect-ws → suite-desktop ▼
▼ TrezorConnect.call({ setAppSecret, name, value })
Suite: export_secret consent → device │ connect-ws → suite-desktop
confirm → decrypt → { value } ▼
│ service worker decodes {username,password} Suite: export_secret consent → device-encrypt
▼ → Suite Sync persists ciphertext
content script fills the field(s) ▼
content script shows "Uloženo do Trezoru"
name is derived from the page host (lowercased, leading www. stripped). value is a small
versioned JSON envelope { v, u, p }; a non-JSON value is treated as a bare password for
forward/backward compatibility.
Build
npm install # runs postinstall → patch-package
npm run build # → build/ (production)
npm run dev # watch build (development)
Install runs
patch-package(postinstall), which appliespatches/@trezor+connect-common+*.patch. That patch is required: it whitelists the appSecrets methods in@trezor/connect-webextension's client-side API guard (the published beta predates them) and enforces desktop-only routing (no Suite Web fallback). See DESIGN.md → "Transport".
The toolbar popup can wake a closed Suite Dark via a custom-URL deeplink. The scheme
defaults to suitedark:// (what the suite-dark desktop build registers); override it for an
upstream Trezor Suite build:
SUITE_DEEPLINK=trezorsuite:// npm run build
Extension identity (important)
src/manifest.json pins a public key, which fixes the extension id — and therefore the
chrome-extension://<id> origin that all your saved secrets are scoped to. Do not change
or remove it, or Suite will scope secrets under a different origin and previously saved
passwords become unreadable. The matching private key is generated into keys/ (gitignored);
back it up if you plan to publish. To regenerate (only for a fresh identity):
openssl genrsa -out keys/extension-key.pem 2048
openssl rsa -in keys/extension-key.pem -pubout -outform DER | base64 -w0 # → paste into manifest "key"
Popup, connection status & waking Suite
Clicking the toolbar icon opens a small popup that:
- shows whether Suite Dark desktop is reachable (a live status dot; the toolbar icon also
carries a green badge while connected). Reachability is a lightweight WebSocket probe of
ws://127.0.0.1:21335/connect-ws— it opens and immediately closes a socket, so it triggers no Suite popup or device interaction; - offers "Otevřít Suite Dark" when the app is closed — this fires the
suitedark://deeplink to launch/focus the desktop app, then polls until connect-ws comes up; - links to the project (web, downloads, GitHub) and shows the how-to.
The service worker polls the connection every ~30s (chrome.alarms) and keeps the badge +
cached status warm; the popup reads that cache for an instant first paint and re-probes on open.
Website (GitHub Pages)
A self-contained landing page lives in docs/: it explains what the
extension is, how the fill flow and the live connection status work, how to install it, and
links to the Suite Dark project. It is plain HTML/CSS (no build step) and matches the popup's
dark theme.
Publish it by enabling GitHub Pages on this repo → Settings → Pages → Build and deployment
→ Deploy from a branch → main / /docs. It then serves at
https://suite-dark.github.io/suite-extension-dark/ (the URL the popup's Web link points
to). The docs/.nojekyll file disables Jekyll so the page is served verbatim.
Load it in the browser
npm run build- Chrome →
chrome://extensions→ enable Developer mode → Load unpacked → select thebuild/folder. - Open Trezor Suite Dark (desktop) with a wallet open — or click the extension icon → Otevřít Suite Dark.
- On any page, right-click a password field. Either Fill from Trezor Suite password manager to fill a login you already have in Suite, or Save password to Trezor Suite + Fill password with Trezor Suite to use the extension's own appSecrets vault.
- To review or delete what's stored in the extension's vault, open the extension icon → Uložená hesla (the manage view, opens in its own tab). Each action needs a Suite confirmation.
Project layout
| File | Role |
|---|---|
src/manifest.json |
MV3 manifest (context menus, content script, action popup, pinned key/identity). |
src/service-worker.ts |
Registers the menus, calls getAppSecret/setAppSecret, routes the result; polls connection + updates the badge. |
src/content-script.ts |
Tracks the right-clicked field; fills password (+ nearby username), and captures the current login for saving. |
src/popup.html / src/popup.ts |
Action popup: connection status, wake-via-deeplink, project links, link to manage view. |
src/options.html / src/options.ts |
Manage view (full tab): list saved secret names + revoke them (listAppSecrets/revokeAppSecret). |
src/connection.ts |
Shared connect-ws reachability probe + status types. |
webpack.config.js |
Bundles the entry points; injects __SUITE_WEB_URL__ + __SUITE_DEEPLINK__. |
patches/ |
patch-package patch of @trezor/connect-common (whitelist appSecrets methods + desktop-only). |
keys/ |
Extension signing key (private .pem gitignored; public key is pinned in the manifest). |
docs/ |
GitHub Pages landing page (self-contained index.html). |
DESIGN.md |
Architecture + the appSecrets wire contract. |
Security notes
- Plaintext credentials exist only transiently in the service worker and content script, and are never logged or persisted.
- The extension performs no encryption itself — Suite + the Trezor device do. Suite stores only ciphertext (Suite Sync); reads require a physical device confirmation.
- Secrets are isolated per origin by Suite. Note that the local connect-ws server currently
trusts the
Originheader verbatim, so any local process reaching127.0.0.1:21335could present this extension's origin — a known local-trust limitation of the desktop transport.