Last active
August 23, 2025 22:24
-
-
Save nbonamy/55b56d19a37b6153206fd75e91a0f8f1 to your computer and use it in GitHub Desktop.
Script to dump all login items from Bitwarden browser extension
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// In case you have lost your master password but can log in the extension using TouchID or similar | |
// This was tested in Edge so should be working in Chrome | |
// Open the extension window and detach it | |
// Filter to show only logins as this script was meant for that only :) | |
// Then press shortcut to open developer tools | |
// Copy/Paste script in console and be patient! | |
// NOTE: the script will fail if you have two items that have the same title and username (cleanup before running it!) | |
// | |
// config | |
const startItem = null | |
const startIndex = 0 | |
const maxItems = -1 | |
// constants | |
const scrollBase = 40 | |
const scrollInc = 59 | |
// result | |
let allItems = [] | |
// disable emitter warning | |
const oldWarn = console.warn | |
console.warn = function(arguments) { | |
if (!arguments.join(' ').includes('NG0953')) { | |
oldWarn.apply(console, arguments) | |
} | |
} | |
// get the item count | |
const itemCount = parseInt(document.querySelector('app-vault-list-items-container[id=allItems] > bit-section span[slot=end]').textContent.trim()) | |
console.log(`➡️ Total items: ${itemCount}`) | |
// intialize for 1st item | |
let prev = startItem | |
document.querySelector('[data-testid=popup-layout-scroll-region]').scrollTo(0, scrollBase + startIndex * scrollInc) | |
await new Promise(resolve => setTimeout(resolve, 1000)); | |
try { | |
while (true) { | |
// find all the items | |
let items = document.querySelectorAll('app-vault-list-items-container[id=allItems] bit-item > bit-item-action button') | |
if (items.length === 0) break | |
// find previous | |
if (prev == null) { | |
index = 0 | |
} else { | |
index = Array.from(items).findIndex( | |
item => item.textContent.trim() === prev | |
); | |
if (index !== -1) index++; | |
else { | |
console.warn('Item not found:', prev); | |
break; | |
} | |
} | |
// get item and click it | |
let item = items[index] | |
if (!item) continue | |
prev = item.textContent.trim() | |
item.click() | |
await new Promise(resolve => setTimeout(resolve, 500)); | |
// get data | |
const title = document.querySelector('h2[data-testid=item-name]') ? document.querySelector('h2[data-testid=item-name]').textContent.trim() : '' | |
const userName = document.querySelector('input[id=userName]') ? document.querySelector('input[id=userName]').value.trim() : '' | |
const password = document.querySelector('input[id=password]') ? document.querySelector('input[id=password]').value.trim() : '' | |
const websites = [] | |
document.querySelectorAll('input[data-testid=login-website]').forEach(input => { | |
websites.push(input.value.trim()) | |
}); | |
// totp is on Edit page | |
let totp = '' | |
if (document.querySelector('input[id=totp]')) { | |
document.querySelector('popup-footer button[buttontype=primary]').click() | |
await new Promise(resolve => setTimeout(resolve, 500)); | |
if (document.querySelector('input[formcontrolname=totp]')) { | |
totp = document.querySelector('input[formcontrolname=totp]').value | |
} | |
document.querySelector('popup-footer button[buttontype=secondary]').click() | |
} else { | |
document.querySelector('button[title=Back]').click() | |
} | |
// skip items without websites (list not filtered so bringing back notes and other stuff) | |
if (websites.length) { | |
allItems.push({ | |
type: 1, | |
name: title, | |
login: { | |
uris: websites.map(website => ({ match: null, uri: website })), | |
username: userName, | |
password: password, | |
...(totp.length ? { totp } : {}) | |
} | |
}) | |
console.log(`✅ ${title} (${userName})`); | |
} | |
// stop if needed | |
if (maxItems > 0 && allItems.length >= maxItems) { | |
break | |
} | |
// now wait for back animation | |
await new Promise(resolve => setTimeout(resolve, 750)); | |
// and scroll to current item | |
const scrollTo = scrollBase + scrollInc * (startIndex + allItems.length - 1 ) | |
document.querySelector('[data-testid=popup-layout-scroll-region]').scrollTo(0, scrollTo) | |
await new Promise(resolve => setTimeout(resolve, 500)); | |
} | |
} catch (e) { | |
console.error(e); | |
} | |
// restore | |
console.warn = oldWarn | |
// done! | |
console.log({ | |
items: allItems | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment