Skip to content

fix(extension): don't offer a Web Store install for an extension that's already here - #86

Merged
ralyodio merged 1 commit into
mainfrom
worktree-fix-webstore-install-hang
Aug 30, 2026
Merged

fix(extension): don't offer a Web Store install for an extension that's already here#86
ralyodio merged 1 commit into
mainfrom
worktree-fix-webstore-install-hang

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Opening the MarkSyncr listing and clicking Add to TronBrowser hung: the install dialog sat spinning, never installed, and would not dismiss.

Why

MarkSyncr is bundled. build-release.sh fetches its CRX and stages it into extensions/marksyncr, and the launcher loads every extension subdir with --load-extension. The CRX's manifest carries a key, so the bundled copy claims the same extension id as the store listing (hjcjjcpialiakkalcgadnfnoomdaegjg).

Clicking Add therefore asked Chromium to install a downloaded CRX over an extension it already had from a command-line (unpacked) location — which a CRX cannot replace. The prompt had nothing to complete and no failure to report, so it never resolved.

install-helper.js offered that button unconditionally; it never asked whether the extension was already installed.

What changed

The rule is general rather than a special case for the one listing that exposed it — any already-installed extension has the same problem. The service worker looks the id up with chrome.management (content scripts don't get the API) and the button shows what can actually succeed:

State Button
Installed + enabled, bundled ✓ Bundled with TronBrowser (inert)
Installed + enabled, user's own ✓ Already in TronBrowser (inert)
Installed but switched off ⏻ Enable in TronBrowser — installing again could not have fixed that either
Absent, listed in our store ⬇ Add from TronBrowser Store
Absent ⬇ Add to TronBrowser (CWS CRX)

The lookup is bounded at 3s and every failure path resolves null, so an unknown answer falls back to the plain CRX install this button already did. A stalled registry must not decide whether the button works.

Adds the management permission, which both the lookup and the enable need.

Note for maintainers

A bundled extension can never be updated or reinstalled from the Web Store — a --load-extension copy outranks a downloaded CRX. MarkSyncr updates have to ship through the bundle. Documented in the extension README.

Verification

  • install-state.js holds the decision as a pure function with 15 new tests, including the MarkSyncr case.
  • Full desktop suite: 89 passing, eslint clean.
  • Not exercised in a live browser — no Chromium on the build host — so the dialog behaviour itself is reasoned from the id collision, not observed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XbNfTZM5a6sH1A54dsf7ns

…'s already here

Opening the MarkSyncr listing on chromewebstore.google.com and clicking "Add to
TronBrowser" hung: the install dialog sat there spinning, never installed, and would
not dismiss.

MarkSyncr is bundled. build-release.sh fetches its CRX and stages it into
extensions/marksyncr, and the launcher loads every extension subdir with
--load-extension. The CRX's manifest carries a `key`, so the bundled copy claims the
same extension id as the store listing (hjcjjcpialiakkalcgadnfnoomdaegjg). Clicking Add
therefore asked Chromium to install a downloaded CRX over an extension it already had
from a command-line (unpacked) location, which a CRX cannot replace. The prompt had
nothing to complete and no failure to report, so it never resolved.

install-helper.js offered that button unconditionally — it never asked whether the
extension was already installed. It does now, and the rule is general rather than a
special case for the one listing that exposed it: any already-installed extension has
the same problem. The service worker looks the id up with chrome.management (content
scripts don't get the API) and the button shows what can actually succeed — nothing
when it is installed and enabled, Enable when it is installed and switched off, and an
install only when it is genuinely absent.

The lookup is bounded at 3s and every failure path resolves null, so an unknown answer
falls back to the plain CRX install this button already did. A stalled registry must not
be what decides whether the button works.

Adds the `management` permission, which is what the lookup and the enable both need.

The decision is pure and lives in install-state.js with 15 tests, including the
MarkSyncr case that started this. Full desktop suite: 89 passing, eslint clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XbNfTZM5a6sH1A54dsf7ns
: { action: 'none', label: '✗ Could not enable', title: 'Enabling failed. Turn it on from chrome://extensions.' });
return;
}
window.location.href = target.url;
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

55 finding(s)

HIGH/CRITICAL: 7 | MEDIUM: 24 | LOW: 24

Severity Rule Location
HIGH sh-remote-script-execution apps/desktop/launcher/tronbrowser:84
HIGH sh-remote-script-execution apps/desktop/launcher/tronbrowser:394
HIGH sh-remote-script-execution apps/web/public/install.sh:134
HIGH sh-remote-script-execution apps/web/public/install.sh:139
HIGH sh-remote-script-execution apps/web/public/install.sh:238
HIGH sh-remote-script-execution apps/web/public/install.sh:256
HIGH sh-remote-script-execution apps/web/public/install.sh:794
MEDIUM js-open-redirect apps/desktop/extensions/ai-sidebar/install-helper.js:156
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/media.js:34
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/media.js:57
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/newtab.js:237
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/newtab.js:266
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/newtab.js:336
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/options.js:305
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/sidepanel.js:77
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/sidepanel.js:165
MEDIUM js-unescaped-html-sink apps/extensions/public/store.js:77
MEDIUM js-unescaped-html-sink apps/extensions/public/store.js:227
MEDIUM js-unescaped-html-sink apps/extensions/public/store.js:569
MEDIUM js-unescaped-html-sink apps/web/public/app.js:29
MEDIUM js-unescaped-html-sink apps/web/public/dns.js:54
MEDIUM js-unescaped-html-sink apps/web/public/settings.js:96
MEDIUM js-unescaped-html-sink apps/web/public/settings.js:168
MEDIUM js-dynamic-code-execution packages/sdk/src/mcp/tools.ts:80
MEDIUM js-dynamic-code-execution packages/sdk/src/mcp/tools.ts:92
MEDIUM js-dynamic-code-execution packages/sdk/src/mcp/tools.ts:103
MEDIUM js-dynamic-code-execution packages/sdk/src/page.ts:65
MEDIUM js-dynamic-code-execution packages/sdk/src/page.ts:70
MEDIUM sql-template-interpolation services/api/src/store/db.ts:116
MEDIUM js-dynamic-code-execution services/api/src/store/scanner.ts:44
MEDIUM sh-predictable-temp-path start.sh:25
LOW insecure-temp-file apps/desktop/src/launcher.test.ts:21
LOW insecure-temp-file apps/desktop/src/tor.test.ts:40
LOW insecure-temp-file apps/desktop/src/tor.test.ts:44
LOW insecure-temp-file apps/desktop/test/launcher.test.ts:93
LOW js-dynamic-code-execution packages/agent-runtime/src/analyze/form-script.test.ts:6
LOW js-unescaped-html-sink packages/agent-runtime/src/analyze/form-script.test.ts:26
LOW js-unescaped-html-sink packages/agent-runtime/src/analyze/form-script.test.ts:49
LOW js-dynamic-code-execution packages/browser-core/src/automation/extract-script.test.ts:6
LOW js-unescaped-html-sink packages/browser-core/src/automation/extract-script.test.ts:35
LOW js-unescaped-html-sink packages/browser-core/src/automation/extract-script.test.ts:47
LOW js-unescaped-html-sink packages/browser-core/src/automation/extract-script.test.ts:70
LOW js-unescaped-html-sink packages/browser-core/src/automation/extract-script.test.ts:89
LOW js-dynamic-code-execution packages/browser-core/src/automation/snapshot-script.test.ts:11
LOW js-unescaped-html-sink packages/browser-core/src/automation/snapshot-script.test.ts:24
LOW js-unescaped-html-sink packages/browser-core/src/automation/snapshot-script.test.ts:63
LOW js-unescaped-html-sink packages/provenance/src/scan.test.ts:47
LOW js-unescaped-html-sink packages/provenance/src/scan.test.ts:170
LOW js-unescaped-html-sink packages/provenance/src/scan.test.ts:216
LOW insecure-temp-file packages/sdk/src/sdk-e2e.test.ts:85

…and 5 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 810409a into main Aug 30, 2026
8 checks passed
@ralyodio
ralyodio deleted the worktree-fix-webstore-install-hang branch August 30, 2026 06:24
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.

2 participants