Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/scripts/cask-pr-handoff-workflow.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,40 @@ assert_not_contains() {
fi
}

# Run the real loop up to PR discovery. An invalid published Desktop archive must
# never reach either the open-PR handoff or the already-merged recovery path, while
# the independently verified CLI cask remains deliverable.
archive_gate="${tmp_dir}/archive-gate.sh"
awk '
/^ for name in ksail ksail-desktop; do/ { in_loop = 1 }
in_loop && /^ matches=/ { exit }
in_loop { line = $0; sub(/^ /, "", line); print line }
END { print " printf '\''delivered:%s\\n'\'' \"$name\""; print "done" }
' "${homebrew_block}" >"${archive_gate}"
gate_root="${tmp_dir}/archive-gate"
mkdir -p "${gate_root}/.github/scripts"
cat >"${gate_root}/.github/scripts/verify-published-desktop-archive.sh" <<'EOF'
#!/usr/bin/env bash
printf 'BLOCKED: published Desktop bundle has an invalid resource seal\n' >&2
exit 1
EOF
chmod +x "${gate_root}/.github/scripts/verify-published-desktop-archive.sh"
(
cd "${gate_root}"
TAG=v7.212.40 TAP=devantler-tech/homebrew-tap handoff_failed=0
GITHUB_STEP_SUMMARY="${tmp_dir}/archive-gate-summary"
# shellcheck source=/dev/null
source "${archive_gate}"
[[ "${handoff_failed}" -eq 1 ]] || fail 'invalid Desktop archive must make the handoff fail'
) >"${tmp_dir}/archive-gate.log" 2>&1 || {
cat "${tmp_dir}/archive-gate.log" >&2
fail 'invalid published Desktop archive reached cask handoff'
}
grep -Fxq 'delivered:ksail' "${tmp_dir}/archive-gate.log" || fail 'Desktop rejection blocked the CLI cask'
if grep -Fxq 'delivered:ksail-desktop' "${tmp_dir}/archive-gate.log"; then
fail 'invalid published Desktop archive reached open or merged PR recovery'
fi

race_root="${tmp_dir}/merged-race"
mkdir -p "${race_root}/.github/scripts" "${race_root}/evidence"
cat >"${race_root}/.github/scripts/find-merged-cask-pr-handoff.sh" <<'EOF'
Expand Down
44 changes: 44 additions & 0 deletions .github/scripts/verify-published-desktop-archive.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Verify the immutable Desktop download before delivering or recovering its cask.
set -euo pipefail

