feat(extension): pin the Chrome id so a store install replaces a sideload - #223
Merged
Conversation
…load Without a "key", Chrome derives an unpacked extension's id from its install path, while the store's .crx gets an id derived from the store's signing key. The two never match, so installing from the store landed a *second* copy beside an existing sideload — each with its own toolbar icon, and the old one still reporting its old version from getManifest(). That is exactly what "still shows 0.9.0 after installing 0.9.2" was. The key here is the store's own signing public key for this listing, so an unpacked build and the store .crx both resolve to jmojjohoigoecfkjlobdmiejcdjjbhnb and Chrome treats them as the same extension — an install replaces, upgrades or downgrades the other instead of duplicating it. Firefox already pinned its id via browser_specific_settings.gecko.id; this is the Chrome counterpart. Verified from the built dist: an unpacked install resolves to the store id, and the manifest key is byte-identical to the public key in the .crx the store serves (Chrome rejects a CRX whose key disagrees with its signature). This does not merge copies already installed — those carry the old path-derived id, so remove the stale entry once. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
vu1nz Security Review0 finding(s) in PR #? No security issues found. |
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.
Why
Follow-up to the "still shows v0.9.0 after installing 0.9.2" report. The popup renders
chrome.runtime.getManifest().version, which reports whichever copy is running — so that popup genuinely was 0.9.0. Two copies were installed at once.Cause: with no
keyin the manifest, Chrome derives an unpacked extension's id from its install path, while the store's.crxgets an id derived from the store's signing key. They never match, so a store install lands beside an existing sideload rather than replacing it — two entries, two toolbar icons, and the stale one keeps answering.Firefox never had this problem: it already pins its id via
browser_specific_settings.gecko.id. This is the missing Chrome counterpart.What
Adds
keytomanifest.chrome.json, set to the store's own signing public key for this listing. Both an unpacked build and the store.crxnow resolve tojmojjohoigoecfkjlobdmiejcdjjbhnb, so Chrome treats them as one extension and an install replaces the other.Version bumped 0.9.2 → 0.9.3 across
package.jsonand both manifests, since the change only reaches anyone through a newextension-v*release.Verification
dist/manifest.jsoncarries the key; an unpacked install of it derives exactly the store id..crxtronbrowser.dev serves today — Chrome rejects a CRX whosekeydisagrees with its signature, so this had to match and does.Caveat
This cannot merge copies already installed — an existing sideload keeps its old path-derived id, so the stale entry has to be removed by hand once. After that, store and sideload stay a single extension.
Note the id is now load-bearing:
signing.tsin tronbrowser.dev warns it can never be rotated without orphaning every install, so this key should be treated as permanent.🤖 Generated with Claude Code