6bf8cebf3f01
fix: Firefox release scripts now submit source archive via --upload-source-code (audit PB-26-018)
- time
- 2026-08-30 14:28 UTC · 1 month ago
- machine
- koh2
- agent
- gpt-5
- session
- PB-26-018 audit remediation
- parent
- a20650466475
↓ peach-browser-6bf8cebf.tar.gzatlas at this save
files changed7 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1scripts/release-firefox.ts+8 −5scripts/release.test.ts+86 −0scripts/release.ts+6 −6vitest.config.ts+1 −1manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "5.0.27",4+ "version": "5.0.28",55 "description": "Zero-knowledge password manager",66 "browser_specific_settings": {77 "gecko": {manifest.json+1 −1
@@ -2,7 +2,7 @@22 "manifest_version": 3,33 "minimum_chrome_version": "116",44 "name": "Peach Password Manager",5- "version": "5.0.27",5+ "version": "5.0.28",66 "description": "Zero-knowledge password manager",77 "action": {88 "default_popup": "src/popup/index.html",package.json+1 −1
@@ -1,6 +1,6 @@11 {22 "name": "peach",3- "version": "5.0.27",3+ "version": "5.0.28",44 "description": "Zero-knowledge password manager",55 "license": "GPL-3.0-only",66 "type": "module",scripts/release-firefox.ts+8 −5
@@ -50,7 +50,11 @@5050 // AMO requires source for any extension with minified/transpiled code.5151 // This zips everything a reviewer needs to build Peach from source.5252 53-const sourceZip = `peach-source-${version}.zip`;53+// Keep the reviewer archive beside the signed artifact for traceability.54+if (!existsSync(join(ROOT, "artifacts"))) {55+ mkdirSync(join(ROOT, "artifacts"));56+}57+const sourceZip = join(ROOT, "artifacts", `peach-source-${version}.zip`);5458 run(5559 [5660 "zip -r",@@ -93,6 +97,8 @@9397 "--source-dir dist-firefox",9498 "--artifacts-dir artifacts",9599 "--channel=listed",100+ "--upload-source-code",101+ sourceZip,96102 ].join(" "),97103 "Signing and submitting to AMO",98104 // web-ext reads WEB_EXT_API_KEY/WEB_EXT_API_SECRET from env — pass via@@ -103,13 +109,10 @@103109 }104110 );105111 106-// ── Cleanup zip ───────────────────────────────────────────────────────────────107-108-run(`trash ${sourceZip}`, "Cleaning up source zip");109-110112 // ── Done ──────────────────────────────────────────────────────────────────────111113 112114 console.log(`\n✓ Peach Firefox v${version} submitted to AMO.`);115+console.log(` Source archive retained at ${sourceZip}.`);113116 console.log(" Existing users will receive the update automatically.");114117 console.log(" Check AMO developer hub for review status.");115118 console.log("");scripts/release.test.ts+86 −0
@@ -0,0 +1,86 @@1+import { execFileSync } from "node:child_process";2+import { existsSync, mkdtempSync, readFileSync } from "node:fs";3+import { fileURLToPath } from "node:url";4+import { dirname, join, resolve } from "node:path";5+import { tmpdir } from "node:os";6+import { describe, expect, it } from "vitest";7+8+const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");9+const RELEASE_SCRIPTS = ["scripts/release.ts", "scripts/release-firefox.ts"];10+11+function readReleaseScript(path: string): string {12+ return readFileSync(join(ROOT, path), "utf8");13+}14+15+function signCommand(script: string): string {16+ const start = script.indexOf('"npx web-ext sign"');17+ const end = script.indexOf('].join(" ")', start);18+ return script.slice(start, end);19+}20+21+describe("Firefox release source submission", () => {22+ it("passes the generated source archive to web-ext sign in both entry points", () => {23+ for (const path of RELEASE_SCRIPTS) {24+ const command = signCommand(readReleaseScript(path));25+ expect(command).toContain('"--upload-source-code",');26+ expect(command).toContain("sourceZip");27+ expect(command.match(/--upload-source-code/g)).toHaveLength(1);28+ }29+ });30+31+ it("builds an archive containing the reviewed crypto source and Noise build script", () => {32+ const archive = join(mkdtempSync(join(tmpdir(), "peach-source-test-")), "source.zip");33+ execFileSync(34+ "zip",35+ [36+ "-q",37+ "-r",38+ archive,39+ "src",40+ "scripts",41+ "public",42+ "crypto-core",43+ "manifest.json",44+ "manifest.firefox.json",45+ "package.json",46+ "bun.lock",47+ "tsconfig.json",48+ "vite.config.ts",49+ "--exclude",50+ "node_modules/*",51+ "--exclude",52+ "dist-*/*",53+ "--exclude",54+ "crypto-core/target/*",55+ "--exclude",56+ "crypto-core/generated/*",57+ "--exclude",58+ "*.zip",59+ "--exclude",60+ ".DS_Store",61+ "--exclude",62+ "__MACOSX/*",63+ ],64+ { cwd: ROOT },65+ );66+67+ const contents = execFileSync("unzip", ["-Z1", archive], { encoding: "utf8" }).split("\n");68+ expect(contents).toEqual(69+ expect.arrayContaining([70+ "crypto-core/Cargo.toml",71+ "crypto-core/Cargo.lock",72+ "crypto-core/src/lib.rs",73+ "scripts/build-noise-core.mjs",74+ ]),75+ );76+77+ for (const path of RELEASE_SCRIPTS) {78+ const script = readReleaseScript(path);79+ expect(script).toContain('"crypto-core",');80+ expect(existsSync(join(ROOT, "crypto-core/Cargo.toml"))).toBe(true);81+ expect(existsSync(join(ROOT, "crypto-core/Cargo.lock"))).toBe(true);82+ expect(existsSync(join(ROOT, "crypto-core/src/lib.rs"))).toBe(true);83+ expect(existsSync(join(ROOT, "scripts/build-noise-core.mjs"))).toBe(true);84+ }85+ });86+});scripts/release.ts+6 −6
@@ -10,7 +10,7 @@1010 // AMO_JWT_ISSUER, AMO_JWT_SECRET (not needed for --chrome-only)1111 1212 import { execSync } from "child_process";13-import { readFileSync, existsSync, mkdirSync, rmSync } from "fs";13+import { readFileSync, existsSync, mkdirSync } from "fs";1414 import { join } from "path";1515 1616 const ROOT = process.cwd();@@ -103,7 +103,7 @@103103 console.log(`\n${ACCENT}${BOLD}Chrome Web Store${RESET}`);104104 105105 const chromeZip = join(ROOT, "artifacts", `peach-chrome-${version}.zip`);106-if (existsSync(chromeZip)) rmSync(chromeZip);106+if (existsSync(chromeZip)) run(`trash "${chromeZip}"`, "Moving existing Chrome archive to Trash");107107 108108 run(109109 `cd dist-chrome && zip -r "${chromeZip}" . --exclude '*.map' --exclude '.DS_Store' --exclude '__MACOSX/*'`,@@ -135,7 +135,7 @@135135 if (!CHROME_ONLY) {136136 console.log(`\n${ACCENT}${BOLD}Firefox Add-ons (AMO)${RESET}`);137137 138- const sourceZip = `peach-source-${version}.zip`;138+ const sourceZip = join(ROOT, "artifacts", `peach-source-${version}.zip`);139139 run(140140 [141141 "zip -r",@@ -170,6 +170,8 @@170170 "--source-dir dist-firefox",171171 "--artifacts-dir artifacts",172172 "--channel=listed",173+ "--upload-source-code",174+ sourceZip,173175 ].join(" "),174176 "Signing and submitting to Firefox AMO",175177 // web-ext reads WEB_EXT_API_KEY/WEB_EXT_API_SECRET from env — pass via@@ -180,9 +182,7 @@180182 },181183 );182184 183- rmSync(join(ROOT, sourceZip), { force: true });184-185- success(`Firefox v${version} submitted — review typically takes minutes`);185+ success(`Firefox v${version} submitted with source archive retained in artifacts/`);186186 }187187 188188 // ─── Done ─────────────────────────────────────────────────────────────────────vitest.config.ts+1 −1
@@ -4,7 +4,7 @@44 export default defineConfig({55 plugins: [solidPlugin()],66 test: {7- include: ["src/**/*.test.ts", "src/**/*.test.tsx"],7+ include: ["src/**/*.test.ts", "src/**/*.test.tsx", "scripts/**/*.test.ts"],88 setupFiles: ["./src/test/setup.ts"],99 environment: "node",1010 // M14/SEC-007: tests exercising dev-only handlers