Browser extension
Fill and save logins from the vault in Chrome and Firefox.
apps/extension is a Manifest V3 extension for Chrome, other Chromium
browsers and Firefox 140 or newer (142 on Android). It uses the web app's
lib/crypto module, so it reads and writes the same vault.
The vault itself is created in the web app. The extension signs in and unlocks an existing one.
Build and load
pnpm build:extension # writes apps/extension/dist/chrome and dist/firefox- Chrome: open
chrome://extensions, enable Developer mode, choose Load unpacked and pickapps/extension/dist/chrome. - Firefox: open
about:debugging#/runtime/this-firefox, choose Load Temporary Add-on and pickapps/extension/dist/firefox/manifest.json.
The two builds differ only in the manifest: Chrome runs the background script as a service worker, Firefox as an event page with an add-on id.
Pointing it at a server
pnpm build:extension builds for https://app.montarapass.com. To use
another server, turn on Settings → Self-hosted and enter its Server
URL, such as https://pass.example.com/api. Only the origin is kept: the
extension adds /api/v1/… itself, and links to the web app on the same
origin.
pnpm --filter @montara-pass/extension watch builds in development mode,
where the API and the web app run on separate ports. That build points at
http://localhost:8080 and http://localhost:5173, and its settings show
both as API server and Web app. apps/extension/.env overrides the
defaults of either build (VITE_DEFAULT_API_URL and VITE_DEFAULT_APP_URL).
The API accepts extension origins without a CORS entry.
The popup
After signing in and unlocking, the popup shows:
- Logins for the current site, each with a Fill button.
- The whole vault, with search, a sort toggle and buttons to copy the username or the password.
- Add, to save a login for the current site, with a password generator (20 characters by default).
- Full screen, which opens the vault in a tab.
Full screen
The tab view has three columns: filters (all items, logins, secure notes, favorites), the list, and the selected item. The list is the wide column and shows each item's folder and when it was last used; the item beside it keeps a reading width. Passwords stay hidden until revealed. Items can be created and edited here, including their folder. A secure note is written in the same Markdown editor as in the web app, loaded when one is opened.
The owner of an item can Share it from here: the item's key is wrapped for the other user in the browser, as in the web app. Changing someone's access or taking it away is done in the web app, which re-encrypts the item for the people who are left. Folders, favorites and the trash are managed there too.
The side menu also holds a Password generator and Password health, the same two tools as in the web app. Health lists the logins with a weak password and those sharing one; Open shows the login in the vault.
When Fill is offered
A saved login is offered for a page when the page's host equals the saved
host, or is a subdomain of it. www. is ignored on both sides.
| Saved URL | Page | Offered |
|---|---|---|
example.com | login.example.com | yes |
login.example.com | example.com | no |
example.com | example.com.evil.net | no |
Fill touches the active tab only when it is clicked, and fills only visible username and password fields. The inline menu looks for login fields on every page.
The inline menu
The extension puts a badge inside the focused login field. Entering an empty login field opens a menu under it with the logins saved for that site; picking one fills the form. The menu stays away when the site has no saved login or nobody is signed in, and closes on typing. Esc closes it for the rest of the page. The badge, or Alt+↓ in the field, opens it at any time.
It is always on, which is why the extension asks for access to every site when it is installed: a content script runs on each page to find its login fields. Nothing about the page leaves the browser.
| Piece | Where it runs | What it can see |
|---|---|---|
content.js | the page, top frame only | the page's fields, including what is typed into them; nothing from the vault |
inline.html | a frame of the extension's own origin | the unlocked keys and the logins for the site |
The page around the menu can neither read the frame nor script it, and the content script never receives a password from the vault: the frame fills the form itself, the same way the popup does. The frame learns which site it is on from the browser's record of its tab, not from the page.
In a password field that is choosing a password, the menu also offers a
generated one, with a button to draw another. Picking it fills the password
and its confirmation, and keeps it for the
save prompt straight away, so it is
not lost if the form is sent in a way the extension does not notice. A field
counts when it is marked autocomplete="new-password", comes with a second
password field, or sits in a form or at an address that reads like a
sign-up. Nothing is suggested on Montara Pass itself, where the password
being chosen is the master password, or while the vault is locked.
A field gets the badge when it is a password field, a text field in a form
that has one, or a field that takes a username or an email going by its
type, autocomplete, name or id.
Clicks a page tries to borrow
Before filling, the menu checks that it is fully opaque, on top of the page and still on a site the login matches, and it ignores clicks in its first 300 ms. The menu sits in the browser's top layer; an overlay placed there after it that lets clicks through is not detected.
While the vault is locked the menu lists nothing and offers Unlock, which opens the popup.
Saving a login as it is submitted
When a form with a filled password field is sent, by a submit, a button or
Enter, the content script hands the username and password to the
background script, which keeps them in chrome.storage.session for that tab.
On the page that follows, or on the same one once the form is gone, a prompt
in the top right corner offers to save the login. It is the same kind of
frame as the inline menu, so the page cannot read or script it.
Nothing is shown when the vault already has that username, or that password, for the site. Nothing is kept when nobody is signed in, or for the Montara Pass app itself, where the password typed is the master password. While the vault is locked the prompt offers Unlock first. Not now, Esc, closing the tab or five minutes without an answer drop the login.
A form the site refuses is offered as well: the extension sees that a form was sent, not what the site answered.
Site icons and last use
A login keeps the icon of its site inside its encrypted content, as a PNG
of at most 64 pixels. The extension fetches it once, when the login is
saved: the tab's own icon, then /favicon.ico, then the icons the page
names in its <link> tags. The request goes to the site itself, with no
cookies and no referrer, and no icon service is involved, so no third party
learns which sites are in the vault. Every client then shows the icon
without asking anyone; the web app shows it too, but cannot fetch one.
A login the user owns that has no icon yet, because it is older or was made in the web app, gets one the next time the extension lists the vault. That rewrites the item once, which moves its "Last updated". A site without an icon is remembered as such and keeps the keyhole. Logins shared by someone else are left alone.
Filling a login, from the popup or the inline menu, calls
PUT /api/v1/items/:id/used. The
time is shown as "Last used …", and the inline menu lists the account used
last first. It is stored per member, so it follows the user to their other
browsers and is not shown to the people a login is shared with. A server
without the route is ignored: the login is filled all the same.
Where secrets are kept
| What | Where | Cleared |
|---|---|---|
| Access and refresh tokens | chrome.storage.local | on sign out |
| Unlocked vault keys | chrome.storage.session (in memory) | after 15 minutes without use, when the browser closes, on Lock |
| Master password | nowhere | used once to unlock |
| A login just submitted, until it is saved or declined | chrome.storage.session (in memory) | on save, on Not now, when the tab closes, after 5 minutes |
Permissions
| Permission | Why |
|---|---|
storage | Tokens, settings and the unlocked keys |
activeTab, scripting | Fill the page the user is looking at, on click |
alarms | The auto-lock timer |
clipboardWrite | Copy buttons |
| Host permission for every site | Run the inline menu's content script, and call a self-hosted server at any address |
For Firefox the manifest also declares what leaves the browser
(data_collection_permissions): authentication information and personally
identifying information. That covers the sign-in email and password sent to
the API and the encrypted vault stored there. Firefox shows it when the
extension is installed; 140 is the first release that does, which is why it
is the minimum version.