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 }); }
|
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 });
|
const picked = await pickChannelEntry({ id, currentVer, updateURL, pubkeysHex, verifyPublisher, publisher, log, timeoutMs });
|
||||||
if (picked.status !== "ok") return picked;
|
if (picked.status !== "ok") return picked;
|
||||||
const best = picked.best;
|
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 });
|
const pkg = await fetchVerifiedPackage({ id, version: best.version, url: best.url, sha256: best.sha256, log });
|
||||||
if (!pkg.ok) return pkg;
|
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 { fs.mkdirSync(stagedDir, { recursive: true }); } catch {}
|
||||||
try { placeDir(pkg.tmpDir, stageOut); }
|
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 }; }
|
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,
|
currentVer: manifest.version,
|
||||||
updateURL: manifest.updateURL,
|
updateURL: manifest.updateURL,
|
||||||
publisher: manifest.publisher || null,
|
publisher: manifest.publisher || null,
|
||||||
|
currentManifest: manifest,
|
||||||
stagedDir, pubkeysHex, verifyPublisher, log, timeoutMs,
|
stagedDir, pubkeysHex, verifyPublisher, log, timeoutMs,
|
||||||
})
|
})
|
||||||
.then((r) => report.push({ id: manifest.id, currentVer: manifest.version, updateURL: manifest.updateURL, ...r }))
|
.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 };
|
return { report };
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = { communityUpdateProblem,
|
||||||
cmpVer,
|
cmpVer,
|
||||||
promoteStagedUpdates,
|
promoteStagedUpdates,
|
||||||
checkAndStageUpdates,
|
checkAndStageUpdates,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue