Skip to content

chore: apply least privilege permissions to github actions#40687

Open
yasnagat wants to merge 4 commits into
developfrom
least-privilege-gh-actions
Open

chore: apply least privilege permissions to github actions#40687
yasnagat wants to merge 4 commits into
developfrom
least-privilege-gh-actions

Conversation

@yasnagat

@yasnagat yasnagat commented May 25, 2026

Copy link
Copy Markdown
Member

Proposed changes (including videos or screenshots)

This PR applies the principle of least privilege to GITHUB_TOKEN permissions across all GitHub Actions workflows by setting permissions: {} globally and explicitly re-granting only the minimum permissions required per job, as part of a supply chain security hardening effort.

Workflows relied on GitHub’s default token scopes, which may grant more access than most jobs actually need. Scoping permissions at the job level reduces the attack surface in the event that a GitHub Action, third-party dependency, or CI component is compromised, helping mitigate the impact of potential supply chain attacks and limiting unnecessary repository access.

This change only affects the permissions granted to the workflow GITHUB_TOKEN. Operations authenticated using explicitly configured Personal Access Tokens (PATs) continue to have the scopes assigned to those tokens and are not affected by workflow permissions settings.

Issue(s)

SB-975

Steps to test or reproduce

Further comments

Summary by CodeRabbit

  • Chores
    • Applied explicit, least-privilege GitHub Actions token permissions across all CI/CD workflows to enhance security posture.

@dionisio-bot

dionisio-bot Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot

changeset-bot Bot commented May 25, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 28c4684

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

All GitHub Actions workflows are updated to explicitly define token permissions. Workflow-level permissions: {} disables default token access, and job-level permissions blocks grant only required scopes: contents: read for read operations, contents: write for publishing, pull-requests: read for release workflows, and security-events: write for security scanning.

Changes

Explicit GitHub Actions Permissions

Layer / File(s) Summary
Explicit GitHub Actions token permissions across workflows
.github/workflows/auto-close-duplicates.yml, .github/workflows/ci-code-check.yml, .github/workflows/ci-deploy-gh-pages.yml, .github/workflows/ci-test-e2e.yml, .github/workflows/ci-test-storybook.yml, .github/workflows/ci-test-unit.yml, .github/workflows/ci.yml, .github/workflows/codeql-analysis.yml, .github/workflows/dedupe-issues.yml, .github/workflows/new-release.yml, .github/workflows/pr-update-description.yml, .github/workflows/publish-release.yml, .github/workflows/release-candidate.yml, .github/workflows/update-version-durability.yml
Workflow-level permissions: {} declarations are added to restrict default GITHUB_TOKEN access, and job-level permissions blocks are added to explicitly grant only required scopes. Workflows now follow a consistent least-privilege pattern.

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested labels

type: chore

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'chore: apply least privilege permissions to github actions' accurately summarizes the main change: explicitly setting restrictive GitHub Actions token permissions across all workflows.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • SB-975: Request failed with status code 401

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented May 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 69.74%. Comparing base (f7d47dd) to head (28c4684).
⚠️ Report is 162 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #40687      +/-   ##
===========================================
+ Coverage    69.64%   69.74%   +0.09%     
===========================================
  Files         3338     3327      -11     
  Lines       123232   123134      -98     
  Branches     21989    21963      -26     
===========================================
+ Hits         85830    85883      +53     
+ Misses       34049    33898     -151     
  Partials      3353     3353              
Flag Coverage Δ
e2e 59.37% <ø> (+0.16%) ⬆️
e2e-api 46.14% <ø> (+0.21%) ⬆️
unit 70.46% <ø> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@yasnagat yasnagat changed the title Least privilege gh actions chore: least privilege gh actions May 26, 2026
@yasnagat yasnagat changed the title chore: least privilege gh actions chore: apply least privilege permissions to github actions May 26, 2026
@yasnagat yasnagat marked this pull request as ready for review June 12, 2026 16:25
@yasnagat yasnagat requested a review from a team as a code owner June 12, 2026 16:25
@julio-rocketchat julio-rocketchat added this to the 8.6.0 milestone Jun 12, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 25-26: The workflow default grants repository read to all jobs;
change the workflow-level permissions from "contents: read" to an empty mapping
(permissions: {}) and then add "contents: read" to only the jobs that need it
(e.g., the release-versions job and any jobs that run checkout/git operations).
Update the job definitions (for example, the release-versions job) to include a
job-level permissions block granting contents: read so those jobs retain
required access while other jobs remain least-privilege.

In @.github/workflows/new-release.yml:
- Around line 23-24: The workflow currently passes the PAT to actions/checkout
(via with: token: ${{ secrets.CI_PAT }}) and also sets env: GITHUB_TOKEN at job
level, which persists the PAT for subsequent steps; update the actions/checkout
step(s) that use ${{ secrets.CI_PAT }} to include persist-credentials: false,
remove the workflow/job-level env: GITHUB_TOKEN: ${{ secrets.CI_PAT }}, and
instead pass the PAT only to the specific step(s) that need it (e.g., the
./packages/release-action step) by setting env or with on that step alone so
checkout doesn't leave a PAT-backed credential available to later steps.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3af1d1cc-4784-4269-a2f3-8dbd7107970d

📥 Commits

Reviewing files that changed from the base of the PR and between 10266c1 and 28c4684.

📒 Files selected for processing (14)
  • .github/workflows/auto-close-duplicates.yml
  • .github/workflows/ci-code-check.yml
  • .github/workflows/ci-deploy-gh-pages.yml
  • .github/workflows/ci-test-e2e.yml
  • .github/workflows/ci-test-storybook.yml
  • .github/workflows/ci-test-unit.yml
  • .github/workflows/ci.yml
  • .github/workflows/codeql-analysis.yml
  • .github/workflows/dedupe-issues.yml
  • .github/workflows/new-release.yml
  • .github/workflows/pr-update-description.yml
  • .github/workflows/publish-release.yml
  • .github/workflows/release-candidate.yml
  • .github/workflows/update-version-durability.yml
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Hacktron Security Check
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-04-27T18:32:21.871Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 40321
File: .github/workflows/ci.yml:137-145
Timestamp: 2026-04-27T18:32:21.871Z
Learning: In .github/workflows/ci.yml, the `diff` step under `release-versions` intentionally uses a bash `if` with `gh pr diff ... | grep -q ...; then ... fi`. For non-`pull_request` workflow triggers where `GH_PR_NUM` can be empty, the `gh` command may fail, but the surrounding bash `if` is relied on to treat that failure as the condition being false and skip the `then` block, allowing the step/job to exit cleanly. Do not add extra guards for non-PR event types unless this failure/skip behavior is intentionally changed.

Applied to files:

  • .github/workflows/ci.yml
🔇 Additional comments (10)
.github/workflows/auto-close-duplicates.yml (1)

8-16: LGTM!

.github/workflows/ci-code-check.yml (1)

16-22: LGTM!

.github/workflows/ci-deploy-gh-pages.yml (1)

11-17: LGTM!

.github/workflows/ci-test-e2e.yml (1)

71-77: LGTM!

.github/workflows/ci-test-storybook.yml (1)

22-28: LGTM!

.github/workflows/ci-test-unit.yml (1)

26-32: LGTM!

.github/workflows/ci.yml (1)

32-34: LGTM!

.github/workflows/codeql-analysis.yml (1)

15-18: LGTM!

.github/workflows/dedupe-issues.yml (1)

13-14: LGTM!

Also applies to: 19-21

.github/workflows/update-version-durability.yml (1)

16-17: Permission change is safe for publishing (local action doesn’t rely on checkout’s push remote).

.github/actions/update-version-durability/index.js publishes via Document360 (D360_TOKEN) and only reads GitHub releases via Octokit using the provided GH_TOKEN; there’s no git push/remote configuration in the action, so contents: read on GITHUB_TOKEN doesn’t affect the publish path.

Comment thread .github/workflows/ci.yml
Comment on lines +23 to +24
permissions: {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does actions/checkout persist the provided token in local git config by default, and what changes when persist-credentials is set to false?

💡 Result:

Yes, the actions/checkout action persists the provided authentication token (or SSH key) in the local git configuration by default to enable subsequent authenticated git commands during the job [1][2][3]. Regarding how it is persisted, recent versions of the action have improved security by storing these credentials in a separate file located under $RUNNER_TEMP, rather than writing them directly into the.git/config file [1][4][5]. Regardless of the storage location, the action automatically removes these credentials during the post-job cleanup process [1][6][3]. When you set persist-credentials to false, the action will not configure the token or SSH key in the local git configuration [1][3]. This is primarily used as an opt-out mechanism if you do not want the runner to have authenticated git access for subsequent steps or if you prefer to manage credentials manually [1][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

files=(
  ".github/workflows/new-release.yml"
  ".github/workflows/pr-update-description.yml"
  ".github/workflows/publish-release.yml"
  ".github/workflows/release-candidate.yml"
)

for f in "${files[@]}"; do
  echo "===== $f ====="
  if [[ ! -f "$f" ]]; then
    echo "MISSING: $f"
    continue
  fi
  # show permissions block too
  rg -n "permissions:" "$f" || true

  # show checkout steps and any token usage
  rg -n "uses:\s*actions/checkout|with:|persist-credentials|CI_PAT|secrets\.CI_PAT|GITHUB_TOKEN" "$f" -n || true

  # print small surrounding context for checkout-related lines
  rg -n "uses:\s*actions/checkout" "$f" | while IFS=: read -r ln _; do
    start=$((ln-5)); end=$((ln+25))
    echo "--- context around checkout at line $ln (show $start-$end) ---"
    sed -n "${start},${end}p" "$f" | nl -ba -w2 -s":"
  done
done

Repository: RocketChat/Rocket.Chat

Length of output: 495


🏁 Script executed:

#!/bin/bash
set -euo pipefail

files=(
  ".github/workflows/new-release.yml"
  ".github/workflows/pr-update-description.yml"
  ".github/workflows/publish-release.yml"
  ".github/workflows/release-candidate.yml"
)

print_context () {
  local f="$1" line="$2" start="$3" end="$4"
  echo "--- context around '$f:$line' (show $start-$end) ---"
  # use awk to print line numbers since nl may be unavailable
  awk -v s="$start" -v e="$end" 'NR>=s && NR<=e { printf "%4d:%s\n", NR, $0 }' "$f"
}

for f in "${files[@]}"; do
  echo
  echo "===== $f ====="
  if [[ ! -f "$f" ]]; then
    echo "MISSING: $f"
    continue
  fi

  echo "-- permissions blocks --"
  rg -n "^\\s*permissions:|permissions:" "$f" || true

  echo "-- checkout + token usage --"
  rg -n "uses:\\s*actions/checkout@|with:|persist-credentials|token:\\s*\\$\\{\\{\\s*secrets\\.CI_PAT\\s*\\}\\}|secrets\\.CI_PAT|GITHUB_TOKEN:" "$f" || true

  echo "-- persist-credentials setting (if any) --"
  rg -n "persist-credentials" "$f" || true

  echo "-- checkout steps (with surrounding context) --"
  rg -n "uses:\\s*actions/checkout@" "$f" | while IFS=: read -r ln rest; do
    start=$((ln-8)); end=$((ln+30))
    (( start<1 )) && start=1
    print_context "$f" "$ln" "$start" "$end"
  done

  echo "-- git write-like commands after checkout (best-effort) --"
  # crude scan: show lines containing common write actions
  rg -n "\\bgit\\b|git (push|tag|commit|fetch|config|remote|checkout)" "$f" || true
done

Repository: RocketChat/Rocket.Chat

Length of output: 6700


Reduce PAT blast radius in checkout across release workflows: these workflows set workflow-level permissions: {} (job contents: read), but still pass with: token: ${{ secrets.CI_PAT }} to actions/checkout (no persist-credentials: false), and later set env: GITHUB_TOKEN: ${{ secrets.CI_PAT }}—so checkout credential persistence leaves a broader PAT-backed git auth available for subsequent steps rather than limiting write access.

  • Suggested fix (apply to all four): add persist-credentials: false to the actions/checkout step that uses ${{ secrets.CI_PAT }}, and pass the PAT only to the specific ./packages/release-action step(s) that require it.
    • .github/workflows/new-release.yml (~32-36, ~57)
    • .github/workflows/pr-update-description.yml (~20-24, ~42)
    • .github/workflows/publish-release.yml (~23-26, ~46)
    • .github/workflows/release-candidate.yml (~14-19, ~39)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/new-release.yml around lines 23 - 24, The workflow
currently passes the PAT to actions/checkout (via with: token: ${{
secrets.CI_PAT }}) and also sets env: GITHUB_TOKEN at job level, which persists
the PAT for subsequent steps; update the actions/checkout step(s) that use ${{
secrets.CI_PAT }} to include persist-credentials: false, remove the
workflow/job-level env: GITHUB_TOKEN: ${{ secrets.CI_PAT }}, and instead pass
the PAT only to the specific step(s) that need it (e.g., the
./packages/release-action step) by setting env or with on that step alone so
checkout doesn't leave a PAT-backed credential available to later steps.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 14 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".github/workflows/ci-test-e2e.yml">

<violation number="1" location=".github/workflows/ci-test-e2e.yml:77">
P2: Grant `actions: read` to this job. The telemetry step needs it even with `comment_on_pr: false`, so the perf path can fail after the workflow token is locked down.</violation>
</file>

<file name=".github/workflows/ci.yml">

<violation number="1" location=".github/workflows/ci.yml:26">
P2: This workflow uses `permissions: contents: read` at the workflow level, but all other workflows in this PR correctly use `permissions: {}`. Workflow-level permissions apply as a default to any job that doesn't declare its own `permissions` block, so this grants `contents: read` to jobs that may not need GitHub API access. For consistency with the least-privilege approach, change this to `permissions: {}` and ensure each job that needs `contents: read` declares it explicitly (as `release-versions` already does).</violation>
</file>

Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.

Re-trigger cubic

test:
runs-on: ubuntu-24.04
permissions:
contents: read

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Grant actions: read to this job. The telemetry step needs it even with comment_on_pr: false, so the perf path can fail after the workflow token is locked down.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/ci-test-e2e.yml, line 77:

<comment>Grant `actions: read` to this job. The telemetry step needs it even with `comment_on_pr: false`, so the perf path can fail after the workflow token is locked down.</comment>

<file context>
@@ -68,9 +68,13 @@ env:
   test:
     runs-on: ubuntu-24.04
+    permissions:
+      contents: read
 
     env:
</file context>
Suggested change
contents: read
actions: read
contents: read

Comment thread .github/workflows/ci.yml
TOOL_NODE_FLAGS: ${{ vars.TOOL_NODE_FLAGS }}

permissions:
contents: read

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This workflow uses permissions: contents: read at the workflow level, but all other workflows in this PR correctly use permissions: {}. Workflow-level permissions apply as a default to any job that doesn't declare its own permissions block, so this grants contents: read to jobs that may not need GitHub API access. For consistency with the least-privilege approach, change this to permissions: {} and ensure each job that needs contents: read declares it explicitly (as release-versions already does).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/ci.yml, line 26:

<comment>This workflow uses `permissions: contents: read` at the workflow level, but all other workflows in this PR correctly use `permissions: {}`. Workflow-level permissions apply as a default to any job that doesn't declare its own `permissions` block, so this grants `contents: read` to jobs that may not need GitHub API access. For consistency with the least-privilege approach, change this to `permissions: {}` and ensure each job that needs `contents: read` declares it explicitly (as `release-versions` already does).</comment>

<file context>
@@ -22,10 +22,16 @@ concurrency:
   TOOL_NODE_FLAGS: ${{ vars.TOOL_NODE_FLAGS }}
 
+permissions:
+  contents: read
+
 jobs:
</file context>

@julio-rocketchat julio-rocketchat modified the milestones: 8.6.0, 8.7.0 Jun 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants