fix: improve hostname parsing logic in getSiteName function#1417
Open
Hank076 wants to merge 151 commits into
Open
fix: improve hostname parsing logic in getSiteName function#1417Hank076 wants to merge 151 commits into
Hank076 wants to merge 151 commits into
Conversation
EntryStorage.import() ran parseInt() on the type and algorithm fields,
but both are persisted as their enum *names* ("hotp", "SHA256"), so
parseInt always returned NaN and fell back to the defaults. Every
imported entry therefore became TOTP/SHA1, losing HOTP/Steam/Battle/hex
types and SHA256/SHA512 digests.
Map the stored name back to the enum (case-insensitive for algorithm),
guarding with a typeof-number check so legacy numeric data still falls
back safely.
Refs Authenticator-Extension#1292, Authenticator-Extension#405, Authenticator-Extension#1442, Authenticator-Extension#1294, Authenticator-Extension#1089
second % 60 keeps the sign of the dividend in JS, so a negative clock offset could leave state.second negative; the old "+ 60" only covered offsets down to -60. Use a positive modulo so any offset stays in 0..59. Refs Authenticator-Extension#1310
The otpauth importer rejected periods that were > 60 or not a divisor of 60, silently dropping valid values like 45, 90 or 120 and falling back to 30s, which produces wrong codes. Validate as a positive integer instead. Refs Authenticator-Extension#1271, Authenticator-Extension#1508
Toggling Smart Filter off also fired the "smart filter loosely matches the domain name" notification. Show it only when the toggle is turned on. Refs Authenticator-Extension#1282
An unset autolock falls back to a 30-minute default in setAutolock(), but the advisor treated "unset" the same as "disabled" and kept showing the warning. Only warn when autolock is explicitly set to 0. Refs Authenticator-Extension#1281
The algorithm and digits <select>s used v-model without the .number modifier, so the in-memory entry held string values (e.g. "2"). The generator compares the algorithm with === against the numeric enum, so every non-SHA1 choice fell through to SHA1 until the entry was reloaded from storage (which normalises the type). Add .number like the type select already does. Refs Authenticator-Extension#1184
The OneDrive OAuth code-for-token POST had no body at all, so Microsoft's token endpoint always rejected it and sign-in never completed. Unlike Google's endpoint (which accepts the params in the query string), Microsoft requires them in the x-www-form-urlencoded body. Add client_id, client_secret, code, redirect_uri and grant_type. Refs Authenticator-Extension#1494, Authenticator-Extension#1369, Authenticator-Extension#1408, Authenticator-Extension#1202, Authenticator-Extension#1424
The search box was only revealed by the clearFilter action, so a large account list never showed it on initial load or after unlocking with a passphrase. Reveal it after entries load when there are >= 10 of them and a smart filter isn't currently applied. Refs Authenticator-Extension#1496, Authenticator-Extension#1400
andOTP exports a flat JSON array using a "label" field instead of the keyed object with "account" that the importer expects, so its backups imported as empty/garbled. Detect the array form and map each entry to RawOTPStorage (label -> account with the redundant issuer prefix stripped, type lower-cased; EntryStorage.import normalises type/algorithm names). Refs Authenticator-Extension#1304
getQrUrl encoded the whole label, turning the issuer/account separator into %3A. Several authenticators (including Google Authenticator) split the label on a literal ":" and reject the %3A form, so the exported QR scanned as invalid. Encode issuer and account separately and strip the internal "::host" match suffix, matching the text backup and the otpauth spec. Refs Authenticator-Extension#1302
Autofill collected every text/number/tel/password input regardless of visibility, so the code could be written into a hidden honeypot or an off-screen field instead of the real 2FA box. Filter candidates by checkVisibility() (guarded for browsers without it). Refs Authenticator-Extension#1273, Authenticator-Extension#1136
moveCode received from/to positions in the displayed (pinned-first) order but spliced and re-indexed the raw entries array, so dragging scrambled the list whenever any entry was pinned. Reorder the displayed view first, then write back sequential indices. No change when nothing is pinned. Refs Authenticator-Extension#1312, Authenticator-Extension#1428
The issuer/account edit inputs let keydown events bubble to the list's arrow-key navigation handlers, so pressing left/right to move the text cursor moved focus to another entry, blurred the field and committed the edit (e.g. saving an emptied account). Stop keydown propagation on those inputs. Refs Authenticator-Extension#495
…ding
failedCount filtered Promise.allSettled results with `!res`, but those are
always-truthy {status,value} objects, so the count was always 0 and both
failure branches were dead code — a fully failed otpauth-migration import
still reported "migrationsuccess". Inspect status/value instead.
Refs code review §2.2
chrome.storage.sync.set was awaited with no error handling, so hitting the sync quota (MAX_ITEMS / QUOTA_BYTES_PER_ITEM) rejected as a silent unhandled rejection while the entry stayed in the Vuex view but was never persisted — the long-standing "can't add more than N accounts" data loss. Wrap the write with a clear error, and surface it when adding an account so a save that failed no longer shows a phantom entry. Refs code review §2.1
… codes isMatchedEntry matched the bound host with an unanchored indexOf and matched the page-controlled <title> as a substring, so a hostile page at google.com.attacker.com (or just <title>Google</title>) could be the sole match and receive the user's live OTP via the autofill command. Add a strict mode (used by autofill) that anchors the host match to a real domain boundary and drops the title hint; display filtering stays loose. Refs code review §2.3
nextCode() guarded on this.$store.state.style.hotpDisabled, but the flag lives one level deeper (state.style.style.hotpDisabled), so the guard was always undefined and every click advanced and persisted the HOTP counter with no rate limit. The disabled CSS class also read a misspelled `hotpDiabled` and never applied. Fix both. Refs code review §3.4
…ckup getOneLineOtpBackupFile reassigned otpStorage.issuer/account in place, but that object is the live Vuex export state, so generating a one-line backup corrupted the stored issuer/account (colons stripped, values %-encoded) for any subsequent JSON backup and double-encoded on a repeat download. Compute sanitized values into locals instead. Refs code review §3.3
…tion The hand-rolled protobuf walker trusted every attacker-controlled length byte and read past the buffer (subBytesArray returns undefined), and indexed the algorithm/digits/type lookup tables with raw bytes — out-of-range values produced otpauth://undefined/...&algorithm=undefined, silently importing entries that generate wrong codes. decodeURIComponent on a missing/garbled data= param could also throw and abort the whole import batch. Guard the data= extraction and decode, bounds-check each field before reading, skip entries with out-of-range algorithm/digits/type, and wrap the caller so one bad payload can't abort a mixed import. Refs code review §3.2
The confirm action added an anonymous window "confirm" listener on every dispatch and never removed it, so listeners accumulated and a single confirm event re-fired every stale one, resolving old promises unexpectedly. Use a named handler that removes itself when it fires. Refs code review §3.5
applyPassphrase switches to LoadingPage before decrypting/migrating, which can throw (e.g. "argon2 did not return a hash!"). The caller awaited without catching, so the error became an unhandled rejection and the UI was stuck on LoadingPage with no way back. Catch it, return to EnterPasswordPage and alert. Refs code review §3.6
Each argon2 call added an anonymous window "message" listener that was never removed and resolved on the first message to arrive, with no link between request and reply — overlapping hash/verify calls (e.g. the v3 multi-key unlock loop) could resolve with the wrong response, and listeners piled up. Route every call through one helper that tags the request with a unique id, only accepts the reply from the sandbox iframe matching that id, removes its listener, and times out instead of hanging forever. Collapse the six copied inline postMessage blocks (Accounts, import) onto argonHash/argonVerify. Refs code review §3.1, §3.6 (sandbox timeout)
crypto-js is imported and run at runtime (OTP secret AES encrypt/decrypt in key-utilities, encryption, migration, etc.) but was declared under devDependencies, so an SBOM or `npm install --omit=dev` would drop the actual crypto library. @types/lodash is type-only and was in dependencies. Swap them to the correct sections. Refs code review §5, §6
The base webpack config sets devtool: "source-map" for development; the prod config merged it through, so release artifacts shipped full source maps (and build.sh copies the .map files into the package). Override devtool: false for production. Refs code review §5
The fork notice ended with `read -n1` (Press any key to continue), which hangs any non-interactive/automated build on a fork remote. Keep the notice but drop the blocking read. Refs code review §5
…mport decryptBackupData JSON.parsed the AES-decrypted v2/v3 payload with no guard, so one entry that decrypts to invalid UTF-8/JSON threw and aborted the whole backup import. Wrap it and continue past the bad entry. Refs code review §6
new Date(header).getTime() returns NaN for an unparseable Date header, which made the offset NaN and fell through to "clock_too_far_off" — a misleading state. Return "updateFailure" instead. Refs code review §6
The Dropbox upload handler only special-cased 401 and otherwise JSON-parsed the body, so a 5xx or HTML error page would throw or be misread. Reject non-2xx responses with a clear error. Refs code review §6
base32tohex mapped chars outside [A-Z2-7=] to indexOf -1, producing NaN hex that CryptoJS.enc.Hex.parse accepts without error, so a corrupted secret yielded a well-formed but always-wrong OTP with no feedback. Throw after normalization so generate() lands in its existing catch and shows CodeState.Invalid.
split("::") took only the first two parts, so an issuer containing a
literal "::" (e.g. "My::Bank") with a bound host exported as
"My::Bank::example.com" round-tripped into issuer "My" and host "bank",
corrupting the entry and voiding the autofill host binding. Decode at
lastIndexOf("::") everywhere (migrateLegacyHost, isMatchedEntry, entry
display, txt export), and reject "::" in issuer inputs (add form and
inline edit) with a new errorissuer message so new data stays
unambiguous. Adds regression tests for the round-trip, matching, and
base32 validation.
dismissInsight and clearIgnoreList fired commitItems() without awaiting it, racing the storage re-read inside the following getInsights() (a dismissed insight could reappear) and turning storage write failures into unhandled rejections.
Manual item.split("=") kept only the second segment, silently
truncating secrets containing "=" padding (AAAA== imported as AAAA).
URLSearchParams preserves the full value. The type/label split stays
manual because otpauth:// is a non-special scheme whose authority
parsing differs between Chrome and Node URL implementations. Malformed
URIs missing "//" are now counted as failed instead of throwing and
aborting the whole batch.
Compiled bundles now emit to js/ instead of dist/. Updated the webpack output path/publicPath, all manifest service_worker and content-script references, view/*.html script srcs, and the runtime chrome.scripting.executeScript injection paths in background.ts and the popup components so host-bound autofill keeps resolving the built content script.
Make cloud backup shippable without a confidential OAuth secret in the package. Browser extensions are public clients; any secret bundled in the build is extractable by anyone who installs it. Dropbox: upgrade from the deprecated OAuth implicit flow to Authorization Code + PKCE. No client secret, and it now also obtains a refresh token. Google Drive: removed entirely (in-client OAuth flow, DrivePage UI, and the manifest oauth2 block). There is no way to do Google OAuth inside a browser extension without either shipping a confidential secret or running a backend: - chrome.identity.getAuthToken is blocked for new extensions by Google's 2023-10 OAuth custom-URI-scheme restriction, which surfaces as "400 invalid_request: Custom URI scheme is not supported on Chrome apps". - chrome.identity.launchWebAuthFlow (chromiumapp.org redirect) requires a Google "Web application" client, whose token endpoint forces a confidential client_secret even when PKCE is used -- so the secret would ship in the package or require a backend token broker to hide it. - The only secret-free in-client option, the implicit flow, is removed in OAuth 2.1 and strongly discouraged by Google. Rather than ship a confidential secret or stand up a backend, Google Drive cloud sync is dropped. Local encrypted backup export/import is unaffected; users can still place the exported encrypted file into Drive manually. The inert driveToken store/type plumbing is intentionally left to avoid a broad BackupState type refactor and is dead (never set).
…ions Google Drive backup was removed (05053e8) and OneDrive is disabled, so their host permissions and CSP connect-src entries were unused. Keep www.google.com (clock sync) and Dropbox.
- Feature Request now links to this repo's discussions (was upstream) - Drop the Crowdin translation link; this fork's locales are generated - Add optional "Affected area" and "Console errors" fields to the bug report to aid triage without adding required friction - Remove legacy ISSUE_TEMPLATE.md, superseded by the chooser directory
errorissuer is referenced by AddAccountPage/EntryComponent but was missing from zh_TW, so those users saw the English fallback. Also drop 19 keys with zero code references: upstream leftovers, removed Google Drive / disabled OneDrive strings, and theme options not in the menu.
Align every locale's messages.json to the 164-key set defined by zh_TW: drop the 19 unused keys (including from the en source that drives key iteration), and backfill missing keys translated into each locale's language (1482 additions across 42 locales, incl. errorissuer). Existing translations are left byte-for-byte untouched. New strings were machine-translated by per-locale agents and are not yet human-reviewed.
Per-locale review pass against the English source: fix mistranslations, translate strings that were left in English, restore the "OTPilot" proper noun where it had been genericised to "Authenticator", and normalise the broken "$ SERVICE $" placeholder in Hebrew so it resolves. Key set (164), description fields and placeholders objects are unchanged. Still machine-generated QA — not yet human-reviewed.
Fold in the opus verifier audit for de/fr/es/ja/ko/ru/zh_CN: resolve same-English-string inconsistencies (learn_more, secret family), correct "OTPAuth URI" wording in ko, normalise Russian e/yo spelling of the "account" term, drop a stray word/period, and tighten a few advisor and insight labels. pt_BR passed clean and is untouched. Key set (164), description fields and placeholders objects unchanged.
The fork no longer uses Crowdin, and the inherited i18n workflow was broken here anyway: scripts/i18n.sh decrypts upstream's deploy key with a DEPLOY_KEY_PASSWORD secret this fork does not have, so it failed on every push to dev. That autofill also rewrote locale files at 4-space indent. Remove crowdin.yml, scripts/i18n.js, scripts/i18n.sh and the upstream scripts/deploy-key.gpg. Repurpose the i18n workflow to run the new scripts/check-i18n.js, which fails CI (push + pull_request) if any locale's key set diverges from _locales/en/messages.json. Translations are now maintained by hand.
backupGetExport() unconditionally skipped EncOTPStorage (AES-GCM/v4) accounts even for a plaintext export, emitting their ciphertext into a supposedly unencrypted backup. On restore the importer has no passphrase to unlock it and drops the entry, silently losing the account. Decrypt v4 entries with the unlocked encryption instance for plaintext exports; encrypted backups still keep the ciphertext plus appended keys. Adds a round-trip test.
applyEncryption() only warned on malformed or hash-mismatched decrypted data then applied it anyway, risking wrong OTPs or wrong host binding on swapped/corrupted ciphertext; now it aborts and leaves the entry locked. Dropbox logout only removed the access token, leaving the refresh token to silently mint a new one on the next getToken(). It also used two un-awaited removeItem() calls (read-modify-write) that raced and restored one of the tokens. Both tokens are now cleared in a single commit, then revoke runs best-effort.
Replace upstream README content with fork-specific docs: OTPilot store listing link, fork feature list (Dropbox PKCE backup, Advisor, host-bound autofill), current build/test/i18n instructions; drop dead Travis/Crowdin badges, upstream store links, and Safari section.
…Chrome Chrome 137+ removed the --load-extension flag from branded Google Chrome builds, so the extension never loads under the system Chrome provided by mymindstorm/puppeteer-headful and navigation to chrome-extension://.../view/test.html fails with ERR_BLOCKED_BY_CLIENT. Let npm ci download puppeteer's Chrome for Testing (which keeps --load-extension) and run the headful tests under xvfb-run instead.
checkout v2->v4, setup-node v2.1.2->v4 (Node 22 + npm cache), codeql-action v1->v3 with explicit language and least-privilege permissions; drop the obsolete HEAD^2 checkout hack and Autobuild step that interpreted-language repos never needed.
Never triggered on this fork: they depend on the upstream release branch and secrets (CREDS_FILE_PASSWORD, DEPLOY_KEY_PASSWORD) that do not exist here, and the archived create-release/upload-release-asset actions would need a rewrite anyway if this fork ever ships releases.
On v* tags: build the chrome target (works with empty credentials, unlike prod), zip the chrome/ output, and publish it as a GitHub release with the built-in token. No external secrets required.
--generate-notes builds notes from merged PRs, but this fork commits directly to dev, so generated notes would be nearly empty. The tag annotation is author-controlled and always has content.
actions/checkout flattens the annotated tag to its commit (actions/checkout#290), so --notes-from-tag fell back to the commit message. Force-fetching the real tag ref restores the annotation.
GitHub deprecated Node 20 on runners in 2025-09; the v4 actions emitted deprecation annotations on every run.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix IP address handling priority