Skip to content

Commit 7b1ce34

Browse files
fix(release): make Windows recovery fail closed
1 parent cd5ad52 commit 7b1ce34

2 files changed

Lines changed: 40 additions & 2 deletions

File tree

.github/workflows/windows-node-release.yml

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ jobs:
4040
if ($env:RELEASE_TAG -notmatch '^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-(alpha|beta)\.[1-9][0-9]*)|(-[1-9][0-9]*))?$') {
4141
throw "Invalid OpenClaw release tag: $env:RELEASE_TAG"
4242
}
43+
$stableRelease = -not (
44+
$env:RELEASE_TAG.Contains("-alpha.") -or
45+
$env:RELEASE_TAG.Contains("-beta.")
46+
)
4347
if ($env:WINDOWS_NODE_TAG -notmatch '^v[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z]+([.-][0-9A-Za-z]+)*)?$') {
4448
throw "windows_node_tag must be an explicit openclaw-windows-node release tag, not latest: $env:WINDOWS_NODE_TAG"
4549
}
@@ -54,6 +58,10 @@ jobs:
5458
"OpenClawCompanion-Setup-x64.exe",
5559
"OpenClawCompanion-Setup-arm64.exe"
5660
)
61+
$allowedTargetCompanionAssetNames = @(
62+
$requiredInstallerNames
63+
"OpenClawCompanion-SHA256SUMS.txt"
64+
)
5765
if ($expectedDigests.Count -ne $requiredInstallerNames.Count) {
5866
throw "expected_installer_digests must contain exactly the current installer asset contract."
5967
}
@@ -64,10 +72,22 @@ jobs:
6472
}
6573
}
6674
67-
$targetRelease = gh release view $env:RELEASE_TAG --repo $env:GITHUB_REPOSITORY --json tagName,isDraft,isPrerelease,url | ConvertFrom-Json
75+
$targetRelease = gh release view $env:RELEASE_TAG --repo $env:GITHUB_REPOSITORY --json tagName,isDraft,isPrerelease,assets,url | ConvertFrom-Json
6876
if ($targetRelease.tagName -ne $env:RELEASE_TAG) {
6977
throw "OpenClaw release tag mismatch: expected $env:RELEASE_TAG, got $($targetRelease.tagName)"
7078
}
79+
$unexpectedTargetCompanionAssets = @(
80+
$targetRelease.assets |
81+
Where-Object {
82+
$_.name.StartsWith("OpenClawCompanion-") -and
83+
$_.name -notin $allowedTargetCompanionAssetNames
84+
} |
85+
ForEach-Object name |
86+
Sort-Object
87+
)
88+
if ($unexpectedTargetCompanionAssets.Count -ne 0) {
89+
throw "Target OpenClaw release contains unexpected OpenClawCompanion assets before upload: $($unexpectedTargetCompanionAssets -join ', ')"
90+
}
7191
7292
$sourceRelease = gh release view $env:WINDOWS_NODE_TAG --repo openclaw/openclaw-windows-node --json tagName,isDraft,isPrerelease,url | ConvertFrom-Json
7393
if ($sourceRelease.tagName -ne $env:WINDOWS_NODE_TAG) {
@@ -76,7 +96,7 @@ jobs:
7696
if ($sourceRelease.isDraft) {
7797
throw "Windows source release must be published: $($sourceRelease.url)"
7898
}
79-
if (-not $targetRelease.isPrerelease -and $sourceRelease.isPrerelease) {
99+
if ($stableRelease -and $sourceRelease.isPrerelease) {
80100
throw "Stable OpenClaw releases require a non-prerelease Windows source release: $($sourceRelease.url)"
81101
}
82102

test/scripts/package-acceptance-workflow.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1656,6 +1656,24 @@ describe("package artifact reuse", () => {
16561656
expect(releaseWorkflow).toContain('else error("malformed Windows checksum manifest entry")');
16571657
});
16581658

1659+
it("rejects unsafe direct Windows recovery before uploading assets", () => {
1660+
const windowsWorkflow = readFileSync(WINDOWS_NODE_RELEASE_WORKFLOW, "utf8");
1661+
const classifyStableReleaseIndex = windowsWorkflow.indexOf("$stableRelease = -not (");
1662+
const rejectPrereleaseSourceIndex = windowsWorkflow.indexOf(
1663+
"if ($stableRelease -and $sourceRelease.isPrerelease)",
1664+
);
1665+
const rejectUnexpectedTargetAssetsIndex = windowsWorkflow.indexOf(
1666+
"Target OpenClaw release contains unexpected OpenClawCompanion assets before upload",
1667+
);
1668+
const uploadAssetsIndex = windowsWorkflow.indexOf("gh release upload $env:RELEASE_TAG");
1669+
1670+
expect(classifyStableReleaseIndex).toBeGreaterThan(-1);
1671+
expect(rejectPrereleaseSourceIndex).toBeGreaterThan(classifyStableReleaseIndex);
1672+
expect(windowsWorkflow).not.toContain("-not $targetRelease.isPrerelease");
1673+
expect(rejectUnexpectedTargetAssetsIndex).toBeGreaterThan(-1);
1674+
expect(uploadAssetsIndex).toBeGreaterThan(rejectUnexpectedTargetAssetsIndex);
1675+
});
1676+
16591677
it("keeps beta release verification and ClawHub publish repair hooks wired", () => {
16601678
const packageJson = JSON.parse(readFileSync("package.json", "utf8")) as {
16611679
scripts?: Record<string, string>;

0 commit comments

Comments
 (0)