Unverified Sender in chrome.runtime.onMessage |
The message listener does not inspect sender.id or sender.tab.url, so any code running in a context that can call chrome.runtime.sendMessage (e.g., any permitted content script) could invoke privileged functions such as buildRequest or approveTransaction. |
At the top of the listener, check if (sender.id !== chrome.runtime.id) return false;. For "onLoginPage", additionally check sender.tab.url matches exactly the Duo login page domain. |
Private Keys in chrome.storage.sync |
Private RSA keys (privateRaw) are stored in cleartext in chrome.storage.sync. This means they are synchronized (in plaintext) to Google’s servers, and if the user’s Google account is compromised, so are the keys. |
Switch to chrome.storage.local for all private key material. Optionally encrypt the private key under a user‑provided passphrase before storing. |
Potential XSS via innerHTML on Untrusted API Data |
The traverse() function directly inserts Duo API transaction attributes into the DOM via p.innerHTML = … or successDetails.innerHTML = …. If Duo’s server ever returned malicious HTML or if an attacker spoofed the API, this could result in a cross‑site scripting attack. |
Escape all inserted strings (e.g., via a helper like escapeHtml) or use textContent and create <b> elements manually to avoid interpreting any embedded HTML. |
Malicious Import Overwriting host / privateRaw / publicRaw |
When the user imports a base64‑encoded JSON, they (or an attacker) could inject a device whose host is not Duo’s official API. The extension might then sign requests to a malicious server (which could return arbitrary data) or leak device keys. |
During import, validate that every host value matches a strict Duo API domain pattern (e.g., /^api\-\d+\.duosecurity\.com$/). Perform a test GET /push/v2/device/transactions immediately after import; if it fails, reject the import. |
Excessive storage.sync Quota Usage |
Storing large Base64‑encoded key blobs in chrome.storage.sync can exceed Chrome’s per‑item or total sync quota, resulting in failed writes or data loss. |
Store only minimal metadata (device IDs, click levels, names) in storage.sync. Keep the actual key blobs in storage.local, which has a much higher per‑item quota and does not synchronize to the cloud by default. |
| Insufficient Validation of Activation Code |
The code only checks the length of the two segments of the activation code. If a user accidentally enters a code with the correct length but an invalid hostname, atob(code[1]) might produce a host that is not Duo’s. The extension will then attempt to POST the public key to a possibly malicious host. |
Validate that host strictly matches Duo’s expected hostname pattern (e.g., ends with .duosecurity.com). Optionally perform a HEAD or simple GET /push/v2/status to confirm it is indeed Duo before proceeding. |
| Error Details Potentially Leaking Internal State |
Some catch blocks rethrow errors that embed raw exception messages (Failed to fetch <url>: <networkErr>). Those messages might be shown directly to the user in the popup. |
Wrap or sanitize error messages before displaying them. For internal logging, use console.error, but for UI feedback, display only user‑friendly text (e.g., “Network error, please try again later”). |
I like the extension! My phone broke and I need better than a 4 day temporary code. Here are some security considerations. You mentioned you are releasing a new version?
chrome.runtime.onMessagesender.idorsender.tab.url, so any code running in a context that can callchrome.runtime.sendMessage(e.g., any permitted content script) could invoke privileged functions such asbuildRequestorapproveTransaction.(sender.id !== chrome.runtime.id) return false;. For"onLoginPage", additionally checksender.tab.urlmatches exactly the Duo login page domain.chrome.storage.syncprivateRaw) are stored in cleartext inchrome.storage.sync. This means they are synchronized (in plaintext) to Google’s servers, and if the user’s Google account is compromised, so are the keys.chrome.storage.localfor all private key material. Optionally encrypt the private key under a user‑provided passphrase before storing.innerHTMLon Untrusted API Datatraverse()function directly inserts Duo API transaction attributes into the DOM viap.innerHTML = …orsuccessDetails.innerHTML = …. If Duo’s server ever returned malicious HTML or if an attacker spoofed the API, this could result in a cross‑site scripting attack.escapeHtml) or usetextContentand create<b>elements manually to avoid interpreting any embedded HTML.host/privateRaw/publicRawhostis not Duo’s official API. The extension might then sign requests to a malicious server (which could return arbitrary data) or leak device keys.hostvalue matches a strict Duo API domain pattern (e.g.,/^api\-\d+\.duosecurity\.com$/). Perform a testGET /push/v2/device/transactionsimmediately after import; if it fails, reject the import.storage.syncQuota Usagechrome.storage.synccan exceed Chrome’s per‑item or total sync quota, resulting in failed writes or data loss.storage.sync. Keep the actual key blobs instorage.local, which has a much higher per‑item quota and does not synchronize to the cloud by default.atob(code[1])might produce a host that is not Duo’s. The extension will then attempt to POST the public key to a possibly malicious host..duosecurity.com). Optionally perform aHEADor simpleGET /push/v2/statusto confirm it is indeed Duo before proceeding.catchblocks rethrow errors that embed raw exception messages (Failed to fetch <url>: <networkErr>). Those messages might be shown directly to the user in the popup.console.error, but for UI feedback, display only user‑friendly text (e.g., “Network error, please try again later”).