diff --git a/.github/release-notes/v2.2.1.md b/.github/release-notes/v2.2.1.md new file mode 100644 index 0000000..ea492c7 --- /dev/null +++ b/.github/release-notes/v2.2.1.md @@ -0,0 +1,22 @@ +# BadBuilderLinux v2.2.1 + +Maintenance release incorporating upstream BadBuilder changes through September 14, 2026, while preserving BadBuilderLinux's Linux-first design and security hardening. + +## Changes + +- **Windows UAC:** Include upstream's requireAdministrator manifest in the self-contained Windows x64 executable so launching it normally requests elevation. The explicit Windows administrator check remains in place. +- **Linux remains unprivileged:** The Linux application still runs as a normal user and requests sudo only for the narrowly scoped USB-disk preparation/cleanup helper. +- **Upstream integration:** Incorporate upstream's September changes into Git history. The fork already implemented submenu **Back** navigation, runtime-derived version display, and ABadAvatar 1.3 Beta support; those hardened implementations remain in place rather than being replaced by upstream's less constrained equivalents. +- **ABadAvatar 1.3 Beta:** Continue pinning the upstream beta asset to its exact release, filename, and SHA-256. It remains a beta; this release does **not** upgrade it to a new exploit version. + +## Downloads + +- BadBuilder-linux-x64 — self-contained Linux x64 executable +- BadBuilder-win-x64.exe — self-contained Windows x64 executable +- SHA256SUMS — SHA-256 checksums for both executables + +Linux users may need to run chmod +x BadBuilder-linux-x64. + +The automated release workflow builds and tests both operating systems and checks the executable version before publishing. Manual physical USB testing is not part of the GitHub Actions suite. + +**Warning:** BadBuilder formats and erases the selected USB disk. Verify its exact device path before confirming. The ABadAvatar 1.3 Beta release advises disconnecting the Xbox 360 from the Internet before running the exploit. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..dfca785 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,150 @@ +name: Publish release + +on: + push: + branches: [main] + paths: + - BadBuilder/BadBuilder.csproj + - .github/workflows/release.yml + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: badbuilder-release-main + cancel-in-progress: false + +jobs: + prepare: + runs-on: ubuntu-latest + outputs: + tag: ${{ steps.version.outputs.tag }} + version: ${{ steps.version.outputs.version }} + needed: ${{ steps.version.outputs.needed }} + steps: + - uses: actions/checkout@v7 + - name: Determine next release + id: version + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + version="$(sed -n 's/^[[:space:]]*\([^<]*\)<\/VersionPrefix>.*/\1/p' BadBuilder/BadBuilder.csproj)" + if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Unexpected VersionPrefix: $version" >&2 + exit 1 + fi + tag="v$version" + echo "tag=$tag" >> "$GITHUB_OUTPUT" + echo "version=$version" >> "$GITHUB_OUTPUT" + if gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + echo "Release $tag already exists; no publication needed." + echo "needed=false" >> "$GITHUB_OUTPUT" + else + test -f ".github/release-notes/$tag.md" || { echo "Missing release notes for $tag" >&2; exit 1; } + echo "needed=true" >> "$GITHUB_OUTPUT" + fi + + build: + needs: prepare + if: needs.prepare.outputs.needed == 'true' + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + rid: linux-x64 + binary: BadBuilder + - os: windows-latest + rid: win-x64 + binary: BadBuilder.exe + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-dotnet@v6 + with: + dotnet-version: 10.0.x + cache: true + cache-dependency-path: '**/*.csproj' + - name: Restore + run: dotnet restore BadBuilder.Tests/BadBuilder.Tests.csproj + - name: Audit dependencies + run: dotnet list BadBuilder/BadBuilder.csproj package --vulnerable --include-transitive + - name: Build with warnings as errors + run: dotnet build BadBuilder.Tests/BadBuilder.Tests.csproj -c Release --no-restore + - name: Run unit tests + run: dotnet test BadBuilder.Tests/BadBuilder.Tests.csproj -c Release --no-build --no-restore + - name: Publish self-contained executable + run: dotnet publish BadBuilder/BadBuilder.csproj -c Release -r ${{ matrix.rid }} --self-contained true --no-restore -o dist + - name: Validate Linux executable version + if: runner.os == 'Linux' + shell: bash + run: | + set -euo pipefail + actual="$(./dist/BadBuilder --version)" + [[ "$actual" == "${{ needs.prepare.outputs.version }}"* ]] || { echo "Unexpected version: $actual"; exit 1; } + file dist/BadBuilder | grep -q ELF + - name: Validate Windows executable version + if: runner.os == 'Windows' + shell: pwsh + run: | + $actual = (./dist/BadBuilder.exe --version).Trim() + if (-not $actual.StartsWith('${{ needs.prepare.outputs.version }}')) { + throw "Unexpected version: $actual" + } + $bytes = [System.IO.File]::ReadAllBytes((Resolve-Path ./dist/BadBuilder.exe)) + if ($bytes[0] -ne 77 -or $bytes[1] -ne 90) { + throw "Windows executable is missing the MZ header" + } + - uses: actions/upload-artifact@v7 + with: + name: release-${{ matrix.rid }} + path: dist/${{ matrix.binary }} + if-no-files-found: error + compression-level: 0 + + publish: + needs: [prepare, build] + if: needs.prepare.outputs.needed == 'true' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v7 + - uses: actions/download-artifact@v7 + with: + name: release-linux-x64 + path: release/linux + - uses: actions/download-artifact@v7 + with: + name: release-win-x64 + path: release/windows + - name: Assemble verified release assets + shell: bash + run: | + set -euo pipefail + mv release/linux/BadBuilder release/BadBuilder-linux-x64 + mv release/windows/BadBuilder.exe release/BadBuilder-win-x64.exe + chmod +x release/BadBuilder-linux-x64 + file release/BadBuilder-linux-x64 | grep -q ELF + file release/BadBuilder-win-x64.exe | grep -q PE32 + cd release + sha256sum BadBuilder-linux-x64 BadBuilder-win-x64.exe > SHA256SUMS + sha256sum -c SHA256SUMS + - name: Create GitHub release + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ needs.prepare.outputs.tag }} + shell: bash + run: | + set -euo pipefail + gh release create "$TAG" \ + release/BadBuilder-linux-x64 \ + release/BadBuilder-win-x64.exe \ + release/SHA256SUMS \ + --repo "$GITHUB_REPOSITORY" \ + --target "$GITHUB_SHA" \ + --title "BadBuilderLinux $TAG" \ + --notes-file ".github/release-notes/$TAG.md" diff --git a/BadBuilder/BadBuilder.csproj b/BadBuilder/BadBuilder.csproj index dfc4f3e..c5ecdae 100644 --- a/BadBuilder/BadBuilder.csproj +++ b/BadBuilder/BadBuilder.csproj @@ -12,7 +12,7 @@ all low latest-recommended - 2.2.0 + 2.2.1 win-x64;linux-x64 true true diff --git a/README.md b/README.md index 05f74d0..2b2b6ae 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,10 @@ Two destructive tests are opt-in and never run in normal CI: Set `BADBUILDER_LIVE_SMOKE=1` to resolve all upstream catalog sources without downloading an asset or touching a disk. +## Publishing a release + +Releases are published by GitHub Actions after the VersionPrefix in BadBuilder/BadBuilder.csproj is increased on main. Add the corresponding release notes at .github/release-notes/vX.Y.Z.md before merging. The release.yml workflow builds and tests both supported x64 platforms, verifies the version strings and native binary formats, generates SHA-256 checksums, then creates the matching version tag and GitHub release. An already-published version is skipped. To retry a failed release, rerun the workflow from GitHub Actions. + ## Offline archives If release metadata is unavailable and no valid cache exists, BadBuilder asks for a local archive for every configured artifact. No configured built-in homebrew is silently skipped. Local files go through the same checksum, approval, extraction, layout, and preflight checks as downloads.