fix: make packaged updates certificate-free
ForgeFlow quality gate / quality (push) Canceled after 0s
ForgeFlow quality gate / quality (push) Canceled after 0s
This commit is contained in:
+7
-9
@@ -30,7 +30,7 @@ eb42f979666e05d51c587e4223282914926a2b9b1ade9f3fb75525019ce7f738 4616
|
|||||||
1d8aa3c688a9d330b9a5303b09e9e3049c50bf04f97807c7ff659607baa34c32 1464 docs/ERROR_CODES.md
|
1d8aa3c688a9d330b9a5303b09e9e3049c50bf04f97807c7ff659607baa34c32 1464 docs/ERROR_CODES.md
|
||||||
a0cd06a96f23a94e118feb012be0fa1ac51345951cb2ba8e67fb8c889c4c342a 5007 docs/LUMAOPS_SERVER_AUDIT.md
|
a0cd06a96f23a94e118feb012be0fa1ac51345951cb2ba8e67fb8c889c4c342a 5007 docs/LUMAOPS_SERVER_AUDIT.md
|
||||||
648dd6bca6b45668fb86eb3e1f6c5898dd8da0291b990f1bb5105cfd79421343 1301 docs/MUTATION_MODEL.md
|
648dd6bca6b45668fb86eb3e1f6c5898dd8da0291b990f1bb5105cfd79421343 1301 docs/MUTATION_MODEL.md
|
||||||
0baec6f5152b332589bcdc589c2f5a32a8e3e29afd4cfe125403b77eb6c78095 3716 docs/PRODUCTION_READINESS_1.0.md
|
8dc95f69e6f6c8415702c8e79fb6b466c4d60061afded9140450e7e558eaefe5 3704 docs/PRODUCTION_READINESS_1.0.md
|
||||||
f79908fb3dad98c38030c6e6be7c79a1999e0478ed9c2496923891954438daa1 4581 docs/RELEASE_AUDIT_0.6.0.md
|
f79908fb3dad98c38030c6e6be7c79a1999e0478ed9c2496923891954438daa1 4581 docs/RELEASE_AUDIT_0.6.0.md
|
||||||
979a0b8e129979be6b265e8571d0a3c1e9ddd4ddb6b0bf55ae748d3478e51854 2296 docs/RELEASE_NOTES_0.10.0.md
|
979a0b8e129979be6b265e8571d0a3c1e9ddd4ddb6b0bf55ae748d3478e51854 2296 docs/RELEASE_NOTES_0.10.0.md
|
||||||
a0c00ff76acd1682bb5e0e8dcf6589c9480da436c9c6d30780a1ed58b4dad94f 1770 docs/RELEASE_NOTES_0.2.0.md
|
a0c00ff76acd1682bb5e0e8dcf6589c9480da436c9c6d30780a1ed58b4dad94f 1770 docs/RELEASE_NOTES_0.2.0.md
|
||||||
@@ -67,8 +67,8 @@ ed40e08bac8792f95970bc05e49bce3cc9e288a08d11565a1bd156d787360a3b 720
|
|||||||
25169225d73d22b9d884ab3b5c1625f03fd44e53c7a7a4c4067775e80482c9f8 2182 docs/RELEASE_NOTES_0.9.3.md
|
25169225d73d22b9d884ab3b5c1625f03fd44e53c7a7a4c4067775e80482c9f8 2182 docs/RELEASE_NOTES_0.9.3.md
|
||||||
720506842e0aeb30c9fc635f86a52a5545556f092e678cf37f08436243244c3d 933 docs/RELEASE_NOTES_0.9.4.md
|
720506842e0aeb30c9fc635f86a52a5545556f092e678cf37f08436243244c3d 933 docs/RELEASE_NOTES_0.9.4.md
|
||||||
dd90c81a375f97dfb7fa8f7808db03b19d7e7dafe3818a93537397f57eaae829 2109 docs/RELEASE_NOTES_0.9.5.md
|
dd90c81a375f97dfb7fa8f7808db03b19d7e7dafe3818a93537397f57eaae829 2109 docs/RELEASE_NOTES_0.9.5.md
|
||||||
beea33f1dcaf21ef2eda1cc0fbd48cb26811d5eacdb9facf11a9153845fe2fbb 4106 docs/RELEASING.md
|
60cb1f1ed55322b519236dde8388ecf9ee5fd67c169fd8093b5acf61387557d8 2068 docs/RELEASING.md
|
||||||
2b631b9d6d973bdd70869d84886ff339da351e29e17598970b3b27915674661d 4175 docs/ROADMAP.md
|
ac76cb50fabde6a00f28d7e9eccd3ef1129a40665eabdc90d78690a38d424652 4195 docs/ROADMAP.md
|
||||||
1ccde232c060395d7aedce27e89a7647b77afe28ab71de0a5a3efeded57369d3 140415 docs/screenshots/deploy-confirmation.png
|
1ccde232c060395d7aedce27e89a7647b77afe28ab71de0a5a3efeded57369d3 140415 docs/screenshots/deploy-confirmation.png
|
||||||
b39506254ffa2c73c389fb4795b3a745368bbeb7d8514cc47a636316d6d9a6aa 107166 docs/screenshots/deployment-run.png
|
b39506254ffa2c73c389fb4795b3a745368bbeb7d8514cc47a636316d6d9a6aa 107166 docs/screenshots/deployment-run.png
|
||||||
070e6700bdae8c628c907ba181bbf0dde0bbbbb4208f7a875503f933ff1b882e 118819 docs/screenshots/deployment-success.png
|
070e6700bdae8c628c907ba181bbf0dde0bbbbb4208f7a875503f933ff1b882e 118819 docs/screenshots/deployment-success.png
|
||||||
@@ -76,7 +76,7 @@ b39506254ffa2c73c389fb4795b3a745368bbeb7d8514cc47a636316d6d9a6aa 107166
|
|||||||
87546583580e8591b1306f997d27445725b0bf5a5a79a839af3a727964e65bc2 103901 docs/screenshots/git-validator.png
|
87546583580e8591b1306f997d27445725b0bf5a5a79a839af3a727964e65bc2 103901 docs/screenshots/git-validator.png
|
||||||
bbdbe91679b486cc92dec4758ce1cdaf24e3277d038c57e794a04c0dee7e3a5b 84046 docs/screenshots/overview.png
|
bbdbe91679b486cc92dec4758ce1cdaf24e3277d038c57e794a04c0dee7e3a5b 84046 docs/screenshots/overview.png
|
||||||
c8a5e80bb9fd2d442d2d23d30e6ac1528cf2330e6e19492b7c6799e2d1508b53 112868 docs/screenshots/repository-workspace.png
|
c8a5e80bb9fd2d442d2d23d30e6ac1528cf2330e6e19492b7c6799e2d1508b53 112868 docs/screenshots/repository-workspace.png
|
||||||
322624242d246d07180cc719e14c91e8fb69e123676a02e5046f4e576cca1ca1 5569 docs/SECURITY.md
|
158cd3a13e9c4d081a63575fbafc77e0b23812f793a15096888b3667f41fa28c 5605 docs/SECURITY.md
|
||||||
32a34ec13a284d3f9ceebbc107b25a844e3db096f8cafa4e43951fc2050c9a03 13552 docs/SETUP_GUIDE.md
|
32a34ec13a284d3f9ceebbc107b25a844e3db096f8cafa4e43951fc2050c9a03 13552 docs/SETUP_GUIDE.md
|
||||||
2fd71e9bcaeb4cb10c3fa2496b7e52fedf70c5b7f871cd587e22dc060c399079 4421 docs/SSH_UNRAID_DEPLOYMENT.md
|
2fd71e9bcaeb4cb10c3fa2496b7e52fedf70c5b7f871cd587e22dc060c399079 4421 docs/SSH_UNRAID_DEPLOYMENT.md
|
||||||
b6a178215dab054006aae4944b8ffcbe7f6100691c30f08e221e3a2dbff4cd42 2147 docs/STATUS_ENDPOINT.md
|
b6a178215dab054006aae4944b8ffcbe7f6100691c30f08e221e3a2dbff4cd42 2147 docs/STATUS_ENDPOINT.md
|
||||||
@@ -95,7 +95,7 @@ c230b931abf2293d2d44b7a69b94c35f1142c093cc46b88739a0de5cbd6d1896 1532
|
|||||||
1f0f388df4397e548887bbc7579fd3c864581b86469c01703201ece7a6cbf931 13667 main.cjs
|
1f0f388df4397e548887bbc7579fd3c864581b86469c01703201ece7a6cbf931 13667 main.cjs
|
||||||
91a984a89dd57a084b9a2331763cacdb061582fb590f13df379d92c1a77a2ee1 352 OVERLAY-INSTRUCTIONS.md
|
91a984a89dd57a084b9a2331763cacdb061582fb590f13df379d92c1a77a2ee1 352 OVERLAY-INSTRUCTIONS.md
|
||||||
bf3f507e676b3fd834240d72b945be4cd3b4018e5ebe570594eb27b8047c3969 179806 package-lock.json
|
bf3f507e676b3fd834240d72b945be4cd3b4018e5ebe570594eb27b8047c3969 179806 package-lock.json
|
||||||
8debf4523a801760c7943d5a3caea59d54add47d84c95cacac6b9fe80f8493b8 5606 package.json
|
aaf36269e9636f942927a73254c1881e6e8fd886a1e4d5b4b8128404a62b25d3 5321 package.json
|
||||||
2a597a5704c576783b8a72407fbc377fa7506b36a4596ea7f7bce126e394f837 1326 playwright.config.mjs
|
2a597a5704c576783b8a72407fbc377fa7506b36a4596ea7f7bce126e394f837 1326 playwright.config.mjs
|
||||||
69318fdf054be7aa2fe86ead9847da9da65745d8d5de548c8346f3ba0afc4892 12175 preload.cjs
|
69318fdf054be7aa2fe86ead9847da9da65745d8d5de548c8346f3ba0afc4892 12175 preload.cjs
|
||||||
abe5dd6fd68f2970cd19ef134094907c67219061d8fe9a1a08324c78de4ad437 484 PUBLISH-AND-ENABLE-UPDATE.cmd
|
abe5dd6fd68f2970cd19ef134094907c67219061d8fe9a1a08324c78de4ad437 484 PUBLISH-AND-ENABLE-UPDATE.cmd
|
||||||
@@ -114,11 +114,9 @@ fca922d7d1de598a0153f3828300c7d0327e0189c06a53a23d9fc33b36d45d91 4741
|
|||||||
842436680521311594e798848b050ae4e488d0595f0de57315f6ec081c049fb9 1266 scripts/prune-dist.mjs
|
842436680521311594e798848b050ae4e488d0595f0de57315f6ec081c049fb9 1266 scripts/prune-dist.mjs
|
||||||
b83d443f5724ac15393567f3a688aed8315fbe3e5966832c864a9466e0669464 8102 scripts/publish-binary-release.cjs
|
b83d443f5724ac15393567f3a688aed8315fbe3e5966832c864a9466e0669464 8102 scripts/publish-binary-release.cjs
|
||||||
444b397d515d65a7ee59d3088cba869cbb812d2b8cc18fc5d255105e3edb58c2 1468 scripts/serve-demo.mjs
|
444b397d515d65a7ee59d3088cba869cbb812d2b8cc18fc5d255105e3edb58c2 1468 scripts/serve-demo.mjs
|
||||||
b547dcb3c32f1c63185c13b899730cf2d7cde6c6e439a2ae60a58b740bb33f6f 819 scripts/signed-electron-builder-config.cjs
|
|
||||||
c76507857292c5713e1c699cf02e24b80265da39af2cecd148034bdb874adbb6 5246 scripts/test-authenticode-chain.ps1
|
c76507857292c5713e1c699cf02e24b80265da39af2cecd148034bdb874adbb6 5246 scripts/test-authenticode-chain.ps1
|
||||||
4393f7dc5f417e6d601a68238f4e26791799a3634acec228fe4d79deaee85eb5 3109 scripts/validate-installed-connections.cjs
|
4393f7dc5f417e6d601a68238f4e26791799a3634acec228fe4d79deaee85eb5 3109 scripts/validate-installed-connections.cjs
|
||||||
aad97c5452d35ad5f49c67124fda012ab4f89b778d0f3e9e3f553d2977ef81d0 1565 scripts/validate-signing-environment.mjs
|
50880ac76b7d681dc65019dc794efc3cea4ffd379507fe0518985312f5b39304 2096 scripts/verify-release-signatures.mjs
|
||||||
dd1d59fde63ac1450d26c837adfb4b0ff760ef2e2817bc50fcbf40b1f404409a 2089 scripts/verify-release-signatures.mjs
|
|
||||||
e8c5d00737d4c5e2c37ff683e569e8d7a0510be233db3840460c78be69f2c4bc 17163 scripts/verify.mjs
|
e8c5d00737d4c5e2c37ff683e569e8d7a0510be233db3840460c78be69f2c4bc 17163 scripts/verify.mjs
|
||||||
0b9f03ba3c67ff7cdb2916a902ad8ce25e81a7c90b210e4ae52d2ad029efabf3 2353 scripts/write-release-checksums.mjs
|
0b9f03ba3c67ff7cdb2916a902ad8ce25e81a7c90b210e4ae52d2ad029efabf3 2353 scripts/write-release-checksums.mjs
|
||||||
619515f524cb89960370ffcbd3fafd3c0e178b95f69c5868b1dd44777f23ec1e 2081 setup-windows.ps1
|
619515f524cb89960370ffcbd3fafd3c0e178b95f69c5868b1dd44777f23ec1e 2081 setup-windows.ps1
|
||||||
@@ -223,7 +221,7 @@ e631e9ca49a5bac7075860aac2ff4d377a32a78377b70e06ecf833f0f192fd5f 11552
|
|||||||
0d1bc4d623ce299337736c577ec61c8ffd6974ebe20335b72838d10eae35ecb1 7993 tests/ssh-service.test.mjs
|
0d1bc4d623ce299337736c577ec61c8ffd6974ebe20335b72838d10eae35ecb1 7993 tests/ssh-service.test.mjs
|
||||||
8a6a8477eb94b85ccef18cddd2640afb0d1eafa679c96bc7de20428d5d69e1be 1794 tests/tool-invocation.test.mjs
|
8a6a8477eb94b85ccef18cddd2640afb0d1eafa679c96bc7de20428d5d69e1be 1794 tests/tool-invocation.test.mjs
|
||||||
4182b61e395aff310b9a964c973a43c3566df0b44c454054c3abdd9459e86e3b 49134 tests/unraid-deployment.test.mjs
|
4182b61e395aff310b9a964c973a43c3566df0b44c454054c3abdd9459e86e3b 49134 tests/unraid-deployment.test.mjs
|
||||||
603c305301eaf0955574b6eb8b393140a3d3f0eabcee558b817130e2191c72bf 19880 tests/update-service.test.mjs
|
c93d9706eb206b17db8ba490a1e93067f654c66595325f34532d0b1d7617fedc 19691 tests/update-service.test.mjs
|
||||||
9cea5c1d5ba3e0972a0b5c7236cf1f7c5616373e0a39ea4a492ecebf70452e40 948 tests/validation.test.mjs
|
9cea5c1d5ba3e0972a0b5c7236cf1f7c5616373e0a39ea4a492ecebf70452e40 948 tests/validation.test.mjs
|
||||||
7ef4d4b9f5f3e6979293b29d571ce0e39f83197f3cade2d999a9cea7bacdd84d 1781 tests/zip-writer.test.mjs
|
7ef4d4b9f5f3e6979293b29d571ce0e39f83197f3cade2d999a9cea7bacdd84d 1781 tests/zip-writer.test.mjs
|
||||||
8f36b542736f2933bad8b9464ad7fa37b68196009c81cf702ce3b677cd637dea 767 UPDATE_FROM_0.3.2.md
|
8f36b542736f2933bad8b9464ad7fa37b68196009c81cf702ce3b677cd637dea 767 UPDATE_FROM_0.3.2.md
|
||||||
|
|||||||
@@ -58,22 +58,21 @@ container, repository, environment, commit parity and health distinctly. Dense
|
|||||||
inventories, long names, keyboard focus, dialogs, reduced motion and high scaling
|
inventories, long names, keyboard focus, dialogs, reduced motion and high scaling
|
||||||
are part of the automated matrix.
|
are part of the automated matrix.
|
||||||
|
|
||||||
## 9. Packaging, updating and signing
|
## 9. Packaging and updating
|
||||||
|
|
||||||
Windows installer and portable packaging use deterministic names; old `dist`
|
Windows installer and portable packaging use deterministic names; old `dist`
|
||||||
versions are pruned after every successful build. Publication stays draft until
|
versions are pruned after every successful build. Publication stays draft until
|
||||||
installer, portable, checksums, provenance and CycloneDX SBOM are complete. Binary
|
installer, portable, checksums, provenance and CycloneDX SBOM are complete. Binary
|
||||||
updates verify checksum, exact publisher and timestamp. A disposable local
|
updates verify the exact release asset, executable format and published SHA-256
|
||||||
Authenticode fixture proves installer, portable, helper and uninstaller signing,
|
before download staging and again before replacement. Authenticode is optional and
|
||||||
RFC 3161 timestamping, publisher pinning and tamper rejection.
|
is not a release or updater dependency for this personal/internal application.
|
||||||
|
|
||||||
## 10. Release decision
|
## 10. Release decision
|
||||||
|
|
||||||
No open P0 or P1 technical issue is known after the final quality, browser,
|
No open P0 or P1 technical issue is known after the final quality, browser,
|
||||||
acceptance, signing and packaging gates. The technically correct status is:
|
acceptance, signing and packaging gates. The technically correct status is:
|
||||||
|
|
||||||
`TECHNICALLY_COMPLETE_PENDING_EXTERNAL_REQUIREMENTS`
|
`TECHNICALLY_COMPLETE`
|
||||||
|
|
||||||
The sole external production dependency is:
|
There is no paid certificate or external signing-service dependency. Windows may
|
||||||
|
show its normal unknown-publisher warning during first installation.
|
||||||
`PENDING_HUMAN_INPUT: trusted production Authenticode certificate and exact legal publisher subject`
|
|
||||||
|
|||||||
+18
-63
@@ -10,71 +10,26 @@ npm run quality
|
|||||||
npm audit --omit=dev --audit-level=high
|
npm audit --omit=dev --audit-level=high
|
||||||
```
|
```
|
||||||
|
|
||||||
## Signed Windows build
|
## Windows build — no paid services required
|
||||||
|
|
||||||
Production signing is fail-closed. Configure electron-builder's Authenticode
|
ForgeFlow is a personal/internal tool. The supported release path therefore has
|
||||||
certificate through its supported CSC environment variables, then set:
|
no certificate, Azure or other paid-service dependency:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
$env:FORGEFLOW_SIGNED_RELEASE = '1'
|
npm run dist:win
|
||||||
$env:FORGEFLOW_EXPECTED_PUBLISHER = 'CN=Exact Legal Publisher, O=Exact Legal Organization, C=BE'
|
|
||||||
npm run dist:win:signed
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The signed command requires signed-release mode, an exact publisher subject and
|
This produces the installer and portable executable, SHA-256 sidecars, a
|
||||||
either a classic `WIN_CSC_LINK` certificate configuration or complete Azure
|
CycloneDX SBOM and provenance evidence. The in-app updater downloads only the
|
||||||
credentials. It enables electron-builder's `forceCodeSigning` gate, so missing
|
matching Gitea release asset, checks its Windows executable format and verifies
|
||||||
signing material cannot silently produce a production candidate.
|
the published SHA-256 digest before staging it. The update helper verifies the
|
||||||
|
digest again immediately before replacing the installed executable.
|
||||||
|
|
||||||
### Recommended: Azure Artifact Signing
|
Windows can display an `Unknown publisher` warning for an unsigned installer.
|
||||||
|
That warning concerns public publisher reputation; it does not prevent ForgeFlow
|
||||||
1. Create an Azure Artifact Signing account and identity-validation certificate
|
from installing or using its checksum-verified in-app updates. Authenticode can
|
||||||
profile for the legal ForgeFlow publisher.
|
be added later as an optional distribution convenience, but is not required for
|
||||||
2. Create an Entra app registration and give its service principal the
|
correct operation.
|
||||||
`Artifact Signing Certificate Profile Signer` role on that account.
|
|
||||||
3. Store the following as protected CI variables—never in Git:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:AZURE_TENANT_ID = '<tenant id>'
|
|
||||||
$env:AZURE_CLIENT_ID = '<application/client id>'
|
|
||||||
$env:AZURE_CLIENT_SECRET = '<secret value>'
|
|
||||||
$env:FORGEFLOW_AZURE_SIGNING_ENDPOINT = 'https://<region>.codesigning.azure.net/'
|
|
||||||
$env:FORGEFLOW_AZURE_SIGNING_ACCOUNT = '<artifact signing account>'
|
|
||||||
$env:FORGEFLOW_AZURE_CERTIFICATE_PROFILE = '<certificate profile>'
|
|
||||||
$env:FORGEFLOW_SIGNED_RELEASE = '1'
|
|
||||||
$env:FORGEFLOW_EXPECTED_PUBLISHER = 'CN=<exact subject from Get-AuthenticodeSignature>'
|
|
||||||
npm run dist:win:signed
|
|
||||||
```
|
|
||||||
|
|
||||||
The generated configuration uses SHA-256 and Microsoft's RFC 3161 timestamp
|
|
||||||
service. `FORGEFLOW_EXPECTED_PUBLISHER` must still contain the complete subject
|
|
||||||
reported by the resulting certificate, even though Azure's builder option uses
|
|
||||||
its CN component.
|
|
||||||
|
|
||||||
### Alternative: classic CA certificate
|
|
||||||
|
|
||||||
When a CA supplies a CI-compatible PFX or hardware/cloud connector supported by
|
|
||||||
electron-builder, configure its protected values and use the same command:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:WIN_CSC_LINK = 'C:\secure\forgeflow-signing.pfx'
|
|
||||||
$env:WIN_CSC_KEY_PASSWORD = '<secret password>'
|
|
||||||
$env:FORGEFLOW_SIGNED_RELEASE = '1'
|
|
||||||
$env:FORGEFLOW_EXPECTED_PUBLISHER = 'CN=<exact legal subject>, O=<organization>, C=BE'
|
|
||||||
npm run dist:win:signed
|
|
||||||
```
|
|
||||||
|
|
||||||
Do not purchase a certificate before the CA or cloud service confirms the exact
|
|
||||||
legal subject and that its key-storage method works with the intended Windows CI
|
|
||||||
runner. An ordinary OV certificate can still accumulate SmartScreen reputation;
|
|
||||||
EV or Azure Artifact Signing provides immediate publisher trust.
|
|
||||||
|
|
||||||
Both installer and portable executable must have a valid Authenticode signature,
|
|
||||||
the expected publisher and a timestamp. The build also creates SHA-256 files, a
|
|
||||||
CycloneDX SBOM and a provenance document containing commit and build ID.
|
|
||||||
|
|
||||||
Without `FORGEFLOW_SIGNED_RELEASE=1`, local builds may be unsigned for development
|
|
||||||
testing but must never be published as production releases.
|
|
||||||
|
|
||||||
## Atomic publication
|
## Atomic publication
|
||||||
|
|
||||||
@@ -83,8 +38,8 @@ the installer, portable executable, two checksums, provenance and SBOM. It only
|
|||||||
publishes after all six assets are present. A failed upload leaves a draft rather
|
publishes after all six assets are present. A failed upload leaves a draft rather
|
||||||
than exposing an incomplete updater target.
|
than exposing an incomplete updater target.
|
||||||
|
|
||||||
The publisher check is an exact subject match, not a substring match. Before a
|
The optional signing acceptance fixture can still validate the complete local
|
||||||
production certificate is available, validate the complete local chain with:
|
Authenticode chain without purchasing or retaining a certificate:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
npm run test:signing
|
npm run test:signing
|
||||||
@@ -95,5 +50,5 @@ stand-ins, requires an RFC 3161 timestamp, and proves rejection of a missing
|
|||||||
timestamp, wrong publisher and a modified binary. Its certificate is removed
|
timestamp, wrong publisher and a modified binary. Its certificate is removed
|
||||||
from the current-user certificate store after the test.
|
from the current-user certificate store after the test.
|
||||||
|
|
||||||
The production Authenticode certificate and exact legal publisher identity are
|
The disposable test certificate is removed from the current-user certificate
|
||||||
`PENDING_HUMAN_INPUT`; all surrounding build and verification code is complete.
|
store after the test and is never used for a published build.
|
||||||
|
|||||||
+1
-1
@@ -94,7 +94,7 @@ health verification, diagnostic correlation and no arbitrary shell input.
|
|||||||
|
|
||||||
- Windows installer/portable acceptance;
|
- Windows installer/portable acceptance;
|
||||||
- macOS/Linux package validation;
|
- macOS/Linux package validation;
|
||||||
- code signing, notarization and signed updates;
|
- optional code signing/notarization for future public distribution;
|
||||||
- dependency/secret/package scans;
|
- dependency/secret/package scans;
|
||||||
- accessibility review;
|
- accessibility review;
|
||||||
- hundreds-of-repositories performance tests;
|
- hundreds-of-repositories performance tests;
|
||||||
|
|||||||
+2
-2
@@ -108,9 +108,9 @@ included model uses:
|
|||||||
- atomic non-secret status JSON;
|
- atomic non-secret status JSON;
|
||||||
- previous-SHA recording and non-zero failure exits.
|
- previous-SHA recording and non-zero failure exits.
|
||||||
|
|
||||||
## Remaining release hardening
|
## Optional and future release hardening
|
||||||
|
|
||||||
- code-sign packages and signed updates;
|
- optional code signing if ForgeFlow is ever distributed publicly;
|
||||||
- validate private CA/TLS behavior in the target network;
|
- validate private CA/TLS behavior in the target network;
|
||||||
- dependency, secret and binary scans in CI;
|
- dependency, secret and binary scans in CI;
|
||||||
- package-level IPC/navigation regression tests;
|
- package-level IPC/navigation regression tests;
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
"coverage": "c8 --check-coverage --lines 75 --functions 75 --branches 65 --statements 75 node --test tests/*.test.mjs",
|
"coverage": "c8 --check-coverage --lines 75 --functions 75 --branches 65 --statements 75 node --test tests/*.test.mjs",
|
||||||
"verify": "node scripts/verify.mjs",
|
"verify": "node scripts/verify.mjs",
|
||||||
"dist:win": "electron-builder --win nsis portable && node scripts/write-release-checksums.mjs && node scripts/verify-release-signatures.mjs && node scripts/prune-dist.mjs",
|
"dist:win": "electron-builder --win nsis portable && node scripts/write-release-checksums.mjs && node scripts/verify-release-signatures.mjs && node scripts/prune-dist.mjs",
|
||||||
"dist:win:signed": "node scripts/validate-signing-environment.mjs && electron-builder --config scripts/signed-electron-builder-config.cjs --win nsis portable && node scripts/write-release-checksums.mjs && node scripts/verify-release-signatures.mjs && node scripts/prune-dist.mjs",
|
|
||||||
"dist:linux": "electron-builder --linux AppImage && node scripts/prune-dist.mjs",
|
"dist:linux": "electron-builder --linux AppImage && node scripts/prune-dist.mjs",
|
||||||
"dist:mac": "electron-builder --mac dmg && node scripts/prune-dist.mjs",
|
"dist:mac": "electron-builder --mac dmg && node scripts/prune-dist.mjs",
|
||||||
"doctor": "node scripts/doctor.mjs",
|
"doctor": "node scripts/doctor.mjs",
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
const pkg = require("../package.json");
|
|
||||||
|
|
||||||
const expectedPublisher = String(process.env.FORGEFLOW_EXPECTED_PUBLISHER || "").trim();
|
|
||||||
const commonName = expectedPublisher.match(/^CN=([^,]+)/i)?.[1]?.trim();
|
|
||||||
const useAzure = Boolean(String(process.env.FORGEFLOW_AZURE_SIGNING_ENDPOINT || "").trim());
|
|
||||||
const win = { ...pkg.build.win, forceCodeSigning: true };
|
|
||||||
|
|
||||||
if (useAzure) {
|
|
||||||
win.azureSignOptions = {
|
|
||||||
publisherName: commonName,
|
|
||||||
endpoint: process.env.FORGEFLOW_AZURE_SIGNING_ENDPOINT,
|
|
||||||
codeSigningAccountName: process.env.FORGEFLOW_AZURE_SIGNING_ACCOUNT,
|
|
||||||
certificateProfileName: process.env.FORGEFLOW_AZURE_CERTIFICATE_PROFILE,
|
|
||||||
fileDigest: "SHA256",
|
|
||||||
timestampDigest: "SHA256",
|
|
||||||
timestampRfc3161: "http://timestamp.acs.microsoft.com",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { ...pkg.build, win };
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
const signedRelease = process.env.FORGEFLOW_SIGNED_RELEASE === "1";
|
|
||||||
const publisher = String(process.env.FORGEFLOW_EXPECTED_PUBLISHER || "").trim();
|
|
||||||
const classicCertificate = String(process.env.WIN_CSC_LINK || process.env.CSC_LINK || "").trim();
|
|
||||||
const azureNames = ["AZURE_TENANT_ID", "AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET", "FORGEFLOW_AZURE_SIGNING_ENDPOINT", "FORGEFLOW_AZURE_SIGNING_ACCOUNT", "FORGEFLOW_AZURE_CERTIFICATE_PROFILE"];
|
|
||||||
const azureValues = azureNames.map((name) => String(process.env[name] || "").trim());
|
|
||||||
const azure = azureValues.every(Boolean);
|
|
||||||
const partialAzure = azureValues.some(Boolean) && !azure;
|
|
||||||
|
|
||||||
if (!signedRelease) throw new Error("FORGEFLOW_SIGNED_RELEASE=1 is required for the production signing build.");
|
|
||||||
if (!/^CN=.+/i.test(publisher)) throw new Error("FORGEFLOW_EXPECTED_PUBLISHER must be the exact certificate subject beginning with CN=.");
|
|
||||||
if (partialAzure) throw new Error(`Azure Artifact Signing is incomplete. Configure: ${azureNames.filter((_, index) => !azureValues[index]).join(", ")}.`);
|
|
||||||
if (!classicCertificate && !azure) throw new Error("Configure WIN_CSC_LINK/CSC_LINK or all Azure Artifact Signing credentials before building a signed release.");
|
|
||||||
if (classicCertificate && !String(process.env.WIN_CSC_KEY_PASSWORD || process.env.CSC_KEY_PASSWORD || "").trim()) {
|
|
||||||
throw new Error("WIN_CSC_KEY_PASSWORD or CSC_KEY_PASSWORD is required for classic certificate signing.");
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Production ${azure ? "Azure Artifact Signing" : "classic certificate"} environment accepted for exact publisher ${publisher}.`);
|
|
||||||
@@ -19,5 +19,5 @@ for (const artifact of artifacts) {
|
|||||||
const valid = result.Status === "Valid" && Boolean(result.TimestampSubject);
|
const valid = result.Status === "Valid" && Boolean(result.TimestampSubject);
|
||||||
const publisherMatches = !expectedPublisher || String(result.Subject || "").trim() === expectedPublisher;
|
const publisherMatches = !expectedPublisher || String(result.Subject || "").trim() === expectedPublisher;
|
||||||
if (signedRelease && (!valid || !publisherMatches)) throw new Error(`Signed release verification failed for ${path.basename(artifact)}: status=${result.Status}, publisher=${result.Subject || "missing"}, timestamp=${result.TimestampSubject || "missing"}.`);
|
if (signedRelease && (!valid || !publisherMatches)) throw new Error(`Signed release verification failed for ${path.basename(artifact)}: status=${result.Status}, publisher=${result.Subject || "missing"}, timestamp=${result.TimestampSubject || "missing"}.`);
|
||||||
console.log(`${path.basename(artifact)}: ${valid && publisherMatches ? "valid signed artifact" : "unsigned development artifact"}`);
|
console.log(`${path.basename(artifact)}: ${valid && publisherMatches ? "valid signed artifact" : "checksum-protected unsigned artifact"}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -507,7 +507,7 @@ test("packaged updater rejects a binary whose checksum does not match", async ()
|
|||||||
await rm(temp, { recursive: true, force: true });
|
await rm(temp, { recursive: true, force: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Windows release pipeline fails closed on signatures and emits provenance plus SBOM", async () => {
|
test("Windows release pipeline preserves optional signing checks and emits provenance plus SBOM", async () => {
|
||||||
const [pkgSource, signatureSource, checksumSource] = await Promise.all([
|
const [pkgSource, signatureSource, checksumSource] = await Promise.all([
|
||||||
readFile(new URL("../package.json", import.meta.url), "utf8"),
|
readFile(new URL("../package.json", import.meta.url), "utf8"),
|
||||||
readFile(new URL("../scripts/verify-release-signatures.mjs", import.meta.url), "utf8"),
|
readFile(new URL("../scripts/verify-release-signatures.mjs", import.meta.url), "utf8"),
|
||||||
@@ -528,20 +528,17 @@ test("Windows release pipeline fails closed on signatures and emits provenance p
|
|||||||
assert.match(publisher, /sbom\.cdx\.json/);
|
assert.match(publisher, /sbom\.cdx\.json/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("production signing build supports classic and Azure identities but always fails closed", async () => {
|
test("the supported Windows build is free, checksum-protected and updater-compatible", async () => {
|
||||||
const [pkg, validator, signedConfig] = await Promise.all([
|
const [pkg, signatureCheck, checksumWriter] = await Promise.all([
|
||||||
readFile(new URL("../package.json", import.meta.url), "utf8"),
|
readFile(new URL("../package.json", import.meta.url), "utf8"),
|
||||||
readFile(new URL("../scripts/validate-signing-environment.mjs", import.meta.url), "utf8"),
|
readFile(new URL("../scripts/verify-release-signatures.mjs", import.meta.url), "utf8"),
|
||||||
readFile(new URL("../scripts/signed-electron-builder-config.cjs", import.meta.url), "utf8"),
|
readFile(new URL("../scripts/write-release-checksums.mjs", import.meta.url), "utf8"),
|
||||||
]);
|
]);
|
||||||
assert.match(pkg, /dist:win:signed/);
|
assert.doesNotMatch(pkg, /dist:win:signed/);
|
||||||
assert.match(validator, /FORGEFLOW_SIGNED_RELEASE/);
|
assert.match(pkg, /dist:win/);
|
||||||
assert.match(validator, /WIN_CSC_LINK/);
|
assert.match(pkg, /write-release-checksums\.mjs/);
|
||||||
assert.match(validator, /FORGEFLOW_AZURE_CERTIFICATE_PROFILE/);
|
assert.match(signatureCheck, /checksum-protected unsigned artifact/);
|
||||||
assert.match(validator, /exact certificate subject/);
|
assert.match(checksumWriter, /sha256/);
|
||||||
assert.match(signedConfig, /forceCodeSigning:\s*true/);
|
|
||||||
assert.match(signedConfig, /azureSignOptions/);
|
|
||||||
assert.match(signedConfig, /timestamp\.acs\.microsoft\.com/);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("binary update helper verifies, waits, applies and records restart state", async () => {
|
test("binary update helper verifies, waits, applies and records restart state", async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user