if [[ $# != 1 || ! "$1" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([+-][0-9A-Za-z.-]+)?$ ]]; then
printf 'Usage: verify-published-desktop-archive.sh TAG\n' >&2
exit 2
fi

tag="$1"
asset="KSail_${tag#v}_darwin_arm64.zip"
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd -- "${script_dir}/../.." && pwd)"
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
export GH_TOKEN="${KSAIL_CASK_READ_TOKEN:-${GH_TOKEN:-}}"

"${script_dir}/cask-read-api.sh" "repos/devantler-tech/ksail/releases/tags/${tag}" >"${work}/release.json"
if ! digest="$(jq -er --arg tag "$tag" --arg asset "$asset" '
select(.tag_name == $tag and .draft == false and .immutable == true)
| select(.published_at | type == "string" and length > 0)
| [.assets[] | select(.name == $asset)]
| select(length == 1) | .[0]
| select(.size | type == "number" and . > 0 and floor == .)
| .digest | select(type == "string" and test("^sha256:[0-9a-f]{64}$"))
' "${work}/release.json")"; then
printf 'BLOCKED: immutable published Desktop asset evidence is incomplete\n' >&2
exit 1
fi

# The destination is fixed by the product and validated tag, never by response text.
archive="${work}/desktop.zip"
curl --fail --location --silent --show-error --max-time 180 \
--proto '=https' --proto-redir '=https' \
--output "$archive" \
"https://github.com/devantler-tech/ksail/releases/download/${tag}/${asset}"
actual="$(shasum -a 256 "$archive" | awk '{print $1}')"
if [[ "sha256:${actual}" != "$digest" ]]; then
printf 'BLOCKED: downloaded Desktop archive does not match its published digest\n' >&2
exit 1
fi

bash "${repo_root}/desktop/scripts/verify-macos-archive.sh" "$archive"
printf 'PASS: published Desktop archive %s has a valid resource seal (%s)\n' "$tag" "$actual"
79 changes: 79 additions & 0 deletions .github/scripts/verify-published-desktop-archive.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env bash
# Exercise publication recovery with real macOS resource seals and ZIP extraction.
set -euo pipefail

script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
[[ "$(uname -s)" == Darwin ]] || exit 2
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
mkdir -p "$work/KSail.app/Contents/MacOS" "$work/KSail.app/Contents/Resources" "$work/bin"
cp /usr/bin/true "$work/KSail.app/Contents/MacOS/KSail"
cat >"$work/KSail.app/Contents/Info.plist" <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0"><dict>
<key>CFBundleExecutable</key><string>KSail</string>
<key>CFBundleIdentifier</key><string>tech.devantler.ksail.signature-fixture</string>
<key>CFBundlePackageType</key><string>APPL</string>
</dict></plist>
EOF
printf 'Original license\n' >"$work/KSail.app/Contents/Resources/LICENSE"
codesign --force --sign - --timestamp=none "$work/KSail.app" 2>"$work/sign.log"
ditto -c -k --keepParent "$work/KSail.app" "$work/valid.zip"
printf 'Changed after signing\n' >"$work/KSail.app/Contents/Resources/LICENSE"
ditto -c -k --keepParent "$work/KSail.app" "$work/invalid.zip"

# Replace transport only; keep the real digest, extraction and signature checks.
cat >"$work/bin/gh" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
[[ "$*" == 'api repos/devantler-tech/ksail/releases/tags/v7.212.40' ]] || exit 2
cat "$RELEASE_FIXTURE"
EOF
cat >"$work/bin/curl" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
output="" url=""
while (($#)); do
case "$1" in
--output) output="$2"; shift 2 ;;
https://*) url="$1"; shift ;;
*) shift ;;
esac
done
[[ "$url" == https://github.com/devantler-tech/ksail/releases/download/v7.212.40/KSail_7.212.40_darwin_arm64.zip && -n "$output" ]] || exit 2
cp "$ARCHIVE_FIXTURE" "$output"
EOF
chmod +x "$work/bin/gh" "$work/bin/curl"
export PATH="$work/bin:$PATH"
export RELEASE_FIXTURE="$work/release.json" ARCHIVE_FIXTURE="$work/valid.zip"

# Describe the actual fixture publication without using production parsing code.
publication() {
local digest
digest="$(shasum -a 256 "$1" | awk '{print $1}')"
jq -n --arg digest "sha256:$digest" '{tag_name:"v7.212.40",draft:false,
immutable:true,published_at:"2026-11-11T00:00:00Z",
assets:[{name:"KSail_7.212.40_darwin_arm64.zip",size:1,digest:$digest}]}' >"$RELEASE_FIXTURE"
}

publication "$ARCHIVE_FIXTURE"
bash "${script_dir}/verify-published-desktop-archive.sh" v7.212.40 >"$work/valid.log" 2>&1
grep -q '^PASS: published Desktop archive' "$work/valid.log"
printf 'PASS: valid immutable publication\n'

ARCHIVE_FIXTURE="$work/invalid.zip"
if bash "${script_dir}/verify-published-desktop-archive.sh" v7.212.40 >"$work/digest.log" 2>&1; then
printf 'FAIL: download with a different checksum was accepted\n' >&2
exit 1
fi
grep -q 'does not match its published digest' "$work/digest.log"
printf 'PASS: changed download rejected\n'

# Even a checksum-matching publication must fail when its signed resources changed.
publication "$ARCHIVE_FIXTURE"
if bash "${script_dir}/verify-published-desktop-archive.sh" v7.212.40 >"$work/seal.log" 2>&1; then
printf 'FAIL: checksum-matching invalid resource seal was accepted\n' >&2
exit 1
fi
grep -q 'sealed resource is missing or invalid' "$work/seal.log"
printf 'PASS: checksum-matching invalid resource seal rejected\n'
7 changes: 7 additions & 0 deletions .github/workflows/cd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1065,6 +1065,13 @@ jobs:

for name in ksail ksail-desktop; do
branch="goreleaser/${name}"
# Check the actual immutable download before either open-PR delivery or
# merged-PR recovery. A checksum match alone cannot prove a valid seal.
if [[ "$name" == ksail-desktop ]] && ! .github/scripts/verify-published-desktop-archive.sh "$TAG"; then
echo "::error::Published Desktop archive failed signature verification; leaving its cask untouched"
handoff_failed=1
continue
fi
# `|| true` keeps the job fail-soft: a transient API failure (5xx, rate limit,
# network blip) must not abort the step under `set -e` after the release is already public —
# invalid evidence below then warns and skips, same as the no-PR-found case.
Expand Down
23 changes: 23 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,9 @@ jobs:
- 'pkg/svc/installer/cni/calico/Dockerfile'
- '.github/workflows/ci.yaml'
cask-pr-handoff:
- '.github/scripts/verify-published-desktop-archive.sh'
- '.github/scripts/verify-published-desktop-archive.test.sh'
- 'desktop/scripts/verify-macos-archive.sh'
- '.github/scripts/cask-read-api.sh'
- '.github/scripts/cask-read-api.test.sh'
- 'server.json'
Expand Down Expand Up @@ -725,6 +728,22 @@ jobs:
.github/actions/ksail-test-workload/remote-resource-guard.test.sh
.github/actions/ksail-test-workload/install-with-recovery.test.sh

desktop-recovery-signature:
name: 🔐 Verify Published Desktop Signature
runs-on: macos-latest
timeout-minutes: 5
needs: [changes]
if: needs.changes.outputs.cask-pr-handoff == 'true'
permissions:
contents: read
steps:
- name: 📄 Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Verify real resource seals in publication recovery
run: bash .github/scripts/verify-published-desktop-archive.test.sh

cask-pr-handoff:
name: 🔐 Validate Cask PR Handoff
runs-on: ubuntu-latest
Expand Down Expand Up @@ -763,6 +782,8 @@ jobs:
.github/scripts/upload-release-assets.test.sh \
.github/scripts/validate-cask-pr-handoff.sh \
.github/scripts/validate-cask-pr-handoff.test.sh \
.github/scripts/verify-published-desktop-archive.sh \
.github/scripts/verify-published-desktop-archive.test.sh \
.github/scripts/wait-for-github-api-budget.sh \
.github/scripts/wait-for-github-api-budget.test.sh \
.github/scripts/cask-pr-handoff-workflow.test.sh
Expand Down Expand Up @@ -3032,6 +3053,7 @@ jobs:
test-cask-rendering,
release-graph-audit,
cask-pr-handoff,
desktop-recovery-signature,
native-home-paths,
windows-notice-staging,
eks-smoke-scripts,
Expand Down Expand Up @@ -3079,6 +3101,7 @@ jobs:
${{ needs.test-cask-rendering.result }}
${{ needs.release-graph-audit.result }}
${{ needs.cask-pr-handoff.result }}
${{ needs.desktop-recovery-signature.result }}
${{ needs.native-home-paths.result }}
${{ needs.windows-notice-staging.result }}
${{ needs.eks-smoke-scripts.result }}
Expand Down
2 changes: 1 addition & 1 deletion pkg/client/kubescape/release_graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ func auditedReleaseConfigDigests() map[string]string {
".github/actions/release-admission/action.yaml": "a8ac217c7e2195fee64b364748a6724b99aa0aa4a9988127084f1d6b45953f0c",
"scripts/stage-webui.sh": "56f99977b9e5026eedd693e59da4e0cd82d0167e94d0ce4b9c7bb5ce2cd8ed3c",
".github/actions/free-disk-space/free-disk-space.sh": "2dd12fcf3779137ca1cb5f21194947f6a10d3f94f8fb438a1a29ec9021fbdc30",
".github/workflows/cd.yaml#without-uses": "04ab2010cd4425e47ccadbff1dbadf421e496120b3961dd72496bc0451bb4f1a",
".github/workflows/cd.yaml#without-uses": "7235813459ca01e063df01e523341533ed62c8f7c4c29428a08efbcff2f20206",
}
}

Expand Down
Loading