Update LavaMoat policies #115271
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
| name: Update LavaMoat policies | |
| on: | |
| issue_comment: | |
| types: created | |
| concurrency: | |
| group: update-lavamoat-policies-${{ github.event.issue.number }} | |
| cancel-in-progress: true | |
| jobs: | |
| prepare: | |
| name: Prepare | |
| # Only run when the comment is on a pull request containing '@metamaskbot update-policies' | |
| if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '@metamaskbot update-policies') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| env: | |
| REPO: ${{ github.repository }} | |
| PR_NUMBER: ${{ github.event.issue.number }} | |
| COMMENT_ID: ${{ github.event.comment.id }} | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| id-token: write | |
| outputs: | |
| IS_CROSS_REPO_PR: ${{ steps.is-cross-repo.outputs.IS_CROSS_REPO_PR }} | |
| RUN_ID: ${{ steps.find-ci-run.outputs.RUN_ID }} | |
| NO_CI_RUN: ${{ steps.find-ci-run.outputs.NO_CI_RUN }} | |
| VALIDATION_PENDING: ${{ steps.find-ci-run.outputs.VALIDATION_PENDING }} | |
| VALIDATION_SKIPPED: ${{ steps.find-ci-run.outputs.VALIDATION_SKIPPED }} | |
| VALIDATION_FAILED: ${{ steps.find-ci-run.outputs.VALIDATION_FAILED }} | |
| FAILED_JOBS: ${{ steps.find-ci-run.outputs.FAILED_JOBS }} | |
| steps: | |
| - name: Determine whether this PR is cross-repo | |
| id: is-cross-repo | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: echo "IS_CROSS_REPO_PR=$(gh pr view "${PR_NUMBER}" --repo "${REPO}" --json isCrossRepository --jq '.isCrossRepository')" >> "$GITHUB_OUTPUT" | |
| - name: Get token | |
| if: ${{ steps.is-cross-repo.outputs.IS_CROSS_REPO_PR == 'false' }} | |
| id: token | |
| uses: MetaMask/github-tools/.github/actions/get-token@v1 | |
| with: | |
| token-exchange-url: ${{ vars.TOKEN_EXCHANGE_URL }} | |
| permissions: | | |
| actions: read | |
| contents: read | |
| pull_requests: write | |
| - name: React to the comment | |
| if: ${{ steps.is-cross-repo.outputs.IS_CROSS_REPO_PR == 'false' }} | |
| env: | |
| GH_TOKEN: ${{ steps.token.outputs.token }} | |
| run: | | |
| gh api \ | |
| --method POST \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| "/repos/${REPO}/issues/comments/${COMMENT_ID}/reactions" \ | |
| -f content='+1' | |
| - name: Find CI run for PR head commit | |
| if: ${{ steps.is-cross-repo.outputs.IS_CROSS_REPO_PR == 'false' }} | |
| id: find-ci-run | |
| env: | |
| GH_TOKEN: ${{ steps.token.outputs.token }} | |
| run: | | |
| HEAD_SHA=$(gh pr view "${PR_NUMBER}" --repo "${REPO}" --json headRefOid --jq '.headRefOid') | |
| echo "Looking for main.yml run for commit ${HEAD_SHA}..." | |
| RUN_ID=$(gh run list \ | |
| --workflow=main.yml \ | |
| --repo "${REPO}" \ | |
| --commit="${HEAD_SHA}" \ | |
| --limit=1 \ | |
| --json databaseId \ | |
| --jq '.[0].databaseId // empty') | |
| if [ -z "$RUN_ID" ]; then | |
| echo "No CI run found" | |
| echo "NO_CI_RUN=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "Found run ${RUN_ID}, checking validation job status..." | |
| JOBS_JSON=$(gh api "/repos/${REPO}/actions/runs/${RUN_ID}/jobs" --paginate \ | |
| --jq '[.jobs[] | select(.name | startswith("validate-lavamoat-policies"))]') | |
| JOB_COUNT=$(echo "$JOBS_JSON" | jq 'length') | |
| if [ "$JOB_COUNT" -eq 0 ]; then | |
| echo "No validation jobs found in CI run (validation may have been skipped)" | |
| echo "VALIDATION_SKIPPED=true" >> "$GITHUB_OUTPUT" | |
| echo "RUN_ID=${RUN_ID}" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| PENDING=$(echo "$JOBS_JSON" | jq '[.[] | select(.status != "completed")] | length') | |
| if [ "$PENDING" -gt 0 ]; then | |
| echo "LavaMoat validation still running" | |
| echo "VALIDATION_PENDING=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| FAILED_JOBS=$(echo "$JOBS_JSON" | jq -r '[.[] | select(.conclusion != "success") | "- [" + .name + "](" + .html_url + ") (" + .conclusion + ")"] | join("\n")') | |
| echo "RUN_ID=${RUN_ID}" >> "$GITHUB_OUTPUT" | |
| if [ -n "$FAILED_JOBS" ]; then | |
| echo "LavaMoat validation completed with non-success conclusions — policy diffs may exist" | |
| echo "VALIDATION_FAILED=true" >> "$GITHUB_OUTPUT" | |
| { | |
| echo 'FAILED_JOBS<<EOF' | |
| echo "$FAILED_JOBS" | |
| echo 'EOF' | |
| } >> "$GITHUB_OUTPUT" | |
| else | |
| echo "LavaMoat validation completed — all policies up to date" | |
| fi | |
| apply-and-commit: | |
| name: Apply policy diffs and commit | |
| needs: prepare | |
| if: ${{ !cancelled() && needs.prepare.outputs.IS_CROSS_REPO_PR == 'false' }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| env: | |
| REPO: ${{ github.repository }} | |
| PR_NUMBER: ${{ github.event.issue.number }} | |
| RUN_ID: ${{ needs.prepare.outputs.RUN_ID }} | |
| NO_CI_RUN: ${{ needs.prepare.outputs.NO_CI_RUN }} | |
| VALIDATION_PENDING: ${{ needs.prepare.outputs.VALIDATION_PENDING }} | |
| VALIDATION_SKIPPED: ${{ needs.prepare.outputs.VALIDATION_SKIPPED }} | |
| VALIDATION_FAILED: ${{ needs.prepare.outputs.VALIDATION_FAILED }} | |
| FAILED_JOBS: ${{ needs.prepare.outputs.FAILED_JOBS }} | |
| ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - name: Get token | |
| id: token | |
| uses: MetaMask/github-tools/.github/actions/get-token@v1 | |
| with: | |
| token-exchange-url: ${{ vars.TOKEN_EXCHANGE_URL }} | |
| permissions: | | |
| actions: read | |
| contents: write | |
| pull_requests: write | |
| - name: Checkout repository | |
| if: ${{ env.VALIDATION_FAILED == 'true' }} | |
| uses: actions/checkout@v6 | |
| with: | |
| token: ${{ steps.token.outputs.token }} | |
| - name: Checkout pull request | |
| if: ${{ env.VALIDATION_FAILED == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ steps.token.outputs.token }} | |
| run: gh pr checkout "${PR_NUMBER}" | |
| - name: Download policy diffs from CI | |
| if: ${{ env.VALIDATION_FAILED == 'true' }} | |
| uses: actions/download-artifact@v7 | |
| with: | |
| path: lavamoat-policy-diffs | |
| pattern: lavamoat-policy-diff-* | |
| merge-multiple: true | |
| run-id: ${{ env.RUN_ID }} | |
| github-token: ${{ steps.token.outputs.token }} | |
| - name: Check for policy diffs | |
| if: ${{ !cancelled() }} | |
| id: check-diffs | |
| run: echo "HAS_DIFFS=$(compgen -G 'lavamoat-policy-diffs/*.patch' > /dev/null 2>&1 && echo true || echo false)" >> "$GITHUB_OUTPUT" | |
| - name: Apply policy diffs | |
| if: ${{ steps.check-diffs.outputs.HAS_DIFFS == 'true' }} | |
| run: | | |
| for patch in lavamoat-policy-diffs/*.patch; do | |
| echo "Applying ${patch}..." | |
| git apply "${patch}" | |
| done | |
| - name: Commit the updated policies | |
| if: ${{ steps.check-diffs.outputs.HAS_DIFFS == 'true' }} | |
| run: | | |
| git config --global user.name 'MetaMask Bot' | |
| git config --global user.email 'metamaskbot@users.noreply.github.com' | |
| git commit -am "Update LavaMoat policies" | |
| git push | |
| - name: Compare policy changes | |
| if: ${{ steps.check-diffs.outputs.HAS_DIFFS == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ steps.token.outputs.token }} | |
| run: | | |
| BASE_REF=$(gh pr view "${PR_NUMBER}" --json baseRefName --jq .baseRefName) | |
| git fetch origin "${BASE_REF}" | |
| compare_policies() { | |
| local main_dir="$1" | |
| local parent_dir="$2" | |
| git diff "origin/${BASE_REF}" -- "${main_dir}policy.json" > /tmp/main_policy_diff | |
| for folder in "${parent_dir}"*/; do | |
| if [ "$folder" != "$main_dir" ]; then | |
| local file="${folder}policy.json" | |
| if [ -f "$file" ]; then | |
| if diff -q /tmp/main_policy_diff <(git diff "origin/${BASE_REF}" -- "$file") > /dev/null 2>&1; then | |
| echo "✅ ${file} changes match ${main_dir}policy.json changes" | |
| else | |
| echo "👀 ${file} changes **differ from** ${main_dir}policy.json changes" | |
| fi | |
| fi | |
| fi | |
| done | |
| } | |
| { | |
| compare_policies "lavamoat/browserify/main/" "lavamoat/browserify/" | |
| compare_policies "lavamoat/webpack/mv2/main/" "lavamoat/webpack/mv2/" | |
| compare_policies "lavamoat/webpack/mv3/main/" "lavamoat/webpack/mv3/" | |
| } > does_diff_diff.txt | |
| - name: Post comment (policies updated) | |
| if: ${{ steps.check-diffs.outputs.HAS_DIFFS == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ steps.token.outputs.token }} | |
| run: | | |
| echo -e 'Policies updated. \n👀 Please review the diff for suspicious new powers. \n\n> [!TIP] \n> Follow the policy review process outlined in the [LavaMoat Policy Review Process doc](https://github.com/MetaMask/metamask-extension/blob/main/docs/lavamoat-policy-review-process.md) before expecting an approval from Policy Reviewers. \n🧠 Learn how to read policy diffs: https://lavamoat.github.io/guides/policy-diff/#what-to-look-for-when-reviewing-a-policy-diff \n\n' | cat - does_diff_diff.txt | gh pr comment "${PR_NUMBER}" --repo "${REPO}" --body-file - | |
| - name: Post comment (no CI run found) | |
| if: ${{ needs.prepare.result == 'success' && env.NO_CI_RUN == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ steps.token.outputs.token }} | |
| run: | | |
| gh pr comment "${PR_NUMBER}" --repo "${REPO}" --body "No CI run found for this commit. Please wait for CI to start and retry \`@metamaskbot update-policies\`." | |
| - name: Post comment (validation still running) | |
| if: ${{ needs.prepare.result == 'success' && env.VALIDATION_PENDING == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ steps.token.outputs.token }} | |
| run: | | |
| gh pr comment "${PR_NUMBER}" --repo "${REPO}" --body "LavaMoat validation is still running. Please retry \`@metamaskbot update-policies\` after CI validation completes." | |
| - name: Post comment (validation failed but no diffs available) | |
| if: ${{ !cancelled() && env.VALIDATION_FAILED == 'true' && steps.check-diffs.outputs.HAS_DIFFS != 'true' }} | |
| env: | |
| GH_TOKEN: ${{ steps.token.outputs.token }} | |
| run: | | |
| printf 'LavaMoat validation failed but no policy diffs were produced (the validation job may have crashed before generating diffs).\n\nFailed jobs:\n%s' "$FAILED_JOBS" | gh pr comment "${PR_NUMBER}" --repo "${REPO}" --body-file - | |
| - name: Post comment (validation skipped) | |
| if: ${{ needs.prepare.result == 'success' && env.VALIDATION_SKIPPED == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ steps.token.outputs.token }} | |
| run: | | |
| gh pr comment "${PR_NUMBER}" --repo "${REPO}" --body "LavaMoat validation was skipped in CI. Policies were not checked for this commit." | |
| - name: Post comment (no policy changes) | |
| if: ${{ needs.prepare.result == 'success' && env.RUN_ID && env.VALIDATION_FAILED != 'true' && !env.VALIDATION_PENDING && env.VALIDATION_SKIPPED != 'true' }} | |
| env: | |
| GH_TOKEN: ${{ steps.token.outputs.token }} | |
| run: | | |
| gh pr comment "${PR_NUMBER}" --repo "${REPO}" --body "No policy changes" | |
| - name: Post comment if the update failed | |
| if: ${{ (failure() && !(env.VALIDATION_FAILED == 'true' && steps.check-diffs.outputs.HAS_DIFFS != 'true')) || needs.prepare.result == 'failure' }} | |
| env: | |
| GH_TOKEN: ${{ steps.token.outputs.token }} | |
| run: | | |
| gh pr comment "${PR_NUMBER}" --repo "${REPO}" --body "Policy update failed. You can [review the logs or retry the policy update here](${ACTION_RUN_URL})" |