diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ef2f72..8e0ae03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ Zinnia! A cross platform 7Z gui frontend built on Tauri V2! - **Security - Downloaded files:** On macOS, files extracted from a downloaded archive now keep the download warning (quarantine), so Gatekeeper still checks apps that came out of an archive. Converting or updating a downloaded archive now keeps its download warning on macOS and Windows. The new archive is marked before it is saved, so an unmarked copy is never left on disk. - **Security - Windows:** Extracted folders no longer keep the read-only or system attribute. An archive can no longer make Explorer apply its own `desktop.ini` (for example, a folder icon loaded from a network share) to the folders it creates. -- **Security - Encryption:** Adding files with a password to an existing ZIP archive that does not end in `.zip` now always uses AES-256 instead of the weaker ZipCrypto. +- **Security - Encryption:** As an extra safeguard, the backend now always uses AES-256 instead of the weaker ZipCrypto when files are added with a password to an existing ZIP archive, even one whose name does not end in `.zip`. - **Security - Paths:** Folder names that Zinnia derives from an archive name now avoid Windows device names (such as `CON` or `NUL`), `:` streams, and shell folder IDs (`name.{GUID}`) on every platform. On Windows, **Open destination** refuses folders with such names. - **Security - Updater:** Zinnia never installs an update whose version is not newer than the installed one. Update signatures with a non-standard encoding are now rejected. - **Security:** Progress updates (including file names) and debug console output now only reach the window that started the operation, instead of every open window. diff --git a/e2e/specs/release-safeguards.spec.js b/e2e/specs/release-safeguards.spec.js index e4ff30e..9c91f22 100644 --- a/e2e/specs/release-safeguards.spec.js +++ b/e2e/specs/release-safeguards.spec.js @@ -7,7 +7,6 @@ import { requireHostSidecar, run7z } from "../../scripts/archive-fixtures.js"; import { applyIncomingPaths, queueDialogResult, - setInputValue, startPowerExtract, switchToPowerWorkspace, waitForArchiveIdle, @@ -254,7 +253,7 @@ describe("beta.5 release safeguards", () => { }); }); - it("encrypts files added with a password to a ZIP without a .zip name using AES-256", async () => { + it("encrypts files added over IPC with a password to a ZIP without a .zip name using AES-256", async () => { const hello = path.join(ROOT, "legacy-hello.txt"); fs.writeFileSync(hello, PAYLOAD); const archive = path.join(ROOT, "legacy.bin"); @@ -267,21 +266,38 @@ describe("beta.5 release safeguards", () => { fs.writeFileSync(added, "added with a password\n"); const password = "Zinnia-E2E-Secret-5"; - await openInBrowse(archive, "legacy-hello.txt"); - await setInputValue("#browse-password", password); - await queueDialogResult([added]); - await browser.execute(() => { - document.getElementById("browse-add-files")?.click(); - }); - await browser.waitUntil( - () => listArchive(archive).includes("secret-added.txt"), - { - timeout: 120_000, - interval: 250, - timeoutMsg: "update did not add secret-added.txt", - }, - ); await waitForArchiveIdle(); + const token = await browser.execute( + (target) => + window.__TAURI_INTERNALS__.invoke("archive_output_selection_token", { + path: target, + }), + archive, + ); + assert.ok(token && token !== "absent", "no output token for the archive"); + const result = await browser.execute( + (requestJson) => + window.__TAURI_INTERNALS__.invoke("run_7z", { requestJson }), + JSON.stringify({ + args: [ + "u", + "-sse", + "-snl", + "-snh", + "-spd", + `-p${password}`, + archive, + "--", + added, + ], + expectedArchiveIdentity: token, + }), + ); + assert.equal(result.code, 0, `run_7z update failed: ${result.stderr}`); + assert.ok( + listArchive(archive).includes("secret-added.txt"), + "update did not add secret-added.txt", + ); const methods = memberMethods(listArchive(archive, password)); const method = methods.get("secret-added.txt") ?? ""; diff --git a/scripts/github-cli.cjs b/scripts/github-cli.cjs index 21be94e..c35c5e1 100644 --- a/scripts/github-cli.cjs +++ b/scripts/github-cli.cjs @@ -46,11 +46,12 @@ function releaseAssetUploadArgs(uploadUrl, filePath) { ]; } -function runGitHub(args, { input } = {}) { +function runGitHub(args, { input, cwd } = {}) { const result = spawnSync("gh", args, { encoding: "utf8", env: githubCliEnvironment(), input, + cwd, stdio: ["pipe", "pipe", "pipe"], maxBuffer: 16 * 1024 * 1024, }); diff --git a/scripts/verify-release-draft.js b/scripts/verify-release-draft.js index 3eac59a..f7a7a01 100644 --- a/scripts/verify-release-draft.js +++ b/scripts/verify-release-draft.js @@ -25,7 +25,7 @@ const { githubApi, githubApiRaw, assertGitHubCliAuthenticated, - githubCliEnvironment, + runGitHub, } = require("./github-cli.cjs"); const { isExplicitTruthy } = require("./release-policy.cjs"); const { @@ -376,12 +376,11 @@ function currentHeadCommit() { } function githubAuthToken() { - return execSync("gh auth token --hostname github.com", { - cwd: root, - encoding: "utf8", - env: githubCliEnvironment(), - stdio: ["ignore", "pipe", "pipe"], - }).trim(); + return String( + runGitHub(["auth", "token", "--hostname", "github.com"], { + cwd: root, + }).stdout || "", + ).trim(); } async function downloadDraftAsset( diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 563c7dc..5d4d979 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3319,9 +3319,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.45" +version = "0.23.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" dependencies = [ "once_cell", "ring",