You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(store): store-managed CRX signing keys, so Install actually installs
Pressing Install downloaded a .zip. Chromium can only install a signed
.crx — a .zip is sideload-only (unzip, then "Load unpacked") — and no
listing had a .crx, so the store's "one-click install" promise fell back
to a file download for everyone.
Rather than make publishers run openssl and guard a .pem forever (which
is how listings end up zip-only in the first place), the store now holds
the key and signs on their behalf:
- POST /extensions/:id/signing-key — owner-only, create-once. Generates
RSA-2048, derives the Chromium extension id, stores the private key
AES-256-GCM-encrypted under CRX_KEY_SECRET. The private key is never
returned. No rotate/delete: the key *is* the extension id, so replacing
it would orphan every install.
- GET /extensions/:slug/download.crx packs the published .zip into a
CRX3 on the fly, so a listing becomes installable the moment it has a
key — no re-upload, no second artifact to keep in sync.
- "🔑 Generate signing key" button for owners, with the permanence
spelled out in the confirm; the extension id is shown once it exists.
Also fixes two things this exposed in the auto-update path:
- updates.xml advertised `codebase` = crx_url || bundle_url, i.e. it
handed Chromium a .zip it cannot install. It now only ever advertises
a signed .crx, and returns an empty response when there isn't one.
- appid was our internal uuid; Chromium matches updates on the extension
id derived from the signing key.
CRX3 packing is hand-rolled (three protobuf fields) rather than pulling
a dependency. Tests verify the signature with node:crypto over the real
CRX3 signed payload, and round-trip a packed .crx back through the
store's own parser and scanner. 47/47 store tests pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
${ext.isOwner&&!ext.crxId ? `<p class="hint">This listing has no signing key, so Install hands over a .zip that has to be sideloaded. Generating one lets the store serve a signed .crx that installs in one click and auto-updates. The key sets the extension id permanently and can't be rotated.</p>` : ''}
if(!confirm('Generate a signing key for this extension?\n\nThe key permanently sets the extension ID — it cannot be rotated later without every install having to be redone.'))return;
0 commit comments