diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 2d2c3dde4ba..2a2ed892601 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -34,6 +34,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 + persist-credentials: false - name: Show disk usage uses: ./.github/actions/disk-usage @@ -65,6 +66,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 + persist-credentials: false - name: Setup Gradle environment uses: ./.github/actions/setup-gradle @@ -82,6 +84,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 + persist-credentials: false - name: Setup Gradle environment uses: ./.github/actions/setup-gradle @@ -103,6 +106,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 + persist-credentials: false - name: Setup Gradle environment uses: ./.github/actions/setup-gradle @@ -120,6 +124,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 + persist-credentials: false - name: Setup Gradle environment uses: ./.github/actions/setup-gradle @@ -145,6 +150,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 + persist-credentials: false - name: Setup Gradle environment uses: ./.github/actions/setup-gradle @@ -162,6 +168,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 + persist-credentials: false - name: Setup Gradle environment uses: ./.github/actions/setup-gradle @@ -179,6 +186,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 + persist-credentials: false - name: Setup Gradle environment uses: ./.github/actions/setup-gradle diff --git a/.github/workflows/build-cli-tools.yml b/.github/workflows/build-cli-tools.yml index 2cba858ed10..75c3ce7da45 100644 --- a/.github/workflows/build-cli-tools.yml +++ b/.github/workflows/build-cli-tools.yml @@ -28,6 +28,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 + persist-credentials: false - name: Setup Gradle environment uses: ./.github/actions/setup-gradle diff --git a/.github/workflows/build-daily.yml b/.github/workflows/build-daily.yml index e8d53cb3525..7bd5f68d6ed 100644 --- a/.github/workflows/build-daily.yml +++ b/.github/workflows/build-daily.yml @@ -13,7 +13,14 @@ jobs: build-daily: if: ${{ github.repository_owner == 'thunderbird' }} uses: ./.github/workflows/shippable_builds.yml - secrets: inherit + secrets: + MATRIX_NOTIFY_TOKEN: ${{ secrets.MATRIX_NOTIFY_TOKEN }} + BOT_PRIVATE_KEY: ${{ secrets.BOT_PRIVATE_KEY }} + SIGNING_KEY: ${{ secrets.SIGNING_KEY }} + KEY_ALIAS: ${{ secrets.KEY_ALIAS }} + KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }} + PLAY_STORE_ACCOUNT: ${{ secrets.PLAY_STORE_ACCOUNT }} permissions: id-token: write # For GCS publishing (ftp.mo) contents: read diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 9c33ca8ad91..4e3f20b4747 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -35,6 +35,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: token: ${{ steps.app-token.outputs.token || github.token }} + persist-credentials: false - name: Cargo cache uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0 diff --git a/.github/workflows/build-scripts.yml b/.github/workflows/build-scripts.yml index d1d992c725a..f4128f952d6 100644 --- a/.github/workflows/build-scripts.yml +++ b/.github/workflows/build-scripts.yml @@ -27,6 +27,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 + persist-credentials: false - name: Setup Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # @v6.2.0 diff --git a/.github/workflows/pr-auto-assign-reviewer.yml b/.github/workflows/pr-auto-assign-reviewer.yml index 1f3d497f016..be15fb62eb8 100644 --- a/.github/workflows/pr-auto-assign-reviewer.yml +++ b/.github/workflows/pr-auto-assign-reviewer.yml @@ -1,9 +1,14 @@ --- name: PR - Auto Assign Reviewer -# Warning, this job is running on pull_request_target and therefore has access to issue content. -# Don't add any steps that act on external code. -on: +# This workflow uses pull_request_target so it has access to secrets and write +# permissions even for fork PRs, which is required to assign reviewers. +# +# Security constraints that must be maintained to prevent code injection: +# - Do not checkout or execute any code. +# - Do not interpolate free-form PR fields. + +on: # zizmor: ignore[dangerous-triggers] pull_request_target: types: [review_requested] @@ -32,4 +37,4 @@ jobs: PR_NUMBER: ${{ github.event.pull_request.number }} GH_TOKEN: ${{ steps.app-token.outputs.token || github.token }} run: | - gh pr edit $PR_NUMBER --repo $GITHUB_REPOSITORY --add-assignee "$PR_REVIEWER" + gh pr edit "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --add-assignee "$PR_REVIEWER" diff --git a/.github/workflows/pr-dependabot-dependency-guard-update.yml b/.github/workflows/pr-dependabot-dependency-guard-update.yml index c0674a8e296..c6122503913 100644 --- a/.github/workflows/pr-dependabot-dependency-guard-update.yml +++ b/.github/workflows/pr-dependabot-dependency-guard-update.yml @@ -1,10 +1,11 @@ --- name: PR - Update dependency guard for Dependabot PRs -# Warning, this job is running on pull_request_target and therefore has access to issue content. -# Don't add any steps that act on external code. +# This workflow uses pull_request (not pull_request_target) because it only runs +# on same-repo dependabot PRs, guarded by head.repo.full_name. Same-repo PRs have +# write permissions and secret access without needing pull_request_target. on: - pull_request_target: + pull_request: types: - opened - synchronize @@ -26,7 +27,7 @@ permissions: jobs: pr-update-dependency-guard: - if: github.actor == 'dependabot[bot]' && github.event.pull_request.head.repo.full_name == 'thunderbird/thunderbird-android' + if: github.event.pull_request.user.login == 'dependabot[bot]' && github.event.pull_request.head.repo.full_name == 'thunderbird/thunderbird-android' runs-on: ubuntu-latest environment: botmobile timeout-minutes: 90 @@ -44,6 +45,7 @@ jobs: with: ref: ${{ github.head_ref }} token: ${{ steps.app-token.outputs.token || github.token }} + persist-credentials: false - name: Setup Gradle environment uses: ./.github/actions/setup-gradle diff --git a/.github/workflows/pr-label-tb-team.yml b/.github/workflows/pr-label-tb-team.yml index 28e6dde73a1..a4aba7b7517 100644 --- a/.github/workflows/pr-label-tb-team.yml +++ b/.github/workflows/pr-label-tb-team.yml @@ -1,9 +1,14 @@ --- name: PR - Label tb-team -# Warning, this job is running on pull_request_target and therefore has access to issue content. -# Don't add any steps that act on external code. -on: +# This workflow uses pull_request_target so it has access to secrets and write +# permissions even for fork PRs, which is required to add labels. +# +# Security constraints that must be maintained to prevent code injection: +# - Do not checkout or execute any code. +# - Do not interpolate free-form PR fields. + +on: # zizmor: ignore[dangerous-triggers] pull_request_target: types: [opened, reopened] diff --git a/.github/workflows/pr-merged.yml b/.github/workflows/pr-merged.yml index 19ad326b6d9..1b73e39680e 100644 --- a/.github/workflows/pr-merged.yml +++ b/.github/workflows/pr-merged.yml @@ -1,9 +1,14 @@ --- name: PR - Merged -# Warning, this job is running on pull_request_target and therefore has access to issue content. -# Don't add any steps that act on external code. -on: +# This workflow uses pull_request_target so it has access to secrets and write +# permissions even for fork PRs, which is required to post comments and set milestones. +# +# Security constraints that must be maintained to prevent code injection: +# - Do not checkout or execute any code. +# - Do not interpolate free-form PR fields. + +on: # zizmor: ignore[dangerous-triggers] pull_request_target: branches: [main, beta, release] types: [closed] @@ -33,19 +38,19 @@ jobs: GH_TOKEN: ${{ steps.app-token.outputs.token || github.token }} run: | # There should be exactly 3 milestones open at all times - TARGET_BRANCH="${{ github.base_ref }}" + TARGET_BRANCH="${GITHUB_BASE_REF}" case "$TARGET_BRANCH" in main) milestone_index=0 ;; beta) milestone_index=1 ;; release) milestone_index=2 ;; esac echo "$milestone_index" - gh api repos/$GITHUB_REPOSITORY/milestones --jq " + gh api "repos/$GITHUB_REPOSITORY/milestones" --jq " map(select(.state == \"open\" and .due_on != null)) | sort_by(.due_on) | reverse | .[${milestone_index}] | { number, title } | to_entries - | map(.key + \"=\" + (.value|tostring)) | join(\"\n\")" | tee -a $GITHUB_OUTPUT + | map(.key + \"=\" + (.value|tostring)) | join(\"\n\")" | tee -a "$GITHUB_OUTPUT" - name: Thank you if: | @@ -67,7 +72,7 @@ jobs: Hope to see you there! 🚀📱🐦 run: | - gh pr comment $PR_NUMBER --repo $GITHUB_REPOSITORY --body "$MESSAGE" + gh pr comment "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --body "$MESSAGE" - name: Set active milestone on PR env: @@ -75,4 +80,4 @@ jobs: GH_TOKEN: ${{ steps.app-token.outputs.token || github.token }} MILESTONE: ${{ steps.milestone.outputs.number }} run: | - gh api --method PATCH /repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER -f milestone=$MILESTONE + gh api --method PATCH "/repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER" -f "milestone=$MILESTONE" diff --git a/.github/workflows/pr-opened.yml b/.github/workflows/pr-opened.yml index b02dbcd6ee0..57bcf0dd320 100644 --- a/.github/workflows/pr-opened.yml +++ b/.github/workflows/pr-opened.yml @@ -1,9 +1,14 @@ --- name: PR - Opened -# Warning, this job is running on pull_request_target and therefore has access to issue content. -# Don't add any steps that act on external code. -on: +# This workflow uses pull_request_target so it has access to secrets and write +# permissions even for fork PRs, which is required to post comments. +# +# Security constraints that must be maintained to prevent code injection: +# - Do not checkout or execute any code. +# - Do not interpolate free-form PR fields. + +on: # zizmor: ignore[dangerous-triggers] pull_request_target: branches: [beta, release] types: [opened] @@ -30,7 +35,6 @@ jobs: env: PR_NUMBER: ${{ github.event.pull_request.number }} GH_TOKEN: ${{ steps.app-token.outputs.token || github.token }} - MILESTONE: ${{ steps.milestone.outputs.title }} MESSAGE: | Thank you for your uplift request! Please add a comment with the following approval request template filled out. diff --git a/.github/workflows/pr-request-report-labels.yml b/.github/workflows/pr-request-report-labels.yml index 9adeefc4ffc..dabc932e919 100644 --- a/.github/workflows/pr-request-report-labels.yml +++ b/.github/workflows/pr-request-report-labels.yml @@ -1,9 +1,14 @@ --- name: PR - Request report labels -# Warning, this job is running on pull_request_target and therefore has access to issue content. -# Don't add any steps that act on external code. -on: +# This workflow uses pull_request_target so it has access to secrets and write +# permissions even for fork PRs, which is required to post comments and add labels. +# +# Security constraints that must be maintained to prevent code injection: +# - Do not checkout or execute any code. +# - Do not interpolate free-form PR fields. + +on: # zizmor: ignore[dangerous-triggers] pull_request_target: types: [ opened, reopened, synchronize, labeled, unlabeled ] branches: @@ -23,10 +28,13 @@ jobs: steps: - name: Validate report label id: validate + env: + LABELS_JSON: ${{ toJson(github.event.pull_request.labels) }} + PR_BODY: ${{ toJson(github.event.pull_request.body) }} run: | set -euo pipefail - labels_json='${{ toJson(github.event.pull_request.labels) }}' + labels_json="$LABELS_JSON" echo "Current labels:" echo "$labels_json" | jq -r '.[].name' @@ -47,7 +55,7 @@ jobs: feature_flag_count="$(jq '[.[] | select(.name == "feature flag")] | length' <<< "$labels_json")" if [ "$feature_flag_count" -gt 0 ]; then - pr_body='${{ toJson(github.event.pull_request.body) }}' + pr_body="$PR_BODY" pr_feature_flag_key="$(jq -nr --arg body "$pr_body" 'try ($body | gsub("\r"; "") | capture("(?m)^feature-flag:\\s*`(?[^`]+)`$").flag) catch ""')" if [ -z "$pr_feature_flag_key" ]; then echo "valid=false" >> "$GITHUB_OUTPUT" @@ -64,10 +72,11 @@ jobs: env: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ github.event.pull_request.number }} + REPO: ${{ github.repository }} MESSAGE: ${{ steps.validate.outputs.message }} run: | gh pr comment "$PR_NUMBER" \ - --repo "${{ github.repository }}" \ + --repo "$REPO" \ --body "$MESSAGE" - name: Fail if invalid diff --git a/.github/workflows/quality-checks.yml b/.github/workflows/quality-checks.yml index ee711b4acf0..a522d4c85f3 100644 --- a/.github/workflows/quality-checks.yml +++ b/.github/workflows/quality-checks.yml @@ -34,6 +34,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 + persist-credentials: false - name: Setup Gradle environment uses: ./.github/actions/setup-gradle @@ -51,6 +52,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 + persist-credentials: false - name: Setup Gradle environment uses: ./.github/actions/setup-gradle @@ -77,6 +79,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 + persist-credentials: false - name: Setup Gradle environment uses: ./.github/actions/setup-gradle @@ -94,6 +97,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 + persist-credentials: false - name: Setup Gradle environment uses: ./.github/actions/setup-gradle diff --git a/.github/workflows/quality-markdown.yml b/.github/workflows/quality-markdown.yml index 054ad2fe416..2fbb643ab9f 100644 --- a/.github/workflows/quality-markdown.yml +++ b/.github/workflows/quality-markdown.yml @@ -16,6 +16,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Cargo cache uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 # v1.16.0 diff --git a/.github/workflows/security-codeql.yml b/.github/workflows/security-codeql.yml index f1a758d5257..b5cf2db01fb 100644 --- a/.github/workflows/security-codeql.yml +++ b/.github/workflows/security-codeql.yml @@ -35,6 +35,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Setup Gradle environment if: matrix.language == 'java-kotlin' diff --git a/.github/workflows/shippable_builds.yml b/.github/workflows/shippable_builds.yml index b26226e7331..24bdae4d6a0 100644 --- a/.github/workflows/shippable_builds.yml +++ b/.github/workflows/shippable_builds.yml @@ -2,6 +2,21 @@ name: Shippable Build & Signing on: workflow_call: + secrets: + MATRIX_NOTIFY_TOKEN: + required: false + BOT_PRIVATE_KEY: + required: false + SIGNING_KEY: + required: false + KEY_ALIAS: + required: false + KEY_PASSWORD: + required: false + KEY_STORE_PASSWORD: + required: false + PLAY_STORE_ACCOUNT: + required: false workflow_dispatch: inputs: skipThunderbird: @@ -219,6 +234,7 @@ jobs: with: fetch-depth: 0 token: ${{ steps.app-token.outputs.token || github.token }} + persist-credentials: false - name: Setup Gradle environment if: ${{ contains(matrix.releaseTarget, 'github') || needs.dump_config.outputs.releaseType == 'daily' }} @@ -458,6 +474,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ steps.sha.outputs.app_sha }} + persist-credentials: false - name: Setup Gradle environment uses: ./.github/actions/setup-gradle @@ -682,6 +699,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ steps.shanotes.outputs.app_sha }} + persist-credentials: false - name: Download Artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 @@ -755,17 +773,25 @@ jobs: - name: Publish to GitHub Releases id: publish_gh if: ${{ contains(matrix.releaseTarget, 'github') }} - uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 - with: - token: ${{ steps.app-token.outputs.token || github.token }} - target_commitish: ${{ steps.shanotes.outputs.app_sha }} - tag_name: ${{ steps.pkginfo.outputs.TAG_NAME }} - name: ${{ steps.pkginfo.outputs.FULL_VERSION_NAME }} - body: ${{ steps.shanotes.outputs.app_github_notes }} - prerelease: ${{ env.RELEASE_TYPE != 'release' }} - fail_on_unmatched_files: true - files: | - uploads/${{ steps.rename.outputs.PKG_FILE }} + env: + GH_TOKEN: ${{ steps.app-token.outputs.token || github.token }} + TAG_NAME: ${{ steps.pkginfo.outputs.TAG_NAME }} + FULL_VERSION_NAME: ${{ steps.pkginfo.outputs.FULL_VERSION_NAME }} + RELEASE_NOTES: ${{ steps.shanotes.outputs.app_github_notes }} + COMMITISH: ${{ steps.shanotes.outputs.app_sha }} + PKG_FILE: uploads/${{ steps.rename.outputs.PKG_FILE }} + IS_PRERELEASE: ${{ env.RELEASE_TYPE != 'release' }} + run: | + args=( + "$TAG_NAME" + --title "$FULL_VERSION_NAME" + --notes "$RELEASE_NOTES" + --target "$COMMITISH" + "$PKG_FILE" + ) + [ "$IS_PRERELEASE" = "true" ] && args+=(--prerelease) + url=$(gh release create "${args[@]}") + echo "url=$url" >> "$GITHUB_OUTPUT" - name: Adjust release notes for play store upload if: ${{ !inputs.skipGooglePlay && contains(matrix.releaseTarget, 'play') && matrix.playTargetTrack }} @@ -1005,7 +1031,7 @@ jobs: uses: Mercymeilya/last-workflow-status@f86bce177498d6fdd3bda4113d7d93266cfc3fca id: last_status with: - github_token: ${{ secrets.GITHUB_TOKEN }} + github_token: ${{ github.token }} - name: Info uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/translation-update.yml b/.github/workflows/translation-update.yml index 9e70ffca04e..b426519c4ad 100644 --- a/.github/workflows/translation-update.yml +++ b/.github/workflows/translation-update.yml @@ -35,6 +35,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 + persist-credentials: false - name: Configure git and fetch remotes run: | diff --git a/.github/workflows/validate-gradle.yml b/.github/workflows/validate-gradle.yml index 72a21930cc3..565be2f5de2 100644 --- a/.github/workflows/validate-gradle.yml +++ b/.github/workflows/validate-gradle.yml @@ -13,4 +13,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: gradle/actions/wrapper-validation@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 diff --git a/.github/workflows/validate-workflows.yml b/.github/workflows/validate-workflows.yml index 5793dedc05e..eb6a531ac1b 100644 --- a/.github/workflows/validate-workflows.yml +++ b/.github/workflows/validate-workflows.yml @@ -22,6 +22,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Ensure GitHub actions are SHA pinned env: diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 00000000000..f8edc1af41a --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,28 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +--- +name: GitHub Actions Security Analysis with zizmor 🌈 + +on: + push: + branches: ["main"] + pull_request: + branches: ["**"] + +permissions: {} + +jobs: + zizmor: + name: Run zizmor 🌈 + runs-on: ubuntu-latest + permissions: + security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files. + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Run zizmor 🌈 + uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3