Skip to content

fix: improve hostname parsing logic in getSiteName function#1417

Open
Hank076 wants to merge 175 commits into
Authenticator-Extension:devfrom
Hank076:dev
Open

fix: improve hostname parsing logic in getSiteName function#1417
Hank076 wants to merge 175 commits into
Authenticator-Extension:devfrom
Hank076:dev

Conversation

@Hank076

@Hank076 Hank076 commented May 6, 2025

Copy link
Copy Markdown

Fix IP address handling priority

Hank076 and others added 30 commits May 6, 2025 21:52
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
--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.
Hank076 added 28 commits July 11, 2026 11:55
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.
qrcode-reader has been unmaintained since 2017 and was only kept as a
first-attempt decoder with jsqr as its fallback. Decode QR codes with
jsqr directly in both the screen-capture (content.ts) and file-import
(QrImport.vue) paths, and drop the now-unused dependency.
Upgrade eslint 8.57.0 -> 10.7.0 and @typescript-eslint 7.15.0 -> 8.63.0
(both EOL/outdated) and replace .eslintrc.js + .eslintignore with a
flat eslint.config.mjs. Rule behavior is kept equivalent to the v7/v8
baseline (new v10/v8 recommended rules that would change existing
behavior are explicitly disabled); ignores are rewritten as **-prefixed
globs so nested build output (test/chrome/js, test/firefox/js, etc.)
stays excluded the way the old gitignore-style .eslintignore did.

scripts/build.sh drops the now-unsupported `--ext .js,.ts` CLI flag;
file matching is handled by eslint.config.mjs's `files` globs instead.

src/definitions/module-interface.d.ts: the disable comment referenced
`@typescript-eslint/ban-types`, which typescript-eslint v8 split into
several rules; repointed to `@typescript-eslint/no-unsafe-function-type`
to match the actual rule now firing on the `Function` type usages below.
Same-major, low-risk dependency refresh: webpack ^5.94.0->^5.108.4,
sass ^1.26.11->^1.101.0, ts-loader ^9.0.0->^9.6.2, @types/chrome
^0.0.266->^0.2.2, lodash ^4.17.21->^4.18.1.

@types/chrome 0.2.2 tightens chrome.storage.*.get()'s default generic
from any to unknown and renames chrome.windows.createTypeEnum to
CreateType; fixed with minimal `as string`/typed-callback casts in
src/background.ts, src/import.ts, src/store/Accounts.ts and
src/models/storage.ts (no logic changes).

@types/chrome 0.2.2 also uses NoInfer, a TypeScript 5.4+ builtin not
present in the pinned typescript@^5.0.0 (5.0.3), which broke the
standalone `tsc scripts/test-runner.ts` compile in scripts/build.sh
(the main webpack build path is unaffected since ts-loader runs
transpileOnly with fork-ts-checker, which skips declaration/global
diagnostics by default). Added --skipLibCheck to that one tsc
invocation as a scoped workaround -- it only affects third-party
.d.ts checking for this tooling script, not project source. Remove
once typescript is bumped to >=5.4 in a future toolchain upgrade.
TS 5.9 tightens option validation: `--moduleResolution nodenext`
now requires `--module nodenext` (TS5110), so build.sh's
test-runner tsc invocation is updated accordingly. package.json
has no "type": "module", so output for .ts files under
--module nodenext remains CommonJS — node scripts/test-runner.js
(CJS require semantics) is unaffected; npm test exercises the
compiled output directly and passes.

--skipLibCheck stays: dropping it surfaces a pre-existing
@types/sinon-chrome incompatibility (Event<Function> does not
satisfy the sinon-chrome Event constraint), unrelated to this
TS bump.
Prepares for prettier 3 output formatting in a follow-up commit; kept
separate so the reformat commit stays purely mechanical.
prettier 3 turns "no parser could be inferred" into a fatal error
(prettier 2 only warned). src/test/test.ts.disabled is an upstream
dead test file with no recognizable extension that was silently
tolerated before; exclude it via the standard ignore mechanism
instead of touching build.sh/CI globs or deleting the file.
Run prettier --write over the same scope as scripts/build.sh
(STYLEFILES) after bumping to prettier 3. Output-only diff — mainly
trailingComma additions (new default: "all") and re-wrapped long
lines; no logic changes.
…scripts)

- src/test/test.ts.disabled: 2020 upstream leftover (63ea3e5), zero
  references anywhere; not picked up by webpack or test-runner.
- scripts/release.sh, scripts/tag.sh: Travis CI-era release/tag
  automation, zero references in .github/workflows or package.json;
  fully superseded by release.yml (2026-07 GitHub Actions migration).
  tag.sh was already broken (referenced a deploy-key.gpg that no
  longer exists in the repo).
- .prettierignore: its only entry (*.disabled) existed solely to
  work around test.ts.disabled failing prettier 3's parser
  inference; now empty and removed with it.
…vendored equivalents

All three were unmaintained (url-loader's repo is archived; base64-loader
saw its last release years ago; vue-svg-loader never left Vue-3 beta after
2020-09). Removes them entirely rather than pinning dead dependencies.

- .wasm (argon2-browser's binary): webpack 5 asset/inline with a custom
  base64 generator, reproducing base64-loader's exact output — a plain data
  URI here would break at runtime, since argon2-browser's decodeWasmBinary()
  calls atob() directly on the required module's value.
- .png/.jpe?g/.gif: asset/inline, matching url-loader's default (no `limit`
  set = always inline as base64, never falls back to a separate file).
- .svg: a vendored local loader (webpack-loaders/vue-svg-loader.js, MIT,
  based on @ifcanduela/vue-svg-loader) instead of an npm dependency, since
  every viable replacement candidate is itself a single-release,
  near-abandoned package — vendoring the ~15 lines keeps the supply chain
  at zero added packages. Fixed a Windows path-separator bug in the
  process (upstream only strips "/", not "\\", which corrupted the
  generated SFC on Windows).

Also drops `module.noParse: /\.wasm$/`, a base64-loader-specific setting
that silently broke asset/inline for .wasm (verified via isolated repro:
it caused webpack to emit a separate asset/resource file + URL instead of
running the inline generator).
…ck-merge majors

webpack-cli 5.0.1 -> 7.2.1, fork-ts-checker-webpack-plugin 6.5.3 -> 9.1.0,
webpack-merge 5.8.0 -> 6.0.1. All three are devDependencies used only by
scripts/build.sh and webpack.*.js.

No config migration needed: webpack.config.js / webpack.dev.js /
webpack.prod.js all load unchanged under webpack-cli 7's dual ESM/CJS
config loader, fork-ts-checker 9's default options still cover this
project's `new ForkTsCheckerWebpackPlugin()` usage, and webpack-merge 6
keeps a CommonJS `require('webpack-merge').merge` export compatible with
existing usage.

Verified: eslint clean, chrome + firefox builds pass, npm test 29/29
passing. Confirmed fork-ts-checker 9 still fails the build on a
deliberately injected type error (reverted before commit).

webpack.watch.js remains broken (missing webpack-extension-reloader
dependency, pre-existing per its own TODO comment) - unrelated to this
upgrade; webpack-cli 7 loads and fails on it identically to before.
mocha 10.2.0 -> 11.7.6 and sinon 17.0.1 -> 22.0.0 (with matching
@types/mocha and @types/sinon), keeping sinon-chai on 3.7.0 since 4.x
requires chai ^5/^6 and this project pins chai to 4.x.

mocha 11's browser bundle now requires Node core modules via the
"node:" URI scheme (node:fs, node:path, node:util, node:events), which
webpack treats as an unhandled resource scheme rather than a normal
module request, bypassing resolve.fallback entirely. Strip the prefix
via NormalModuleReplacementPlugin in webpack.dev.js so it falls back to
the existing bare-name polyfills, and add an "events" polyfill fallback
(none of the test/sinon API surface changed across the sinon majors).
Only consumer was the Travis-era release.sh (removed in de93d88); the
fork's PKCE architecture ships zero secrets and release.yml never
decrypts anything. The blob was upstream's, undecryptable here.
Dart Sass @import is deprecated (removed in 3.0.0), and every build
emitted 8 deprecation warnings from popup/options/permissions/import
entries pulling in _ui.scss -> _tokens.scss. Replaced with @use via
`sass-migrator module --migrate-deps`, no manual namespacing needed
since no Sass-level members (vars/mixins/functions) cross module
boundaries; only CSS custom properties and @extend are shared.

Verified: build emits zero sass deprecation warnings; compiled CSS is
byte-identical except one inert #info .button-small/#security-* extend
leak that never matched any real DOM node and was already overridden
by a later explicit rule; npm test still 29/29 passing.
webpack-extension-reloader was never installed and does not support
webpack 5, so `npm run dev:chrome` failed with Cannot find module.
Rely on webpack's built-in watch (already enabled) instead, and update
the webpack-merge import to the v6 named-export form.
jsqr is unmaintained (last release 1.4.0, 2021-04) and ships a wrong
version-23 alignment pattern table (74 where ISO/IEC 18004 says 78),
confirmed by cross-checking thonky, zxing sources, and the Nayuki
formula. Replace it with the actively maintained @zxing/library behind
a shared leaf module (src/qr-decoder.ts, no src/models imports) used
by both the import page and the injected content script, plus an
encode->decode round-trip test.

Known trade-off: content.js grows 311 KiB -> ~1.9 MiB (injected on
demand only, not statically). Planned slimming tracked in AGENTS.md:
move decoding into the background service worker, which already holds
the capture dataURL.
Decode captured QR regions in the background worker instead of the
injected content script: content.ts now sends the selection box plus
window.innerWidth with getCapture, and background crops the captured
bitmap via OffscreenCanvas and decodes it directly, feeding the
existing getTotp flow. This drops @zxing/library from content.js
(1.87 MiB back to ~54 KiB) so autofill injection no longer carries an
unused decoder, and saves one message round-trip (sendCaptureUrl is
gone).

The crop/DPR math lives in computeQrCropRegion (qr-decoder.ts leaf
module) with unit tests in utils.test.ts. Decoding failures now
message the tab with action "errorqr"; content.ts finally handles
that action, which also un-silences the pre-existing errorqr senders
in getTotp. The data-URL-to-Blob conversion uses atob rather than
fetch() because the extension CSP (connect-src) blocks data: fetches
inside the MV3 service worker.
The post-drag getCapture sendMessage was fire-and-forget; when the MV3
service worker fails to start (or the receiving end is otherwise gone),
the rejection was swallowed and the scan looked like nothing happened.
Alert with the existing capture_failed message so the user gets
feedback. Normal decode failures are unaffected: the background
listener resolves the promise with undefined; it only rejects when no
receiver exists.
Scanning a QR that decodes to something other than an otpauth:// URI
used to alert the raw decoded text (e.g. a URL), which reads like a
glitch. Send the new error_not_otpauth i18n message through the
existing "text" alert channel instead; the content script is
untouched. The key is added to all 44 locales (41 translated, fy/kaa
fall back to English).
docs/ holds untracked local notes and one-off debug scripts; 'eslint .' during builds failed on them with 125 no-undef errors.
Firefox manifests are bumped in the AMO compliance commit that follows.
- add mandatory browser_specific_settings.gecko.data_collection_permissions (required: none, optional: authenticationInfo), enforced for new extensions since 2025-11
- prune stale Google Drive / OneDrive host permissions and CSP connect-src entries, matching the chrome manifest cleanup in b9614f4
- replace upstream gecko.id with our own GUID {aa04367e-4dc8-4833-87a1-43ee93ce9a42}; the upstream id belongs to the original AMO listing and must not be reused
- bump firefox manifests to 8.0.4
Clears the only first-party UNSAFE_VAR_ASSIGNMENT warning in the AMO validation report; rendered output is unchanged.
Build firefox first and zip immediately: scripts/build.sh wipes the other platform's output directory on every run.
prettier 3 ships its bin as bin/prettier.cjs; the hardcoded bin-prettier.js path from the 2.x era exits 127 since the 3.x bump.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant