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
7 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 −1
manifest.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": {

view file

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",

view file

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",

view file

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("");

view file

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+});

view file

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 ─────────────────────────────────────────────────────────────────────

view file

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

view file