-
Notifications
You must be signed in to change notification settings - Fork 50
ci: enable PR Preview workflow with security hardening (Phase 1) #2772
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+262
−92
Merged
Changes from 9 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
a44a7ec
ci: enable PR Preview workflow with security hardening
piyalbasu 66af2ce
ci: drop source-map strip step (public repo, source already public)
piyalbasu a8a3a73
ci: reframe preview-install warning to focus on review-stage risk
piyalbasu c0326c3
ci: reframe release-notes warning consistently with PR comment
piyalbasu e300b3f
ci: use folded block scalar (>-) for multi-line if:
piyalbasu 5996e09
ci: drop author_association gate (private org memberships fail it)
piyalbasu 64df9d0
ci: move install instructions to release description, slim PR comment
piyalbasu 1e06787
ci: write release notes to file instead of $(cat <<EOF) capture
piyalbasu 08196be
Merge branch 'master' into enable-pr-preview-workflow
piyalbasu a83f88e
ci: address Copilot review feedback
piyalbasu 458d573
ci: point preview at prod freighter-backend (not staging/beta)
piyalbasu b284f50
ci: V1 prod, V2 beta (revert V2 to beta only)
piyalbasu d2bffe7
ci: apply PR Preview security audit fixes
piyalbasu a325207
fix(ci): handle draft releases in pre-create + cleanup delete steps
piyalbasu 4839a74
Merge branch 'master' into enable-pr-preview-workflow
piyalbasu 5d32ec3
Merge branch 'master' into enable-pr-preview-workflow
piyalbasu 614ced5
Merge branch 'master' into enable-pr-preview-workflow
piyalbasu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,184 @@ | ||
| # ---------------------------------------------------------------------- | ||
| # SECURITY INVARIANT — read before editing. | ||
| # | ||
| # Do NOT add any of these triggers without a strict author-association | ||
| # gate at the very first job step: | ||
| # issue_comment, pull_request_target, pull_request_review, | ||
| # pull_request_review_comment, workflow_run | ||
| # These triggers run with FULL repo secrets and a writable GITHUB_TOKEN | ||
| # even when activity originates from a fork PR. Combined with checkout- | ||
| # of-PR-head + execute-code-from-PR (which any build inherently does), | ||
| # they enable Remote Code Execution by anyone who can comment on a PR | ||
| # or open one. | ||
| # | ||
| # Background: the PR Preview design and a pre-rollout security review | ||
| # explicitly forbid these triggers. See the design doc Security section. | ||
| # ---------------------------------------------------------------------- | ||
| name: PR Preview | ||
|
|
||
| env: | ||
| INDEXER_URL: ${{ secrets.INDEXER_URL }} | ||
| INDEXER_V2_URL: ${{ secrets.INDEXER_V2_BETA_URL }} | ||
|
|
||
| on: | ||
| pull_request: | ||
| types: [opened, synchronize, reopened, closed] | ||
|
|
||
| # Default for all jobs is no permissions; each job opts in to the | ||
| # narrowest scope it needs below. | ||
| permissions: {} | ||
|
|
||
| jobs: | ||
| build-and-release: | ||
| name: Build and Release PR Preview | ||
| # Two-layer gate evaluated BEFORE any secret is injected: | ||
| # 1. Skip on PR close (handled by cleanup-release job) | ||
| # 2. Reject fork PRs (defense-in-depth — platform also withholds secrets/token) | ||
| # NOTE: we don't gate on author_association because the field in the | ||
| # webhook event payload only reflects PUBLIC org membership; SDF members | ||
| # with private memberships show as CONTRIBUTOR, which would lock them | ||
| # out. Non-SDF gating is handled by the org-level "Require approval | ||
| # for outside collaborators" setting plus the platform-level fork-PR | ||
| # secret-withholding. | ||
|
piyalbasu marked this conversation as resolved.
|
||
| if: >- | ||
| github.event.action != 'closed' && | ||
| github.event.pull_request.head.repo.full_name == github.repository | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write # release create/delete + tag operations | ||
| pull-requests: write # sticky preview-link comment on the PR | ||
| concurrency: | ||
| group: pr-preview-build-${{ github.event.pull_request.number }} | ||
|
piyalbasu marked this conversation as resolved.
Outdated
|
||
| cancel-in-progress: true | ||
| steps: | ||
| - name: Validate required secrets | ||
| if: ${{ env.INDEXER_URL == '' || env.INDEXER_V2_URL == '' }} | ||
| run: | | ||
| echo "::error::INDEXER_URL or INDEXER_V2_URL is empty. Verify repo Secrets are configured." | ||
| exit 1 | ||
|
|
||
| - name: Checkout code | ||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | ||
|
|
||
| - name: Assert manifest has no top-level `key` field | ||
| run: | | ||
| if jq -e 'has("key")' ./extension/public/static/manifest/v3.json > /dev/null; then | ||
| echo "::error::manifest/v3.json contains a top-level 'key' field. Preview installs would share a Chromium extension ID with Web Store Freighter, leaking storage." | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Rewrite preview manifest identity (Chromium + Firefox) | ||
| env: | ||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||
| run: | | ||
| PREVIEW_NAME="Freighter PR Preview ${PR_NUMBER}" | ||
| PREVIEW_VERSION_NAME="pr-preview-${PR_NUMBER}" | ||
| PREVIEW_GECKO_ID="freighter-pr-preview-${PR_NUMBER}@stellar.org" | ||
|
|
||
|
piyalbasu marked this conversation as resolved.
Outdated
|
||
| jq --arg name "$PREVIEW_NAME" --arg vn "$PREVIEW_VERSION_NAME" \ | ||
| '.name = $name | .version_name = $vn' \ | ||
| ./extension/public/static/manifest/v3.json > /tmp/v3.json | ||
| mv /tmp/v3.json ./extension/public/static/manifest/v3.json | ||
|
|
||
| jq --arg name "$PREVIEW_NAME" --arg vn "$PREVIEW_VERSION_NAME" --arg gid "$PREVIEW_GECKO_ID" \ | ||
| '.name = $name | .version_name = $vn | .browser_specific_settings.gecko.id = $gid' \ | ||
| ./extension/public/static/manifest/v2.json > /tmp/v2.json | ||
| mv /tmp/v2.json ./extension/public/static/manifest/v2.json | ||
|
|
||
| - name: Setup Node | ||
| uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 | ||
| with: | ||
| node-version: 22 | ||
|
|
||
| - name: Enable Corepack | ||
| run: corepack enable | ||
|
|
||
| - name: Install + build extension (production) | ||
| run: yarn && yarn build:freighter-api && yarn build:extension:production | ||
|
|
||
| - name: Use BETA icons | ||
| run: | | ||
| rm -rf ./extension/build/images | ||
| mv ./extension/build/beta_images ./extension/build/images | ||
|
|
||
| - name: Zip extension build | ||
| working-directory: ./extension/build | ||
| run: zip -qq -r ./build.zip * | ||
|
|
||
|
piyalbasu marked this conversation as resolved.
|
||
| - name: Delete existing preview release (idempotent) | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||
| run: gh release delete "pr-preview-${PR_NUMBER}" --yes --cleanup-tag || true | ||
|
|
||
| - name: Create draft preview release | ||
| id: release | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||
| PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} | ||
| PR_URL: ${{ github.event.pull_request.html_url }} | ||
| run: | | ||
| # Write notes to a file rather than $(cat <<EOF) because bash's | ||
| # command-substitution parser tokenizes single quotes inside the | ||
| # heredoc body, breaking on apostrophes in prose. | ||
| cat > /tmp/release-notes.md <<EOF | ||
| Internal preview build for PR [#${PR_NUMBER}](${PR_URL}). SDF collaborators only — non-SDF GitHub users get 404 on this page. Auto-deleted when the PR is closed. | ||
|
|
||
| **Commit:** ${PR_HEAD_SHA} | ||
| **Backend:** staging | ||
|
|
||
| ### How to install (Chromium) | ||
|
|
||
| 1. Download \`build.zip\` from the Assets section below and unzip it | ||
| 2. Open \`chrome://extensions\` in Chrome, Edge, or Brave | ||
| 3. Enable Developer Mode (toggle in the top-right) | ||
| 4. Click "Load Unpacked" and select the unzipped folder | ||
| 5. The extension installs as "Freighter PR Preview #${PR_NUMBER}" with beta icons | ||
|
|
||
| ### Important | ||
|
|
||
| This code is still under review and may contain bugs that have not been caught yet. **Use caution before signing transactions with real funds** — consider testing with a testnet wallet instead. | ||
| EOF | ||
| URL=$(gh release create "pr-preview-${PR_NUMBER}" \ | ||
| ./extension/build/build.zip \ | ||
| --title "PR Preview #${PR_NUMBER}" \ | ||
| --notes-file /tmp/release-notes.md \ | ||
| --draft) | ||
|
piyalbasu marked this conversation as resolved.
Outdated
|
||
| echo "url=${URL}" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Post/update sticky PR comment with preview link | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| GH_REPO: ${{ github.repository }} | ||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||
| RELEASE_URL: ${{ steps.release.outputs.url }} | ||
| run: | | ||
| MARKER="<!-- pr-preview-comment -->" | ||
| BODY="${MARKER}"$'\n'"PR Preview build is ready: ${RELEASE_URL} (SDF collaborators only — install instructions in the release description)" | ||
|
|
||
| EXISTING=$(gh api "repos/${GH_REPO}/issues/${PR_NUMBER}/comments" \ | ||
| --jq ".[] | select(.body | startswith(\"${MARKER}\")) | .id" | head -1) | ||
| if [ -n "$EXISTING" ]; then | ||
| gh api -X PATCH "repos/${GH_REPO}/issues/comments/${EXISTING}" -f body="$BODY" | ||
| else | ||
| gh pr comment "${PR_NUMBER}" --body "$BODY" | ||
| fi | ||
|
|
||
| cleanup-release: | ||
| name: Cleanup PR Preview Release | ||
| if: github.event.action == 'closed' && github.event.pull_request.head.repo.full_name == github.repository | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write | ||
| concurrency: | ||
| group: pr-preview-cleanup-${{ github.event.pull_request.number }} | ||
| # Don't cancel cleanup runs — they need to complete to remove the draft. | ||
| cancel-in-progress: false | ||
| steps: | ||
| - name: Delete draft release and tag | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| GH_REPO: ${{ github.repository }} | ||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||
| run: gh release delete "pr-preview-${PR_NUMBER}" --yes --cleanup-tag || true | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.