From a34a0054b98693b3c25530db80ee30f08446d42f Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Wed, 1 Jul 2026 19:23:07 +0200 Subject: [PATCH 1/3] ci: add zizmor GitHub Actions security scanning workflow Runs zizmor (zizmorcore/zizmor-action) on pushes and PRs to statically audit the workflows for CI/CD security issues (unpinned actions, excessive token permissions, cache poisoning, template injection, credential persistence). Uploads SARIF to code scanning on pushes and same-repo PRs; fork PRs get a read-only token, so the SARIF upload is skipped for them (they still receive inline annotations). Least privilege: empty top-level permissions; the job grants only security-events: write + contents: read. The action itself is pinned to a commit SHA. --- .github/workflows/zizmor.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/zizmor.yml diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 00000000..e9ba32c0 --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,28 @@ +name: zizmor - GitHub Actions Security Analysis + +on: + push: + pull_request: + +permissions: {} + +jobs: + zizmor: + name: Run zizmor + runs-on: ubuntu-latest + permissions: + security-events: write + contents: read + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + + - name: Run zizmor + uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 + with: + # Fork PRs get a read-only token (no security-events: write), so the + # SARIF upload would fail. Skip it for forks — they still get inline + # annotations; pushes and same-repo PRs upload to code scanning. + advanced-security: ${{ github.event.pull_request.head.repo.fork != true }} From 7960c9c4f468701a1a09a4ec8921f1acfdea5a36 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Wed, 1 Jul 2026 19:23:07 +0200 Subject: [PATCH 2/3] ci: harden ci.yml against zizmor findings Security hardening of the CI workflow with no functional change to what the jobs do: - Pin every action to a commit SHA (with a human-readable version comment) to defeat tag-mutation supply-chain attacks (unpinned-uses). - Add a least-privilege top-level `permissions: contents: read` (excessive-permissions). - Set `persist-credentials: false` on every checkout so the workflow token isn't left behind in the local git config (artipacked). - Pass the iOS simulator UDID through a step `env:` var instead of interpolating `steps.simulator.outputs.udid` straight into the run script (template-injection). - Gate every runtime cache on tag/release refs to avoid cache poisoning of release builds: `lookup-only`/`cache-disabled` become true on tags for the flet, AVD and Gradle caches, and the publish job's uv cache is disabled on tags via `enable-cache`. On branch/PR builds these evaluate to normal read-write caching, so CI performance is unchanged. setup-gradle stays on v5 (pinned to v5.0.2) per the existing licensing note; it is not bumped to v6. Verified locally with `zizmor` v1.26.1: no findings (default persona, online audits). --- .github/workflows/ci.yml | 95 ++++++++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61299b99..6ff86017 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,9 @@ env: # target python_version / platform, leaving the two halves mismatched. FLET_VERSION: "0.85.3" +permissions: + contents: read + jobs: version_tables_in_sync: name: Version tables in sync with manifest @@ -39,10 +42,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false - name: Setup Flutter - uses: kuhnroyal/flutter-fvm-config-action/setup@v3 + uses: kuhnroyal/flutter-fvm-config-action/setup@c378498f1d1962d33039c3989411093ef8a17b2c # v3.3 with: path: '.fvmrc' cache: true @@ -79,22 +84,26 @@ jobs: SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }} steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false - name: Setup Flutter - uses: kuhnroyal/flutter-fvm-config-action/setup@v3 + uses: kuhnroyal/flutter-fvm-config-action/setup@c378498f1d1962d33039c3989411093ef8a17b2c # v3.3 with: path: '.fvmrc' cache: true - name: Cache flet downloads - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: ~/.flet/cache key: flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}-${{ hashFiles('src/serious_python/lib/src/python_versions.dart', 'src/serious_python/bin/package_command.dart', 'src/serious_python_android/android/build.gradle.kts', 'src/serious_python_darwin/darwin/prepare_macos.sh', 'src/serious_python_darwin/darwin/prepare_ios.sh', 'src/serious_python_windows/windows/CMakeLists.txt', 'src/serious_python_linux/linux/CMakeLists.txt') }} restore-keys: | flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}- flet-cache-${{ runner.os }}-${{ runner.arch }}- + # Read-only on tag/release builds to avoid cache poisoning. + lookup-only: ${{ startsWith(github.ref, 'refs/tags/') }} - name: Configure ${{ matrix.build_system }} run: | @@ -143,26 +152,30 @@ jobs: SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }} steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false - name: Setup Flutter - uses: kuhnroyal/flutter-fvm-config-action/setup@v3 + uses: kuhnroyal/flutter-fvm-config-action/setup@c378498f1d1962d33039c3989411093ef8a17b2c # v3.3 with: path: '.fvmrc' cache: true - name: Cache flet downloads - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: ~/.flet/cache key: flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}-${{ hashFiles('src/serious_python/lib/src/python_versions.dart', 'src/serious_python/bin/package_command.dart', 'src/serious_python_android/android/build.gradle.kts', 'src/serious_python_darwin/darwin/prepare_macos.sh', 'src/serious_python_darwin/darwin/prepare_ios.sh', 'src/serious_python_windows/windows/CMakeLists.txt', 'src/serious_python_linux/linux/CMakeLists.txt') }} restore-keys: | flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}- flet-cache-${{ runner.os }}-${{ runner.arch }}- + # Read-only on tag/release builds to avoid cache poisoning. + lookup-only: ${{ startsWith(github.ref, 'refs/tags/') }} - name: Setup iOS Simulator id: simulator - uses: futureware-tech/simulator-action@v5 + uses: futureware-tech/simulator-action@e89aa8f93d3aec35083ff49d2854d07f7186f7f5 # v5 with: model: 'iPhone 17 Pro Max' os: "iOS" @@ -180,6 +193,10 @@ jobs: - name: Package + run integration test working-directory: "src/serious_python/example/bridge_example" + env: + # Pass the simulator UDID via env rather than interpolating the + # step output straight into the run script (template injection). + SIMULATOR_UDID: ${{ steps.simulator.outputs.udid }} run: | ts() { date '+%H:%M:%S'; } # SPM is the default; CocoaPods opts out (the SPM job leaves the var @@ -200,9 +217,9 @@ jobs: fi echo "[$(ts)] >>> flutter test integration_test (per-file)" # See macOS job for why each file runs as a separate invocation. - flutter test integration_test/interactivity_test.dart --device-id ${{ steps.simulator.outputs.udid }} --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} - flutter test integration_test/throughput_test.dart --device-id ${{ steps.simulator.outputs.udid }} --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} - flutter test integration_test/memory_test.dart --device-id ${{ steps.simulator.outputs.udid }} --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} + flutter test integration_test/interactivity_test.dart --device-id "$SIMULATOR_UDID" --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} + flutter test integration_test/throughput_test.dart --device-id "$SIMULATOR_UDID" --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} + flutter test integration_test/memory_test.dart --device-id "$SIMULATOR_UDID" --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} echo "[$(ts)] >>> done" bridge_example_android: @@ -219,22 +236,26 @@ jobs: SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }} steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false - name: Setup Flutter - uses: kuhnroyal/flutter-fvm-config-action/setup@v3 + uses: kuhnroyal/flutter-fvm-config-action/setup@c378498f1d1962d33039c3989411093ef8a17b2c # v3.3 with: path: '.fvmrc' cache: true - name: Cache flet downloads - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: ~/.flet/cache key: flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}-${{ hashFiles('src/serious_python/lib/src/python_versions.dart', 'src/serious_python/bin/package_command.dart', 'src/serious_python_android/android/build.gradle.kts', 'src/serious_python_darwin/darwin/prepare_macos.sh', 'src/serious_python_darwin/darwin/prepare_ios.sh', 'src/serious_python_windows/windows/CMakeLists.txt', 'src/serious_python_linux/linux/CMakeLists.txt') }} restore-keys: | flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}- flet-cache-${{ runner.os }}-${{ runner.arch }}- + # Read-only on tag/release builds to avoid cache poisoning. + lookup-only: ${{ startsWith(github.ref, 'refs/tags/') }} - name: Enable KVM run: | @@ -247,19 +268,24 @@ jobs: # `gradle-actions-caching` library under proprietary commercial # Terms of Use (https://blog.gradle.org/github-actions-for-gradle-v6). # v5 was the last fully MIT-licensed major and is on Node 24. - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 + with: + # Disable the Gradle cache on tag/release builds to avoid cache poisoning. + cache-disabled: ${{ startsWith(github.ref, 'refs/tags/') }} - name: AVD cache - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 id: avd-cache with: path: | ~/.android/avd/* ~/.android/adb* key: avd-bridge + # Read-only on tag/release builds to avoid cache poisoning. + lookup-only: ${{ startsWith(github.ref, 'refs/tags/') }} - name: Setup Android Emulator + Run tests - uses: reactivecircus/android-emulator-runner@v2 + uses: reactivecircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2.37.0 env: EMULATOR_PORT: 5554 with: @@ -318,22 +344,26 @@ jobs: SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }} steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false - name: Setup Flutter - uses: kuhnroyal/flutter-fvm-config-action/setup@v3 + uses: kuhnroyal/flutter-fvm-config-action/setup@c378498f1d1962d33039c3989411093ef8a17b2c # v3.3 with: path: '.fvmrc' cache: true - name: Cache flet downloads - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: ~/.flet/cache key: flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}-${{ hashFiles('src/serious_python/lib/src/python_versions.dart', 'src/serious_python/bin/package_command.dart', 'src/serious_python_android/android/build.gradle.kts', 'src/serious_python_darwin/darwin/prepare_macos.sh', 'src/serious_python_darwin/darwin/prepare_ios.sh', 'src/serious_python_windows/windows/CMakeLists.txt', 'src/serious_python_linux/linux/CMakeLists.txt') }} restore-keys: | flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}- flet-cache-${{ runner.os }}-${{ runner.arch }}- + # Read-only on tag/release builds to avoid cache poisoning. + lookup-only: ${{ startsWith(github.ref, 'refs/tags/') }} - name: Package + run integration test working-directory: "src/serious_python/example/bridge_example" @@ -390,29 +420,33 @@ jobs: SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }} steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false - name: Get Flutter version from .fvmrc - uses: kuhnroyal/flutter-fvm-config-action/config@v3 + uses: kuhnroyal/flutter-fvm-config-action/config@c378498f1d1962d33039c3989411093ef8a17b2c # v3.3 id: fvm-config-action with: path: '.fvmrc' - name: Setup Flutter - uses: subosito/flutter-action@v2 + uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 with: flutter-version: ${{ steps.fvm-config-action.outputs.FLUTTER_VERSION }} channel: ${{ matrix.arch == 'arm64' && 'master' || 'stable' }} cache: true - name: Cache flet downloads - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: ~/.flet/cache key: flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}-${{ hashFiles('src/serious_python/lib/src/python_versions.dart', 'src/serious_python/bin/package_command.dart', 'src/serious_python_android/android/build.gradle.kts', 'src/serious_python_darwin/darwin/prepare_macos.sh', 'src/serious_python_darwin/darwin/prepare_ios.sh', 'src/serious_python_windows/windows/CMakeLists.txt', 'src/serious_python_linux/linux/CMakeLists.txt') }} restore-keys: | flet-cache-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python_version }}- flet-cache-${{ runner.os }}-${{ runner.arch }}- + # Read-only on tag/release builds to avoid cache poisoning. + lookup-only: ${{ startsWith(github.ref, 'refs/tags/') }} - name: Install Linux desktop build deps run: | @@ -495,15 +529,20 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: + persist-credentials: false fetch-depth: 0 - name: Setup uv - uses: astral-sh/setup-uv@v8.2.0 + uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 + with: + # Publish runs on tags; disable the uv cache there to avoid + # release-time cache poisoning. + enable-cache: ${{ !startsWith(github.ref, 'refs/tags/') }} - name: Setup Flutter - uses: kuhnroyal/flutter-fvm-config-action/setup@v3 + uses: kuhnroyal/flutter-fvm-config-action/setup@c378498f1d1962d33039c3989411093ef8a17b2c # v3.3 with: path: '.fvmrc' cache: true From 92d24730be3b6cf6caa043c7f1ce35d217b91bff Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Wed, 1 Jul 2026 19:02:49 +0200 Subject: [PATCH 3/3] Update `sync_site_packages.sh`: refine comments on dylib and .so processing --- src/serious_python_darwin/darwin/sync_site_packages.sh | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/serious_python_darwin/darwin/sync_site_packages.sh b/src/serious_python_darwin/darwin/sync_site_packages.sh index 35ee734c..b02652ad 100755 --- a/src/serious_python_darwin/darwin/sync_site_packages.sh +++ b/src/serious_python_darwin/darwin/sync_site_packages.sh @@ -33,14 +33,9 @@ if [[ -n "$SERIOUS_PYTHON_SITE_PACKAGES" && -d "$SERIOUS_PYTHON_SITE_PACKAGES" ] cp -R $SERIOUS_PYTHON_SITE_PACKAGES/* $tmp_dir echo "Converting dylibs to xcframeworks..." - # Process BOTH .so (Python C-extensions) and .dylib (ctypes-loaded shared - # libs, e.g. llama-cpp-python's libllama/libggml*). Previously only *.so - # was framework-ized; *.dylib fell through and shipped the archs[0]=iphoneos - # DEVICE build, so it failed to dlopen on the simulator. + # Process BOTH .so (Python C-extensions) and .dylib (ctypes-loaded shared libs). for _sp_ext in so dylib; do - # -type f: skip SONAME symlinks (e.g. libfoo.dylib -> libfoo.1.dylib); - # only real Mach-O files are converted. Recipes should ship unversioned - # shared libs (as the flet-lib* recipes already do). + # -type f: only match regular files, skipping SONAME symlinks, so only real Mach-O files are converted. find "$tmp_dir/${archs[0]}" -name "*.$_sp_ext" -type f | while read full_dylib; do dylib_relative_path=${full_dylib#$tmp_dir/${archs[0]}/} create_xcframework_from_dylibs \