From 87c0547090d96285cbc8dff9989c0c224603fb7b Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Wed, 15 Jul 2026 07:15:34 +0200 Subject: [PATCH] ci: harden pull request workflows --- .github/workflows/ci.yml | 43 ++++++++++------ .github/workflows/e2e.yml | 105 ++++++++++++++++++++++++++++++++------ 2 files changed, 115 insertions(+), 33 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6abbd6a..4f6a974e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,7 @@ on: push: branches: - main + # Keep untrusted code on the read-only pull_request event. Never use pull_request_target here. pull_request: branches: - main @@ -17,10 +18,12 @@ jobs: contents: read steps: - name: Checkout code - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Setup Flutter - uses: subosito/flutter-action@v2 + uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2 with: channel: "stable" flutter-version: "3.44.0" @@ -28,7 +31,7 @@ jobs: pub-cache: false - name: Cache Pub dependencies - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: | ~/.pub-cache @@ -101,10 +104,12 @@ jobs: contents: read steps: - name: Checkout code - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Setup Flutter - uses: subosito/flutter-action@v2 + uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2 with: channel: "stable" flutter-version: "3.44.0" @@ -112,7 +117,7 @@ jobs: pub-cache: false - name: Cache Pub dependencies - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: | ~/.pub-cache @@ -138,16 +143,18 @@ jobs: contents: read steps: - name: Checkout code - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Setup Java - uses: actions/setup-java@v5 + uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5 with: distribution: "temurin" java-version: "17" - name: Setup Flutter - uses: subosito/flutter-action@v2 + uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2 with: channel: "stable" flutter-version: "3.44.0" @@ -155,14 +162,14 @@ jobs: pub-cache: false - name: Cache Pub dependencies - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: | ~/.pub-cache key: ${{ runner.os }}-pub-v3-${{ hashFiles('**/pubspec.yaml', '**/pubspec.lock') }} - name: Cache Gradle - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: | ~/.gradle/caches @@ -192,10 +199,12 @@ jobs: contents: read steps: - name: Checkout code - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Setup Java - uses: actions/setup-java@v5 + uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5 with: distribution: "temurin" java-version: "17" @@ -210,10 +219,12 @@ jobs: contents: read steps: - name: Checkout code - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Setup Flutter - uses: subosito/flutter-action@v2 + uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2 with: channel: "stable" flutter-version: "3.44.0" @@ -221,7 +232,7 @@ jobs: pub-cache: false - name: Cache Pub dependencies - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: | ~/.pub-cache diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index f19e810d..0f954436 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -4,6 +4,7 @@ on: push: branches: - main + # Keep untrusted code on the read-only pull_request event. Never use pull_request_target here. pull_request: branches: - main @@ -23,11 +24,14 @@ jobs: env: FLUTTER_VERSION: "3.44.0" MAESTRO_VERSION: "2.6.1" + MAESTRO_SHA256: "3440825f514f537c6a96bcf5de995780c2a4a7f83a43208fdc95d4f1fecfad3b" MAESTRO_CLI_NO_ANALYTICS: "1" steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Enable KVM run: | @@ -37,13 +41,13 @@ jobs: sudo udevadm trigger --name-match=kvm - name: Setup Java - uses: actions/setup-java@v4 + uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5 with: distribution: temurin java-version: "17" - name: Setup Flutter - uses: subosito/flutter-action@v2 + uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2 with: channel: stable flutter-version: ${{ env.FLUTTER_VERSION }} @@ -51,13 +55,15 @@ jobs: pub-cache: false - name: Cache Pub dependencies - uses: actions/cache@v4 + id: pub-cache + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: ~/.pub-cache key: ${{ runner.os }}-pub-v3-${{ hashFiles('**/pubspec.yaml', '**/pubspec.lock') }} - name: Cache Gradle - uses: actions/cache@v4 + id: gradle-cache + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: | ~/.gradle/caches @@ -75,16 +81,27 @@ jobs: - name: Cache Maestro CLI id: maestro-cache - uses: actions/cache@v4 + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: | ~/.maestro/bin ~/.maestro/lib key: ${{ runner.os }}-maestro-${{ env.MAESTRO_VERSION }} - - name: Install Maestro CLI + - name: Install verified Maestro CLI if: steps.maestro-cache.outputs.cache-hit != 'true' - run: curl -fsSL "https://get.maestro.mobile.dev" | bash + run: | + archive="$RUNNER_TEMP/maestro.zip" + staging="$RUNNER_TEMP/maestro-install" + curl --fail --location --retry 3 \ + --output "$archive" \ + "https://github.com/mobile-dev-inc/maestro/releases/download/cli-${MAESTRO_VERSION}/maestro.zip" + echo "${MAESTRO_SHA256} ${archive}" | sha256sum --check - + rm -rf "$staging" "$HOME/.maestro/bin" "$HOME/.maestro/lib" + mkdir -p "$staging" "$HOME/.maestro" + unzip -q "$archive" -d "$staging" + mv "$staging/maestro/bin" "$HOME/.maestro/bin" + mv "$staging/maestro/lib" "$HOME/.maestro/lib" - name: Add Maestro to PATH run: | @@ -92,10 +109,21 @@ jobs: "$HOME/.maestro/bin/maestro" --version - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 + + - name: Build deterministic Jellyfin image for pull request + if: github.event_name == 'pull_request' + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 + with: + context: . + file: .maestro/jellyfin-demo/Dockerfile + load: true + tags: plezy-jellyfin-demo:local + cache-from: type=gha,scope=maestro-jellyfin - name: Build and cache deterministic Jellyfin image - uses: docker/build-push-action@v7 + if: github.event_name != 'pull_request' + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 with: context: . file: .maestro/jellyfin-demo/Dockerfile @@ -106,7 +134,7 @@ jobs: - name: Cache Android 15 AVD id: api35-avd-cache - uses: actions/cache@v4 + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: | ~/.android/avd/maestro-api35.avd @@ -115,7 +143,7 @@ jobs: - name: Generate Android 15 AVD snapshot if: steps.api35-avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 + uses: reactivecircus/android-emulator-runner@4c44018e59b437e86cdfc41da381398f93ed8808 # v2 with: api-level: 35 arch: x86_64 @@ -130,7 +158,7 @@ jobs: - name: Cache Android 9 AVD id: api28-avd-cache - uses: actions/cache@v4 + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: | ~/.android/avd/maestro-api28.avd @@ -139,7 +167,7 @@ jobs: - name: Generate Android 9 AVD snapshot if: steps.api28-avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 + uses: reactivecircus/android-emulator-runner@4c44018e59b437e86cdfc41da381398f93ed8808 # v2 with: api-level: 28 target: google_apis_playstore @@ -156,7 +184,7 @@ jobs: - name: Run Android 15 suites id: api35 continue-on-error: true - uses: reactivecircus/android-emulator-runner@v2 + uses: reactivecircus/android-emulator-runner@4c44018e59b437e86cdfc41da381398f93ed8808 # v2 env: MAESTRO_SKIP_BUILD: "1" MAESTRO_SKIP_JELLYFIN_BUILD: "1" @@ -209,7 +237,7 @@ jobs: - name: Run Android 9 legacy playback id: api28 continue-on-error: true - uses: reactivecircus/android-emulator-runner@v2 + uses: reactivecircus/android-emulator-runner@4c44018e59b437e86cdfc41da381398f93ed8808 # v2 env: MAESTRO_SKIP_BUILD: "1" MAESTRO_SKIP_JELLYFIN_BUILD: "1" @@ -230,10 +258,53 @@ jobs: --flow .maestro/flows/05_playback.yaml \ --jellyfin-log build/maestro-legacy/jellyfin.log \ --diagnostics-dir build/maestro-legacy/diagnostics + # Fork PRs may restore trusted caches, but only trusted events publish cache entries. + - name: Save Pub dependency cache + if: github.event_name != 'pull_request' && steps.pub-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + with: + path: ~/.pub-cache + key: ${{ runner.os }}-pub-v3-${{ hashFiles('**/pubspec.yaml', '**/pubspec.lock') }} + + - name: Save Gradle cache + if: github.event_name != 'pull_request' && steps.gradle-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-e2e-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + + - name: Save Maestro CLI cache + if: github.event_name != 'pull_request' && steps.maestro-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + with: + path: | + ~/.maestro/bin + ~/.maestro/lib + key: ${{ runner.os }}-maestro-${{ env.MAESTRO_VERSION }} + + - name: Save Android 15 AVD cache + if: github.event_name != 'pull_request' && steps.api35-avd-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + with: + path: | + ~/.android/avd/maestro-api35.avd + ~/.android/avd/maestro-api35.ini + key: ${{ runner.os }}-avd-v1-api35-x86_64-pixel_6 + + - name: Save Android 9 AVD cache + if: github.event_name != 'pull_request' && steps.api28-avd-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + with: + path: | + ~/.android/avd/maestro-api28.avd + ~/.android/avd/maestro-api28.ini + key: ${{ runner.os }}-avd-v1-api28-x86-pixel_2-playstore - name: Upload Maestro diagnostics if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: maestro-android-diagnostics path: |