From 1310d2e5a7e914626755a84155178115f116482a Mon Sep 17 00:00:00 2001 From: NuklearRabbit <145918611+NuklearRabbit@users.noreply.github.com> Date: Sun, 26 Jul 2026 02:46:34 +0200 Subject: [PATCH] Release ForgeFlow 0.8.5 trusted asset downloads --- CHANGELOG.md | 6 ++++++ SOURCE_MANIFEST.txt | 19 ++++++++++--------- docs/RELEASE_NOTES_0.8.5.md | 11 +++++++++++ package-lock.json | 4 ++-- package.json | 3 ++- scripts/verify.mjs | 5 +++-- src/main/gitea-service.cjs | 8 ++++++++ src/main/update-service.cjs | 15 +++++++++++---- src/renderer/mock-bridge.js | 2 +- tests/update-service.test.mjs | 25 +++++++++++++++---------- 10 files changed, 69 insertions(+), 29 deletions(-) create mode 100644 docs/RELEASE_NOTES_0.8.5.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 33eaec4..b4abc4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.8.5 - 2026-07-26 + +- fixed packaged update downloads when Gitea reports an asset URL with a different public origin; +- downloads release assets by immutable Gitea asset ID on the configured trusted origin; +- retains strict token isolation and never follows authenticated downloads to another host. + ## 0.8.4 - 2026-07-26 - added interactive animated release-flow illustrations to high-value project surfaces; diff --git a/SOURCE_MANIFEST.txt b/SOURCE_MANIFEST.txt index f9b4013..63a3589 100644 --- a/SOURCE_MANIFEST.txt +++ b/SOURCE_MANIFEST.txt @@ -1,4 +1,4 @@ -ForgeFlow 0.8.4 source manifest +ForgeFlow 0.8.5 source manifest SHA-256 BYTES PATH (The manifest excludes itself, dependencies and generated release artifacts.) 755f4db7d76bfec0963ef051748a82810c0d58acd4ffd823aa6928a5167fceb4 58 .gitignore @@ -12,7 +12,7 @@ ca32a76e708d565c4af659f0f4d2615fc32114c3f75aec1454862a3ed1e72c41 2263 4633990a4b055bb3d00fef915ee29e85be5ee8413f809334728ad9688973c183 3364 build/icon-64.png 25048ed854e8ce8fece115e555c98d25507b002f8019b6ae717b54604c868c50 46223 build/icon.ico 16efd2fca83004f781eae40ae0f706a004ce0bddf338dd087b8adf7eb10c1d84 85704 build/icon.png -30857d5b5ab786f2f7a2b7c15f329b209a5759ea6f34e851898c19c70a01a95d 8985 CHANGELOG.md +ddd7faeae0a1700c3459b48ea086da1103c41efaf87f05a50e349389169f5fed 9288 CHANGELOG.md 21cb96e7afe71b1dc791c818dedd244d92f9a6ed4d9ffbb3022ccb187e1bdf0f 852 docs/ACCEPTANCE.md a17f95d96d3c9fbc69d870874e6fbb7472091adefc454b24f835db1279511d72 8296 docs/ARCHITECTURE.md 30a92bcf5daadb019efa2f82cb820ea302490dd1d68fb772674dc3faccd3e594 2045 docs/DEPLOYMENT_SETUP.md @@ -42,6 +42,7 @@ d7bdc61d9b617ad5acf0b2d468eda547fd7509d4af08f33d2661393f25bdcb5a 576 7f1d7c8bc895d309dad2f8ab444d6d2ba3e68c8daa240fecd2abdd9d8a56ba20 729 docs/RELEASE_NOTES_0.8.2.md c2802fa5dbff392c846b82b55e84a8bfb8e1625546fd1eba39f7129318bece96 654 docs/RELEASE_NOTES_0.8.3.md 8c13279987672314332f648889f52338bcdcb243249f9e1c20fb09d85d7808f5 505 docs/RELEASE_NOTES_0.8.4.md +b516db97a0353babc810c24a87a971d30d72a8021d809e6b833ff7ae0458f442 538 docs/RELEASE_NOTES_0.8.5.md 2b631b9d6d973bdd70869d84886ff339da351e29e17598970b3b27915674661d 4175 docs/ROADMAP.md 1ccde232c060395d7aedce27e89a7647b77afe28ab71de0a5a3efeded57369d3 140415 docs/screenshots/deploy-confirmation.png b39506254ffa2c73c389fb4795b3a745368bbeb7d8514cc47a636316d6d9a6aa 107166 docs/screenshots/deployment-run.png @@ -65,8 +66,8 @@ c230b931abf2293d2d44b7a69b94c35f1142c093cc46b88739a0de5cbd6d1896 1532 4a561ead5ba7cdfaf4efce91842a4308c5f2a77980205879d83835efb8a579db 1067 LICENSE 6765015bdf27b288a250192272750b243c3cb8d1326b752d056d1e43317b6344 12935 main.cjs 91a984a89dd57a084b9a2331763cacdb061582fb590f13df379d92c1a77a2ee1 352 OVERLAY-INSTRUCTIONS.md -6c74e7d16379b92028a17b9d8b5b843734710ce8c2813da75e5cda2e822fa913 130466 package-lock.json -05bead9ffc27397b7854f7bda8150806ca433342613834448b28e72dcdd6ccea 3758 package.json +3ffef38b3c056134c2ed8394e7bdcbb1c73e388024aa4f04a668e791060c600a 130466 package-lock.json +ca3f27afd345ca2880f33f619e01bb161272d99f289f19f94831f4625f286006 3795 package.json 3d2ac366a13e9418e3ec6d13ce95b611f30f0228eb3a80ef9e7a936ce9578e24 9080 preload.cjs b31c43d9355c13b5ae4efc0f3649d8cb8d509b2bb7ebb042ff546b7820fb7de8 8411 Publish-ForgeFlow-Release.ps1 a6d32a742412b7836606be00f17be0465f1b6f55d3911f6c73a14029787ba206 14037 README.md @@ -79,7 +80,7 @@ f8359a69d20deb2dfe10042d1bec7b12a95e76e58e36bc5f265f073c3111d056 10287 74433d8a6b24afe368197a469e2fe0c5050c239d7250b84c2f3f598c304778b0 4736 scripts/publish-binary-release.cjs 444b397d515d65a7ee59d3088cba869cbb812d2b8cc18fc5d255105e3edb58c2 1468 scripts/serve-demo.mjs 42203f9e0fd4aae517284d387f265cf1b0b180379bc253a092b5c3c5c4caef0a 2992 scripts/validate-installed-connections.cjs -1b03d3144b911863a688ec4be2ae471afd09214ee901bd76c42429c0f5285062 12242 scripts/verify.mjs +c73c8bed917ee7dbf5f7fa57923456a4fcf43b1b60b3a67da7cfe9b5e2c223f5 12275 scripts/verify.mjs 0079701b5acbfef07b71a9623613d1940805ccd20649d77e3f34c37e79df7655 735 scripts/write-release-checksums.mjs 619515f524cb89960370ffcbd3fafd3c0e178b95f69c5868b1dd44777f23ec1e 2081 setup-windows.ps1 dd613d04b366f2cd071a1685a414016a5fb008082ed1b4cb8b24b79c100f640a 2412 src/main/audit-service.cjs @@ -89,7 +90,7 @@ a381848a296c28f6d14093c96f722967acf9c994ffb867d54dd92bf5ada2729b 23648 c157640e76d558906a9aa9881eda811196623ef1c65fa3467f32f0f84b0ddd0c 15095 src/main/diagnostics-service.cjs a2ef47d5330095b92c2bd22fcc39962091881f9cb60d02e261eb1dd1bd693170 1974 src/main/external-tools-service.cjs 0b7476c2cfe1872601978c20a466c20fe58be35e81b2303e38a753fea62bbc27 32548 src/main/git-service.cjs -f5b4e468c92eb0d96d02357290ac4bfe2d30eef9c7287267882bc146237a8693 16559 src/main/gitea-service.cjs +75f25fa8ee520b03b0bbe4c4cea439853202d318547343cda49a19c6ab868901 16984 src/main/gitea-service.cjs 79593a28b8f40f48a73028be34464f94be2e4c67a027a3f5da78a33bcadc76eb 45539 src/main/ipc.cjs 62f2c80c8210e19370b8556b1f296cbae50dae6b758a39e209f8fb461691fd4c 4235 src/main/log-redaction.cjs 958595a99fb242c127f475f3d8622bdba4c07b2d658703f69fe3992227a9107e 12909 src/main/preflight-service.cjs @@ -98,14 +99,14 @@ e89b54e7e3174b4b0a1dcd9058d8344e29431f9d16d0e6bb8d11559b691440a0 2508 17e2a53f61cd7faba461b9f332967143087eaac95b72001462292976278ca305 7782 src/main/repository-service.cjs b31a63bf8cb1807b3e838e2bf8a0e742738f119d13de8ca9f42e471f072217d3 8328 src/main/ssh-service.cjs 8cf5013de91737dd9345b121586ceec38a6fc8518648975d60593d81fc225c4b 67376 src/main/unraid-deployment-service.cjs -36cc05deda3395e5e9de92b880c8315f508cb88e9b080ae34705057ae696804f 20696 src/main/update-service.cjs +27b9e15dd6530bc7bfab51414430eaef169e0cc4acd027257b1c63f891af8c85 20733 src/main/update-service.cjs ef029189791024421d78b3ac92981f03b20f2086fc4846eaa95d509f65e096a9 194049 src/renderer/app.js 16efd2fca83004f781eae40ae0f706a004ce0bddf338dd087b8adf7eb10c1d84 85704 src/renderer/assets/itworx-mark.png 813b8cdeecac43794166f3db9d3c5d2c441e0292f9ab7bd465ba136d6201e95d 82476 src/renderer/assets/itworx-wordmark-dark.png 094c1b71cc2482a9db250ac175f45f3de68f53277dfbde371a03e61923d00988 75240 src/renderer/assets/itworx-wordmark-light.png 813b8cdeecac43794166f3db9d3c5d2c441e0292f9ab7bd465ba136d6201e95d 82476 src/renderer/assets/itworx-wordmark.png e1c463d6cda9f2b9b78c468845c0a7e8688f0362be5642074a1a5f7122dfe811 762 src/renderer/index.html -342faf4acf1464c871114ca1c82ef926a64cb612a93d022dba401873925f3177 50922 src/renderer/mock-bridge.js +34dd57cd916b3b2faabaf73d3063d5e6ed734393f13db795b51bff150f4bdc2d 50922 src/renderer/mock-bridge.js ebdd804b55c0b13cbf57e51c6d51a60dd838d67bb4c4eaf17ca34c86938a6865 67440 src/renderer/styles.css 0a1e9d9d6cd4d190eb7f85dbc6668d80600b1cf2749cc0c2c51cc428f506f20d 1121 src/shared/clone-target.cjs 5d425d5c2f939d0f6beebee7ebb0c77146cb7e318535ba7286ec7081a4dc2269 2497 src/shared/deployment-policy.cjs @@ -143,7 +144,7 @@ bab853feb0e22aa25af17989baaa632c01efa636533ea67407fecfdd973c7024 627 020eccfa9c4aef7a4ac4736d9af90518fcb6d1ad75aedcfaa1c92832a9e3d6d8 4609 tests/shell-verification.test.mjs 8a6a8477eb94b85ccef18cddd2640afb0d1eafa679c96bc7de20428d5d69e1be 1794 tests/tool-invocation.test.mjs 05c791ea262aef85a7c79874ed900c5d792a78778c577cb803b88a433cd6e2b6 21413 tests/unraid-deployment.test.mjs -0c49d3222ea362dbef171f5ad556a335bad670b47adef660ede101d84814d05e 14849 tests/update-service.test.mjs +f1f0f13ac41f47c9df8ef778d7b98c09a589f61e6f76e14bc0e8943c5ede7560 14990 tests/update-service.test.mjs 9cea5c1d5ba3e0972a0b5c7236cf1f7c5616373e0a39ea4a492ecebf70452e40 948 tests/validation.test.mjs 7ef4d4b9f5f3e6979293b29d571ce0e39f83197f3cade2d999a9cea7bacdd84d 1781 tests/zip-writer.test.mjs 8f36b542736f2933bad8b9464ad7fa37b68196009c81cf702ce3b677cd637dea 767 UPDATE_FROM_0.3.2.md diff --git a/docs/RELEASE_NOTES_0.8.5.md b/docs/RELEASE_NOTES_0.8.5.md new file mode 100644 index 0000000..3d2f237 --- /dev/null +++ b/docs/RELEASE_NOTES_0.8.5.md @@ -0,0 +1,11 @@ +# ForgeFlow 0.8.5 + +ForgeFlow 0.8.5 fixes packaged update downloads for Gitea instances whose +release metadata reports a public asset URL with a different scheme or origin. + +The updater now ignores that mutable browser URL and downloads each release +asset through its immutable asset ID on the configured, trusted Gitea origin. +Authentication tokens remain protected and are never sent to another host. + +Install 0.8.5 manually when upgrading from 0.8.4 because the affected download +path runs before the new updater code can be installed. diff --git a/package-lock.json b/package-lock.json index a21048f..52d60ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "forgeflow", - "version": "0.8.4", + "version": "0.8.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "forgeflow", - "version": "0.8.4", + "version": "0.8.5", "dependencies": { "ssh2": "1.17.0" }, diff --git a/package.json b/package.json index c0cecab..9ebf588 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "forgeflow", - "version": "0.8.4", + "version": "0.8.5", "private": true, "description": "Desktop release cockpit for local Git, Gitea Actions and controlled exact-commit deployments.", "main": "main.cjs", @@ -77,6 +77,7 @@ "docs/RELEASE_NOTES_0.8.2.md", "docs/RELEASE_NOTES_0.8.3.md", "docs/RELEASE_NOTES_0.8.4.md", + "docs/RELEASE_NOTES_0.8.5.md", "docs/ACCEPTANCE.md" ], "asarUnpack": [ diff --git a/scripts/verify.mjs b/scripts/verify.mjs index 247e62a..58503a4 100644 --- a/scripts/verify.mjs +++ b/scripts/verify.mjs @@ -65,6 +65,7 @@ const required = [ "docs/RELEASE_NOTES_0.8.2.md", "docs/RELEASE_NOTES_0.8.3.md", "docs/RELEASE_NOTES_0.8.4.md", + "docs/RELEASE_NOTES_0.8.5.md", "docs/UPDATING.md", "docs/DIAGNOSTICS.md", "docs/DEPLOYMENT_SETUP.md", @@ -103,9 +104,9 @@ for (const file of required) await access(path.join(root, file)); const packageJson = JSON.parse( await readFile(path.join(root, "package.json"), "utf8"), ); -if (packageJson.version !== "0.8.4") +if (packageJson.version !== "0.8.5") throw new Error( - `Expected package version 0.8.4, got ${packageJson.version}.`, + `Expected package version 0.8.5, got ${packageJson.version}.`, ); const sourceManifest = await readFile( path.join(root, "SOURCE_MANIFEST.txt"), diff --git a/src/main/gitea-service.cjs b/src/main/gitea-service.cjs index fd07dd1..9bd4047 100644 --- a/src/main/gitea-service.cjs +++ b/src/main/gitea-service.cjs @@ -331,6 +331,14 @@ class GiteaService { throw new Error("The update download exceeded the redirect limit."); } + async downloadReleaseAsset(owner, repo, assetId, options = {}) { + const numericId = Number(assetId); + if (!Number.isSafeInteger(numericId) || numericId <= 0) + throw new Error("Gitea returned an invalid release asset ID."); + const assetPath = `/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/releases/assets/${numericId}`; + return this.downloadAuthenticated(assetPath, options); + } + async dispatchWorkflow({ owner, repo, workflowFile, ref, inputs = {} }) { const result = await this.request( `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/actions/workflows/${encodeURIComponent(workflowFile)}/dispatches`, diff --git a/src/main/update-service.cjs b/src/main/update-service.cjs index ef1ba0e..9546631 100644 --- a/src/main/update-service.cjs +++ b/src/main/update-service.cjs @@ -291,7 +291,7 @@ class UpdateService { const assets = Array.isArray(release.assets) ? release.assets : []; const asset = assets.find((item) => item.name === assetName); const checksumAsset = assets.find((item) => item.name === checksumName); - if (!asset?.browser_download_url || !checksumAsset?.browser_download_url) { + if (!asset?.id || !checksumAsset?.id) { const error = new Error( `Release v${update.remoteVersion} is missing ${assetName} or its SHA-256 file.`, ); @@ -300,8 +300,12 @@ class UpdateService { } const [binary, checksumBytes] = await Promise.all([ - this.gitea.downloadAuthenticated(asset.browser_download_url), - this.gitea.downloadAuthenticated(checksumAsset.browser_download_url), + this.gitea.downloadReleaseAsset(update.owner, update.repo, asset.id), + this.gitea.downloadReleaseAsset( + update.owner, + update.repo, + checksumAsset.id, + ), ]); if (binary.length < 1_000_000 || binary[0] !== 0x4d || binary[1] !== 0x5a) { throw new Error( @@ -639,7 +643,10 @@ class UpdateService { .catch(() => []); const candidates = []; for (const entry of entries) { - if (!entry.isFile() || !/^(?:apply|binary)-.*\.status\.json$/i.test(entry.name)) + if ( + !entry.isFile() || + !/^(?:apply|binary)-.*\.status\.json$/i.test(entry.name) + ) continue; const filePath = path.join(this.updateDirectory, entry.name); const stat = await fs.stat(filePath).catch(() => null); diff --git a/src/renderer/mock-bridge.js b/src/renderer/mock-bridge.js index e7d09d1..59218cb 100644 --- a/src/renderer/mock-bridge.js +++ b/src/renderer/mock-bridge.js @@ -564,7 +564,7 @@ await wait(80); snapshot(); return { - appVersion: "0.8.4-demo", + appVersion: "0.8.5-demo", platform: "win32", state: clone(state), git: { available: true, version: "git version 2.47.3" }, diff --git a/tests/update-service.test.mjs b/tests/update-service.test.mjs index 425e7b8..4dac5e1 100644 --- a/tests/update-service.test.mjs +++ b/tests/update-service.test.mjs @@ -340,18 +340,21 @@ test("packaged updater downloads only a published checksum-matched Windows asset draft: false, prerelease: false, assets: [ - { name: assetName, browser_download_url: "https://gitea.test/setup" }, + { + id: 41, + name: assetName, + browser_download_url: "http://wrong-origin.test/setup", + }, { name: `${assetName}.sha256`, - browser_download_url: "https://gitea.test/checksum", + id: 42, + browser_download_url: "http://wrong-origin.test/checksum", }, ], }; }, - async downloadAuthenticated(url) { - return url.endsWith("/checksum") - ? Buffer.from(`${sha256} ${assetName}\n`) - : binary; + async downloadReleaseAsset(_owner, _repo, assetId) { + return assetId === 42 ? Buffer.from(`${sha256} ${assetName}\n`) : binary; }, }; const service = new UpdateService({ @@ -398,18 +401,20 @@ test("packaged updater rejects a binary whose checksum does not match", async () tag_name: "v0.8.2", assets: [ { + id: 51, name: assetName, - browser_download_url: "https://gitea.test/portable", + browser_download_url: "http://wrong-origin.test/portable", }, { + id: 52, name: `${assetName}.sha256`, - browser_download_url: "https://gitea.test/checksum", + browser_download_url: "http://wrong-origin.test/checksum", }, ], }; }, - async downloadAuthenticated(url) { - return url.endsWith("/checksum") + async downloadReleaseAsset(_owner, _repo, assetId) { + return assetId === 52 ? Buffer.from(`${"0".repeat(64)} ${assetName}`) : binary; },