Theseus: community updates cannot widen what an extension may do
A community install strips category and absorbs and asks the user, but an update of the same extension was staged and promoted verbatim, so version 2 could claim first-party placement or quietly add capabilities and page-inject origins. Publisher-signed updates now get the same manifest rewrite, and one that asks for new capabilities or new pages is not staged; the user approves it by reinstalling from theseus.x.
This commit is contained in:
parent
bc1b5fc0f3
commit
21964ce385
1 changed files with 36 additions and 2 deletions
|
|
@ -272,7 +272,24 @@ function placeDir(from, to) {
|
|||
catch { fs.cpSync(from, to, { recursive: true }); fs.rmSync(from, { recursive: true, force: true }); }
|
||||
}
|
||||
|
||||
async function stageOne({ id, currentVer, updateURL, stagedDir, pubkeysHex, verifyPublisher, publisher, log, timeoutMs }) {
|
||||
// A community (publisher-signed) update is placed under the same rules as a
|
||||
// community install: it cannot claim first-party placement (category) or
|
||||
// another add-on's key namespace (absorbs), and it cannot widen what it may
|
||||
// do — new capabilities or new page-inject origins — without the user's
|
||||
// consent, which only a reinstall from theseus.x asks for. Such an update is
|
||||
// not staged; the installed version keeps running.
|
||||
function communityUpdateProblem(oldMf, newMf) {
|
||||
const oldCaps = new Set(Array.isArray(oldMf?.capabilities) ? oldMf.capabilities : []);
|
||||
const added = (Array.isArray(newMf?.capabilities) ? newMf.capabilities : []).filter((c) => !oldCaps.has(c));
|
||||
if (added.length) return `asks for new capabilities (${added.join(", ")})`;
|
||||
const origins = (m) => JSON.stringify(((m && m["page-inject"] && m["page-inject"].origins) || []).slice().sort());
|
||||
const oldPre = oldMf && oldMf["page-inject"] && oldMf["page-inject"].preload;
|
||||
const newPre = newMf && newMf["page-inject"] && newMf["page-inject"].preload;
|
||||
if ((newPre && !oldPre) || origins(oldMf) !== origins(newMf) && newPre) return "changes which pages it is injected into";
|
||||
return null;
|
||||
}
|
||||
|
||||
async function stageOne({ id, currentVer, updateURL, stagedDir, pubkeysHex, verifyPublisher, publisher, currentManifest, log, timeoutMs }) {
|
||||
const picked = await pickChannelEntry({ id, currentVer, updateURL, pubkeysHex, verifyPublisher, publisher, log, timeoutMs });
|
||||
if (picked.status !== "ok") return picked;
|
||||
const best = picked.best;
|
||||
|
|
@ -284,6 +301,22 @@ async function stageOne({ id, currentVer, updateURL, stagedDir, pubkeysHex, veri
|
|||
|
||||
const pkg = await fetchVerifiedPackage({ id, version: best.version, url: best.url, sha256: best.sha256, log });
|
||||
if (!pkg.ok) return pkg;
|
||||
if (publisher) {
|
||||
const problem = communityUpdateProblem(currentManifest, pkg.manifest);
|
||||
if (problem) {
|
||||
log(`updates: ${id}@${best.version} not staged — it ${problem}; reinstall it from theseus.x to approve`);
|
||||
try { fs.rmSync(pkg.tmpTop, { recursive: true, force: true }); } catch {}
|
||||
try { fs.rmSync(pkg.tmpFile, { force: true }); } catch {}
|
||||
return { status: "needs-consent", newVer: best.version, detail: problem };
|
||||
}
|
||||
try {
|
||||
const mf = { ...pkg.manifest };
|
||||
delete mf.category; delete mf.absorbs;
|
||||
mf.publisher = best.publisher || publisher;
|
||||
if (!mf.updateURL) mf.updateURL = updateURL;
|
||||
fs.writeFileSync(path.join(pkg.tmpDir, "addon.json"), JSON.stringify(mf, null, 2));
|
||||
} catch (e) { log(`updates: ${id} manifest rewrite failed:`, e?.message); return { status: "extract-failed", newVer: best.version, detail: "manifest rewrite" }; }
|
||||
}
|
||||
try { fs.mkdirSync(stagedDir, { recursive: true }); } catch {}
|
||||
try { placeDir(pkg.tmpDir, stageOut); }
|
||||
catch (ee) { log(`updates: stage move ${id}@${best.version} failed:`, ee.message); return { status: "extract-failed", newVer: best.version, detail: "stage move: " + ee.message }; }
|
||||
|
|
@ -376,6 +409,7 @@ async function checkAndStageUpdates({ addonsDir, stagedDir, pubkeysHex, verifyPu
|
|||
currentVer: manifest.version,
|
||||
updateURL: manifest.updateURL,
|
||||
publisher: manifest.publisher || null,
|
||||
currentManifest: manifest,
|
||||
stagedDir, pubkeysHex, verifyPublisher, log, timeoutMs,
|
||||
})
|
||||
.then((r) => report.push({ id: manifest.id, currentVer: manifest.version, updateURL: manifest.updateURL, ...r }))
|
||||
|
|
@ -386,7 +420,7 @@ async function checkAndStageUpdates({ addonsDir, stagedDir, pubkeysHex, verifyPu
|
|||
return { report };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
module.exports = { communityUpdateProblem,
|
||||
cmpVer,
|
||||
promoteStagedUpdates,
|
||||
checkAndStageUpdates,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue