From 2c7c2f9792aa503725a39caa56574df0802b1ebc Mon Sep 17 00:00:00 2001 From: Amy Galles <9685081+AmyLGalles@users.noreply.github.com> Date: Fri, 14 Aug 2026 07:50:56 -0700 Subject: [PATCH 1/6] Add pull_request_target workflows for community PRs Adds companion -target.yml workflows to support community PRs that need secrets access, following the established pattern from bitwarden/clients. Problem: Community PRs from forks fail when workflows require secrets (Azure auth, GitHub App tokens, etc.) because fork PRs don't have access to repository or organization secrets when triggered via pull_request events. Solution: Created three new -target.yml workflows that use pull_request_target: - build-wasm-internal-target.yml - build-android-target.yml - enforce-labels-target.yml These workflows: 1. Use check-run.yml to validate the triggering actor has write permissions 2. Only run for external forks (not internal PRs) 3. Call the existing workflows with secrets: inherit 4. Require manual approval - they show as "action_required" and must be re-run by a maintainer after code review Security safeguards: - No direct checkout of PR code (calls reusable workflows) - Fork detection: github.event.pull_request.head.repo.full_name != github.repository - Permission validation via check-run.yml - Manual maintainer approval required before execution Workflow for maintainers: 1. Community PR is opened 2. Workflows fail with "action_required" status 3. Maintainer reviews PR code for safety 4. Maintainer clicks "Re-run jobs" on failed checks 5. Workflows run with secrets access after permission validation This follows the same pattern used in bitwarden/clients for build-cli-target.yml, build-browser-target.yml, etc. --- .github/workflows/build-android-target.yml | 35 ++++++++++++++++++ .../workflows/build-wasm-internal-target.yml | 37 +++++++++++++++++++ .github/workflows/enforce-labels-target.yml | 34 +++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 .github/workflows/build-android-target.yml create mode 100644 .github/workflows/build-wasm-internal-target.yml create mode 100644 .github/workflows/enforce-labels-target.yml diff --git a/.github/workflows/build-android-target.yml b/.github/workflows/build-android-target.yml new file mode 100644 index 000000000..5d8bd430c --- /dev/null +++ b/.github/workflows/build-android-target.yml @@ -0,0 +1,35 @@ +# This workflow is intended to be run when we need to build the Android SDK and produce artifacts that require secrets +# when the PR source branch does not have access to secrets (e.g. a fork). +# This workflow will run in the context of the target of the PR and have access to secrets. +# This should only be done after reviewing the PR to ensure that no malicious code has been introduced, +# as it could allow the code on the forked branch to have access to workflow secrets. + +name: Build Android on PR Target + +on: + pull_request_target: + types: [opened, synchronize, reopened] + branches: + - main + +defaults: + run: + shell: bash + +jobs: + check-run: + name: Check PR run + uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main + permissions: + contents: read + + run-workflow: + name: Build Android + needs: check-run + if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + uses: ./.github/workflows/build-android.yml + secrets: inherit + permissions: + contents: read + pull-requests: write + id-token: write diff --git a/.github/workflows/build-wasm-internal-target.yml b/.github/workflows/build-wasm-internal-target.yml new file mode 100644 index 000000000..9f6249ae0 --- /dev/null +++ b/.github/workflows/build-wasm-internal-target.yml @@ -0,0 +1,37 @@ +# This workflow is intended to be run when we need to build the WASM SDK and produce artifacts that require secrets +# when the PR source branch does not have access to secrets (e.g. a fork). +# This workflow will run in the context of the target of the PR and have access to secrets. +# This should only be done after reviewing the PR to ensure that no malicious code has been introduced, +# as it could allow the code on the forked branch to have access to workflow secrets. + +name: Build @bitwarden/sdk-internal on PR Target + +on: + pull_request_target: + types: [opened, synchronize, reopened] + branches: + - main + - rc + - hotfix-rc + +defaults: + run: + shell: bash + +jobs: + check-run: + name: Check PR run + uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main + permissions: + contents: read + + run-workflow: + name: Build @bitwarden/sdk-internal + needs: check-run + if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + uses: ./.github/workflows/build-wasm-internal.yml + secrets: inherit + permissions: + contents: read + pull-requests: write + id-token: write diff --git a/.github/workflows/enforce-labels-target.yml b/.github/workflows/enforce-labels-target.yml new file mode 100644 index 000000000..642571011 --- /dev/null +++ b/.github/workflows/enforce-labels-target.yml @@ -0,0 +1,34 @@ +# This workflow is intended to be run when we need to enforce PR labels +# when the PR source branch does not have access to secrets (e.g. a fork). +# This workflow will run in the context of the target of the PR and have access to secrets. +# This should only be done after reviewing the PR to ensure that no malicious code has been introduced, +# as it could allow the code on the forked branch to have access to workflow secrets. + +name: Enforce PR labels on PR Target + +on: + pull_request_target: + types: [labeled, unlabeled, opened, edited, synchronize] + branches: + - main + +defaults: + run: + shell: bash + +jobs: + check-run: + name: Check PR run + uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main + permissions: + contents: read + + run-workflow: + name: Enforce Labels + needs: check-run + if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + uses: ./.github/workflows/enforce-labels.yml + secrets: inherit + permissions: + contents: read + pull-requests: read From 8626e44add1881604af79bdcaf166301a6e724aa Mon Sep 17 00:00:00 2001 From: Amy Galles <9685081+AmyLGalles@users.noreply.github.com> Date: Fri, 14 Aug 2026 08:29:07 -0700 Subject: [PATCH 2/6] Fix workflow lint errors - Add workflow_call trigger to build-android.yml and build-wasm-internal.yml to make them reusable workflows - Remove rc and hotfix-rc branch targets from build-wasm-internal-target.yml per linter requirement that pull_request_target can only target main branch --- .github/workflows/build-android.yml | 8 ++++++++ .github/workflows/build-wasm-internal-target.yml | 2 -- .github/workflows/build-wasm-internal.yml | 8 ++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index c9523eeb4..9453cd3a6 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -11,6 +11,14 @@ on: description: "Update Android Repo - Opens a PR updating the SDK in bitwarden/android" type: boolean default: false + workflow_call: + secrets: + AZURE_SUBSCRIPTION_ID: + required: true + AZURE_TENANT_ID: + required: true + AZURE_CLIENT_ID: + required: true defaults: run: diff --git a/.github/workflows/build-wasm-internal-target.yml b/.github/workflows/build-wasm-internal-target.yml index 9f6249ae0..72ea6b330 100644 --- a/.github/workflows/build-wasm-internal-target.yml +++ b/.github/workflows/build-wasm-internal-target.yml @@ -11,8 +11,6 @@ on: types: [opened, synchronize, reopened] branches: - main - - rc - - hotfix-rc defaults: run: diff --git a/.github/workflows/build-wasm-internal.yml b/.github/workflows/build-wasm-internal.yml index ac0d4253a..d2c31d62c 100644 --- a/.github/workflows/build-wasm-internal.yml +++ b/.github/workflows/build-wasm-internal.yml @@ -8,6 +8,14 @@ on: - "rc" - "hotfix-rc" workflow_dispatch: + workflow_call: + secrets: + AZURE_SUBSCRIPTION_ID: + required: true + AZURE_TENANT_ID: + required: true + AZURE_CLIENT_ID: + required: true permissions: {} From fb25ff84560b5f9da343aed9772f1c0016da9fc7 Mon Sep 17 00:00:00 2001 From: Amy Galles <9685081+AmyLGalles@users.noreply.github.com> Date: Fri, 14 Aug 2026 08:46:14 -0700 Subject: [PATCH 3/6] Fix critical workflow issues for pull_request_target Problem 1: trigger-wasm-publish was firing on all fork PRs - Added event_name check to ensure it only runs on push events - Changed condition from `github.ref == 'refs/heads/main'` to `github.event_name == 'push' && github.ref == 'refs/heads/main'` - This prevents the production npm publish from triggering on every community fork PR Problem 2: combine job had no checkout under pull_request_target - Updated PR checkout step to accept both pull_request and pull_request_target events - Changed ref from head.ref to head.sha for more reliable checkout - Added repository parameter to support fork PRs - This ensures the combine job can actually run when called from build-android-target.yml --- .github/workflows/build-android.yml | 5 +++-- .github/workflows/build-wasm-internal.yml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 9453cd3a6..7f7bd253a 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -87,10 +87,11 @@ jobs: steps: - name: Checkout repo (PR) uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target' with: fetch-depth: 0 - ref: ${{ github.event.pull_request.head.ref }} + ref: ${{ github.event.pull_request.head.sha }} + repository: ${{ github.event.pull_request.head.repo.full_name }} persist-credentials: false - name: Checkout repo (Push or manual run) diff --git a/.github/workflows/build-wasm-internal.yml b/.github/workflows/build-wasm-internal.yml index d2c31d62c..e002dc4b2 100644 --- a/.github/workflows/build-wasm-internal.yml +++ b/.github/workflows/build-wasm-internal.yml @@ -146,7 +146,7 @@ jobs: trigger-wasm-publish: name: Trigger WASM publish - if: github.ref == 'refs/heads/main' + if: github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: ubuntu-24.04 needs: build permissions: From 2ab848879237fdd3f929a5057b5f08aa4414d6d1 Mon Sep 17 00:00:00 2001 From: Amy Galles <9685081+AmyLGalles@users.noreply.github.com> Date: Fri, 14 Aug 2026 10:37:23 -0700 Subject: [PATCH 4/6] Skip secrets-dependent jobs for fork PRs in base workflows The pull_request trigger still runs on fork PRs, but fork PRs don't have access to secrets or write permissions. This causes the following jobs to fail: In build-android.yml: - combine: needs packages:write for gradle publish - check-android-breaking-changes: needs AZURE_* secrets In build-wasm-internal.yml: - trigger-breaking-change-check: needs AZURE_* secrets Added fork detection to skip these jobs for external PRs: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository This ensures: - Internal PRs run all checks normally via pull_request - Fork PRs skip secrets-dependent jobs in pull_request run - Fork PRs get secrets-dependent jobs via pull_request_target after maintainer approval --- .github/workflows/build-android.yml | 3 ++- .github/workflows/build-wasm-internal.yml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 7f7bd253a..81380f8c6 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -77,6 +77,7 @@ jobs: name: Combine runs-on: ubuntu-24.04 needs: build + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository outputs: sdk-package-id: ${{ steps.publish.outputs.sdk-package-id }} sdk-version: ${{ steps.publish.outputs.sdk-version }} @@ -150,7 +151,7 @@ jobs: check-android-breaking-changes: name: Check for Android breaking changes - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository needs: combine permissions: contents: read diff --git a/.github/workflows/build-wasm-internal.yml b/.github/workflows/build-wasm-internal.yml index e002dc4b2..9b7cca0f3 100644 --- a/.github/workflows/build-wasm-internal.yml +++ b/.github/workflows/build-wasm-internal.yml @@ -163,7 +163,7 @@ jobs: trigger-breaking-change-check: name: Trigger client breaking change checks - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository needs: build permissions: contents: read From 8e63c2ae8ac903d57a0ab7a211684251c29a72ff Mon Sep 17 00:00:00 2001 From: Amy Galles <9685081+AmyLGalles@users.noreply.github.com> Date: Fri, 14 Aug 2026 10:42:27 -0700 Subject: [PATCH 5/6] Fix build-wasm-internal.yml to support pull_request_target properly When called from pull_request_target workflows, the reusable workflow inherits the caller's event context, causing several issues: 1. Checkout had no ref parameter, checked out base branch instead of PR 2. Version setting only ran for pull_request, leaving vars unset 3. Breaking-change check only ran for pull_request, was skipped 4. Integration tests checkout also had no ref parameter Changes: - Split checkout into PR and Push/Dispatch variants like build-android - PR checkout uses head.sha and repository for fork support - Updated version setting to accept pull_request_target - Updated breaking-change check to run for: - Internal PRs via pull_request (has secrets) - Fork PRs via pull_request_target (has secrets after approval) - Skip for fork PRs via pull_request (no secrets) - Fixed integration-tests checkout the same way This ensures fork PRs build the actual PR code and run breaking-change detection after maintainer approval. --- .github/workflows/build-wasm-internal.yml | 28 +++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-wasm-internal.yml b/.github/workflows/build-wasm-internal.yml index 9b7cca0f3..92df72fd1 100644 --- a/.github/workflows/build-wasm-internal.yml +++ b/.github/workflows/build-wasm-internal.yml @@ -43,13 +43,22 @@ jobs: readable: "commercial license" steps: - - name: Checkout repo + - name: Checkout repo (PR) uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target' + with: + ref: ${{ github.event.pull_request.head.sha }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + persist-credentials: false + + - name: Checkout repo (Push or manual run) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' with: persist-credentials: false - name: Set version (PR) - if: ${{ github.event_name == 'pull_request' }} + if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }} env: PR_HEAD_REF: "${{ github.event.pull_request.head.ref }}" run: | @@ -118,8 +127,17 @@ jobs: permissions: contents: read steps: - - name: Checkout repo + - name: Checkout repo (PR) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target' + with: + ref: ${{ github.event.pull_request.head.sha }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + persist-credentials: false + + - name: Checkout repo (Push or manual run) uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' with: persist-credentials: false @@ -163,7 +181,9 @@ jobs: trigger-breaking-change-check: name: Trigger client breaking change checks - if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository + if: | + (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || + github.event_name == 'pull_request_target' needs: build permissions: contents: read From 495e731f3c9df9514d0162e873354d1a93df0b0c Mon Sep 17 00:00:00 2001 From: Amy Galles <9685081+AmyLGalles@users.noreply.github.com> Date: Tue, 18 Aug 2026 07:11:41 -0700 Subject: [PATCH 6/6] Remove unnecessary enforce-labels-target workflow The base enforce-labels.yml workflow only needs read permissions (contents: read, pull-requests: read) and doesn't require any secrets. Fork PRs can already run it successfully under the pull_request trigger. The -target version added unnecessary security risk by running with pull_request_target privileges for a workflow that only performs read operations. --- .github/workflows/enforce-labels-target.yml | 34 --------------------- 1 file changed, 34 deletions(-) delete mode 100644 .github/workflows/enforce-labels-target.yml diff --git a/.github/workflows/enforce-labels-target.yml b/.github/workflows/enforce-labels-target.yml deleted file mode 100644 index 642571011..000000000 --- a/.github/workflows/enforce-labels-target.yml +++ /dev/null @@ -1,34 +0,0 @@ -# This workflow is intended to be run when we need to enforce PR labels -# when the PR source branch does not have access to secrets (e.g. a fork). -# This workflow will run in the context of the target of the PR and have access to secrets. -# This should only be done after reviewing the PR to ensure that no malicious code has been introduced, -# as it could allow the code on the forked branch to have access to workflow secrets. - -name: Enforce PR labels on PR Target - -on: - pull_request_target: - types: [labeled, unlabeled, opened, edited, synchronize] - branches: - - main - -defaults: - run: - shell: bash - -jobs: - check-run: - name: Check PR run - uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main - permissions: - contents: read - - run-workflow: - name: Enforce Labels - needs: check-run - if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} - uses: ./.github/workflows/enforce-labels.yml - secrets: inherit - permissions: - contents: read - pull-requests: read