Skip to content

chore(ci): migrate from PAT to GitHub App token [SEC-58]#4934

Merged
maheshkutty merged 17 commits intodevelopfrom
chore/SEC-58-migrate-pat-to-github-app-token
Mar 10, 2026
Merged

chore(ci): migrate from PAT to GitHub App token [SEC-58]#4934
maheshkutty merged 17 commits intodevelopfrom
chore/SEC-58-migrate-pat-to-github-app-token

Conversation

@lvrach
Copy link
Member

@lvrach lvrach commented Jan 28, 2026

Summary

Migrates ALL workflow authentication from secrets.PAT to GitHub App tokens or GITHUB_TOKEN, using the new simplified pattern with GitHub API for branch creation.

Generated by Claude Code - This PR is part of a batch migration running across ~130 RudderStack repositories.

Linear: SEC-58
Notion: Eliminate Github Bot Users

Changes Summary

Files Migrated to GitHub App Token (9 files)

These workflows create PRs, push branches, or perform cross-repo operations:

File Token Type Permissions
validate-actor.yml App Token permission-members: read (org membership check)
draft-new-release.yml App Token contents: write, pull-requests: write + signed commits
publish-new-release.yml App Token contents: write, pull-requests: write
create-hotfix-branch.yml App Token contents: write (branch creation)
prepare-for-prod-dt-deploy.yml App Token contents: write, pull-requests: write + signed commits, cross-repo: rudder-devops, rudderstack-operator
prepare-for-prod-ut-deploy.yml App Token contents: write, pull-requests: write, cross-repo: rudder-devops
prepare-for-prod-rollback.yml App Token contents: write, pull-requests: write, cross-repo: rudder-devops, rudderstack-operator
prepare-for-staging-deploy.yml App Token contents: write, pull-requests: write, cross-repo: rudder-devops
update-ingestion-service.yml App Token contents: write, pull-requests: write, cross-repo: rudder-ingestion-svc
ingestion-service-test.yml App Token contents: read, cross-repo: rudder-ingestion-svc

Files Migrated to GITHUB_TOKEN (2 files)

These workflows only read data or perform non-triggering operations:

File Token Type Reason
housekeeping.yml GITHUB_TOKEN Labels/closes stale items, deletes branches (doesn't trigger workflows)
dt-test-and-report-code-coverage.yml GITHUB_TOKEN SonarCloud scan (read-only)

Files Updated (Caller Workflows)

File Change
build-pr-artifacts.yml Removed PAT secret from ingestion-service-test call

Architectural Improvements (January 2026 Guidelines)

✅ Permissions at Job Level

  • Moved all permissions from workflow level to job level
  • Each job only gets the permissions it needs
  • More secure and self-documenting

✅ App Token Generated Early

  • Token generation is the first step after harden-runner
  • App Token used for ALL operations (checkout, commits, PRs)
  • Prevents credential confusion

✅ Minimal GITHUB_TOKEN Permissions

  • Set to contents: read at job level
  • App Token handles all write operations
  • Reduces attack surface

✅ Cross-Repo Access via repositories:

  • Explicitly declared for cross-repo workflows
  • Clear documentation of dependencies

✅ Verified Commits via GitHub API

  • NEW: Replaced git push with ryancyq/github-signed-commit action
  • Commits created via GitHub API are verified (green checkmark)
  • Applies to:
    • draft-new-release.yml: Release version bumps and CHANGELOG updates
    • prepare-for-prod-dt-deploy.yml: Cross-repo deployment commits

Why this matters:

  • Git push with ANY token (PAT, GITHUB_TOKEN, or App Token) creates unverified commits
  • Only GitHub API creates verified commits
  • Verified commits improve security posture and audit trail

Code Simplification (This Update)

✅ Migration Pattern Evolution

This update further simplifies the workflows by removing unnecessary git commands:

Removed (No Longer Needed):

  • git config user.name/email in main repo workflows (App identity used instead)
  • git push --set-upstream origin (replaced with GitHub API)

Added (API-based approach):

  • ✅ Branch creation via GitHub API: gh api repos/.../git/refs
  • ✅ Git config moved to cloned repos only (where commits actually happen)

Why this is better:

  1. The signed-commit action (ryancyq/github-signed-commit) creates commits via GitHub's GraphQL API, producing verified commits with the App's identity
  2. This eliminates the need for git config in the main repo
  3. Branches are created via GitHub API before the signed-commit action pushes to them
  4. Git config is only needed in cloned repos (rudder-devops, rudderstack-operator) where traditional git commands are still used

Files Simplified

  • draft-new-release.yml: Removed git config, replaced git push --set-upstream with GitHub API
  • prepare-for-staging-deploy.yml: Removed git config from main flow, moved to cloned repo
  • prepare-for-prod-rollback.yml: Removed git config from main flow, moved to cloned repos
  • prepare-for-prod-ut-deploy.yml: Removed git config from main flow, moved to cloned repo

Why This Migration?

  1. Bot account deprecation: RudderStack is deprecating bot user accounts
  2. Better security: Short-lived tokens with granular, scoped permissions
  3. Audit trail: Better tracking of which automation made changes
  4. Triggers downstream workflows: Unlike GITHUB_TOKEN, app tokens trigger PR checks
  5. Verified commits: GitHub API integration ensures commits are verified
  6. Simpler code: Fewer git commands, cleaner workflows

Test Plan

  • Trigger draft-new-release workflow manually
  • Verify release PR creation works with verified commits
  • Verify branch creation via GitHub API works correctly
  • Trigger housekeeping workflow
  • Verify cross-repo workflows (prod deploy, rollback) with verified commits
  • Check all workflow logs for authentication errors

🤖 Generated with Claude Code

@lvrach lvrach requested a review from a team as a code owner January 28, 2026 22:23
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 28, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'auto_resolve_threads'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

Replaces stored PAT usage with short-lived GitHub App tokens across many workflows, moves top-level permissions to per-job granular permissions, inserts runner hardening steps, and sequences token generation before git/PR/tag operations; several workflows switch some local git pushes to API-driven signed commits/tags. (≤50 words)

Changes

Cohort / File(s) Summary
Auth & token generation
\.github/workflows/validate-actor.yml, \.github/workflows/ingestion-service-test.yml, \.github/workflows/update-ingestion-service.yml, \.github/workflows/prepare-for-prod-dt-deploy.yml, \.github/workflows/prepare-for-prod-rollback.yml, \.github/workflows/prepare-for-prod-ut-deploy.yml, \.github/workflows/prepare-for-staging-deploy.yml, \.github/workflows/draft-new-release.yml, \.github/workflows/publish-new-release.yml, \.github/workflows/create-hotfix-branch.yml, \.github/workflows/build-pr-artifacts.yml
Removed secrets.PAT inputs/usages; added Generate GitHub App Token steps (actions/create-github-app-token) and replaced PAT references with steps.generate-token.outputs.token for GITHUB_TOKEN/GH_TOKEN, checkout/clone, push, tag, and PR operations.
Permissions & scoping
\.github/workflows/create-hotfix-branch.yml, \.github/workflows/draft-new-release.yml, \.github/workflows/publish-new-release.yml, \.github/workflows/prepare-for-*.yml, \.github/workflows/update-ingestion-service.yml, \.github/workflows/*
Removed or reduced top-level workflow permissions; introduced per-job permissions blocks (commonly contents: read, id-token: write where needed) to scope access.
Runner hardening & sequencing
\.github/workflows/create-hotfix-branch.yml, \.github/workflows/prepare-for-prod-*.yml, \.github/workflows/update-ingestion-service.yml, \.github/workflows/ingestion-service-test.yml, \.github/workflows/publish-new-release.yml
Inserted "Harden the runner" steps before network/auth actions and ensured token generation steps run before any git/PR/tag operations.
API-driven flows & signed commits/tags
\.github/workflows/draft-new-release.yml, \.github/workflows/prepare-for-prod-dt-deploy.yml, \.github/workflows/prepare-for-prod-ut-deploy.yml, \.github/workflows/prepare-for-prod-rollback.yml, \.github/workflows/publish-new-release.yml, \.github/workflows/prepare-for-staging-deploy.yml
Replaced some local git push flows with API-based branch/tag/PR creation and added signed-commit/signed-tag steps; commit/PR creation now often uses GitHub API authenticated by generated token.
Per-job permission migrations & minor edits
\.github/workflows/dt-test-and-report-code-coverage.yml, \.github/workflows/housekeeping.yml, \.github/workflows/build-pr-artifacts.yml, \.github/workflows/check-pr-title.yml, \.github/workflows/commitlint.yml, \.github/workflows/integrations_version_audit.yml, \.github/workflows/prepare-for-dev-deploy.yml, \.github/workflows/slack-notify.yml, \.github/workflows/ut-tests.yml, \.github/workflows/verify-server-start.yml, \.github/workflows/verify.yml, \.github/workflows/build-push-docker-image.yml
Moved global permissions to per-job permissions; replaced a few isolated secrets.PAT usages with secrets.GITHUB_TOKEN or removed PAT from inputs; no substantial step logic changes besides permission/token wiring.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Runner as Actions Runner
  participant CreateToken as actions/create-github-app-token
  participant GH as GitHub API
  participant RepoOps as git/gh/checkout/actions
  participant Job as Downstream Job Steps

  Runner->>CreateToken: invoke with app-id & private-key
  CreateToken->>GH: request installation token (permissions)
  GH-->>CreateToken: return installation token
  CreateToken-->>Runner: outputs.token (rgba(0,128,255,0.5))
  Runner->>RepoOps: perform checkout/clone/commit/push/PR using outputs.token
  RepoOps->>GH: authenticated repo/content API calls
  GH-->>RepoOps: responses (clone/push/PR results)
  Runner->>Job: run downstream steps with GITHUB_TOKEN/GH_TOKEN = outputs.token
  Job->>GH: additional authenticated API calls (tags, releases, PRs)
  GH-->>Job: responses
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggestions for Improvement

  1. Consolidate token generation + runner hardening into a reusable composite action to reduce duplicated steps.
  2. Narrow generated token permissions per-job to least privilege and document required scopes inline.
  3. Standardize signed-commit/tag helpers (composite action) and retry/backoff for API calls.
  4. Centralize git user config into a shared step to avoid repeated inline config across workflows.
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main objective of the PR—migrating workflow authentication from PAT to GitHub App tokens, with a reference to the security ticket [SEC-58].
Description check ✅ Passed The description is comprehensive and well-structured, covering changes summary, architectural improvements, code simplification, rationale, and test plan. All required template sections are addressed with detailed information about the migration scope and benefits.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/SEC-58-migrate-pat-to-github-app-token

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @.github/workflows/validate-actor.yml:
- Around line 24-31: The reusable workflow doesn't expose the
RELEASE_PRIVATE_KEY secret to callers via on.workflow_call, so the
create-github-app-token step (id: generate-token) will receive an empty secret;
update the workflow's on.workflow_call section to declare a secrets block that
includes RELEASE_PRIVATE_KEY (and any other required secrets like RELEASE_APP_ID
if not already declared) so callers can pass it or inherit secrets; ensure the
secret name exactly matches RELEASE_PRIVATE_KEY and remove any mismatch to allow
the generate-token step to access the private key at runtime.
- Around line 11-13: Remove the unnecessary id-token permission from the
workflow permissions block: delete the "id-token: write" entry under
"permissions" while keeping "contents: read" intact; this workflow uses GitHub
App auth via actions/create-github-app-token (app-id/private-key) and does not
require OIDC, so removing "id-token: write" enforces least-privilege.

@codecov
Copy link

codecov bot commented Jan 28, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.27%. Comparing base (a551b35) to head (6af63bc).
⚠️ Report is 10 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #4934      +/-   ##
===========================================
- Coverage    92.28%   92.27%   -0.01%     
===========================================
  Files          655      657       +2     
  Lines        35851    35927      +76     
  Branches      8425     8473      +48     
===========================================
+ Hits         33086    33153      +67     
+ Misses        2548     2536      -12     
- Partials       217      238      +21     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @.github/workflows/ingestion-service-test.yml:
- Around line 34-40: The workflow references secrets.RELEASE_PRIVATE_KEY in the
Generate GitHub App Token step (id: generate-token, uses:
actions/create-github-app-token) but the reusable-workflow input secrets
(on.workflow_call.secrets) only declares DOCKERHUB_TOKEN; add
RELEASE_PRIVATE_KEY to the on.workflow_call.secrets block so callers can pass it
in, and ensure any documentation/README for the reusable workflow notes the
required secret name and purpose.

In @.github/workflows/update-ingestion-service.yml:
- Line 8: The reusable workflow declares workflow_call but never exposes the
secret used later; add a secrets entry under workflow_call to declare
RELEASE_PRIVATE_KEY (e.g. under workflow_call.secrets include
RELEASE_PRIVATE_KEY with required: true or appropriate settings) so steps that
reference secrets.RELEASE_PRIVATE_KEY can access it, or remove the workflow_call
block if the workflow is not intended to be reusable; reference the
workflow_call section and the RELEASE_PRIVATE_KEY secret in your change.
- Around line 43-46: The workflow token scope is limited to
"rudder-ingestion-svc" causing the git push of refs/tags/$GO_TAG (and other
checkout/git operations) to be denied; update the permissions block to include
the current repo (add the current repository name alongside
"rudder-ingestion-svc") and ensure the generated app token is used for checkout
and pushes by wiring it into the actions/checkout and subsequent git commands
(use the token as the checkout "token" input or set GIT_AUTH
environment/credential helper before running git push). Target the permissions
block and the steps that call actions/checkout and the git push
(refs/tags/$GO_TAG) so the generated token has write access to the current repo
and is actually applied to those steps.
🧹 Nitpick comments (1)
.github/workflows/prepare-for-prod-rollback.yml (1)

56-58: Consider: Token in clone URL may appear in logs.

The token embedded in the git clone URL is standard practice, but be aware that GitHub Actions masks secrets in logs. Ensure the token output is properly registered as a secret by the create-github-app-token action (it is by default).

As an alternative, you could use git credential helper:

Optional: Use credential helper instead of URL-embedded token
      - name: Clone Devops Repo
        run: |
+          git config --global url."https://x-access-token:${{ steps.generate-token.outputs.token }}@github.com/".insteadOf "https://github.com/"
-          git clone https://${{ steps.generate-token.outputs.token }}@github.com/rudderlabs/rudder-devops.git
+          git clone https://github.com/rudderlabs/rudder-devops.git

This sets up credentials globally rather than per-URL, which some consider cleaner. However, the current approach works well and is widely used.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @.github/workflows/draft-new-release.yml:
- Around line 30-38: Update the GitHub Action invocation in the "Generate GitHub
App Token" step (id: generate-token) to use the latest stable release of
actions/create-github-app-token by changing the uses reference from
actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42
(v2.1.4) to actions/create-github-app-token@v2.2.1; keep the existing with:
inputs (app-id, private-key, permission-contents, permission-pull-requests)
unchanged and preserve or update the comment to reflect the new version/SHA pin
as needed for supply-chain integrity.

In @.github/workflows/prepare-for-prod-dt-deploy.yml:
- Around line 137-156: The ryancyq/github-signed-commit step fails because it
expects the target branch to already exist remotely; before invoking the action
(the steps using ryancyq/github-signed-commit with branch-name
shared-transformer-${{ env.TAG_NAME }}), create and push the branch using the
generated token: run git checkout -b shared-transformer-${{ env.TAG_NAME }} and
git push -u origin shared-transformer-${{ env.TAG_NAME }} (using GH_TOKEN from
steps.generate-token.outputs.token) so the action can commit; apply the same
change for the other two occurrences where ryancyq/github-signed-commit is used.

In @.github/workflows/prepare-for-staging-deploy.yml:
- Around line 125-130: Update the git clone URL to use the documented GitHub App
token username placeholder by changing the existing git clone invocation that
uses steps.generate-token.outputs.token (the line starting with "git clone
https://${{ steps.generate-token.outputs.token }}@github.com/...") so it uses
"x-access-token" as the username and the token as the password (i.e.,
"https://x-access-token:${{ steps.generate-token.outputs.token
}}@github.com/..."), keeping the rest of the run block (cd rudder-devops, git
config user.name, git config user.email) unchanged.
🧹 Nitpick comments (1)
.github/workflows/draft-new-release.yml (1)

89-98: Consider handling pre-existing branch scenario.

If this workflow is re-triggered while a release branch already exists (e.g., from a previous partial run), the API call will fail with a 422 error. While the error is clear, you could make this more robust.

💡 Optional: Check for existing branch before creation
       - name: Create Release Branch via GitHub API
         env:
           GH_TOKEN: ${{ steps.generate-token.outputs.token }}
         run: |
           BASE_SHA=$(git rev-parse origin/main)
+          BRANCH_NAME="${{ steps.create-release.outputs.branch_name }}"
+          
+          # Check if branch already exists
+          if gh api repos/${{ github.repository }}/git/refs/heads/$BRANCH_NAME --silent 2>/dev/null; then
+            echo "Branch $BRANCH_NAME already exists, deleting and recreating..."
+            gh api repos/${{ github.repository }}/git/refs/heads/$BRANCH_NAME --method DELETE
+          fi
+          
           gh api repos/${{ github.repository }}/git/refs \
             --method POST \
-            -f ref="refs/heads/${{ steps.create-release.outputs.branch_name }}" \
+            -f ref="refs/heads/$BRANCH_NAME" \
             -f sha="$BASE_SHA"

Copy link
Member Author

@lvrach lvrach left a comment

Choose a reason for hiding this comment

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

Review: SEC-58 Migration PR

Addressing @maheshkutty's Question

"Is the job level permission is required?" / "I think workflow level permission is sufficient"

Answer: Both approaches work, but there's a key issue with how this PR mixes them:

The Problem: When job-level permissions are specified, they completely override workflow-level permissions (they don't merge). So having both creates confusion:

# Current PR pattern (problematic):
permissions:        # workflow level
  id-token: write   # ← This is ignored when job has permissions
  contents: read

jobs:
  my-job:
    permissions:    # job level - OVERRIDES workflow entirely
      contents: read

Recommendation: Pick ONE approach consistently:

  • Option A (Job-level only): Remove all workflow-level permissions: blocks, keep job-level only
  • Option B (Workflow-level only): Remove job-level permissions: blocks, use workflow-level

Either way: Remove id-token: write - it's unused since this PR uses GitHub App auth (not AWS OIDC).


Critical Issues Found (via CodeRabbit + manual review)

1. Missing Secret Declarations in Reusable Workflows

The following reusable workflows use secrets.RELEASE_PRIVATE_KEY but don't declare it in on.workflow_call.secrets:

File Issue
validate-actor.yml Missing RELEASE_PRIVATE_KEY declaration
ingestion-service-test.yml Missing RELEASE_PRIVATE_KEY declaration
update-ingestion-service.yml Missing RELEASE_PRIVATE_KEY declaration

Impact: When invoked as reusable workflows, the secret will be empty and token generation will fail.

Fix: Add to each file:

on:
  workflow_call:
    secrets:
      RELEASE_PRIVATE_KEY:
        required: true

2. Token Scope Too Narrow in update-ingestion-service.yml

repositories: rudder-ingestion-svc  # ← Missing current repo!

But the workflow pushes tags to rudder-transformer. The token won't have write access.

Fix: Add current repo:

repositories: rudder-transformer,rudder-ingestion-svc

3. Missing Branch Creation Before Signed Commits

ryancyq/github-signed-commit requires the target branch to already exist on remote. In prepare-for-prod-dt-deploy.yml, branches are created locally but not pushed before the action runs.

Affected locations: Lines 137-156, 170-186, 204-220

Fix: Add explicit git push -u origin <branch> before each signed-commit step.

4. Git Clone URL Format

Missing x-access-token: username prefix:

# Current (may fail):
git clone https://${{ token }}@github.com/...

# Correct:
git clone https://x-access-token:${{ token }}@github.com/...

Affected files: prepare-for-prod-dt-deploy.yml, prepare-for-prod-rollback.yml, prepare-for-staging-deploy.yml

5. Unused id-token: write Permission

Remove from all workflows - not needed for GitHub App authentication:

  • validate-actor.yml
  • prepare-for-prod-dt-deploy.yml
  • prepare-for-prod-ut-deploy.yml
  • prepare-for-prod-rollback.yml
  • prepare-for-staging-deploy.yml

Summary

Priority Issue Files Affected
🔴 Critical Missing secret declarations 3 reusable workflows
🔴 Critical Token scope excludes current repo update-ingestion-service.yml
🟠 Major Missing branch creation before signed commits prepare-for-prod-dt-deploy.yml
🟠 Major Git clone URL format 3 deploy workflows
🟡 Minor Unused id-token permission 5 workflows
🟡 Minor Mixed permission levels All modified workflows

Recommendation: Address the critical issues before merge. The workflows will fail at runtime without these fixes.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @.github/workflows/prepare-for-prod-rollback.yml:
- Around line 42-47: The debug echo in the "Get Target Version" step (id:
target-version) uses an undefined variable $tag_name; replace that reference
with the correctly-assigned local variable $version (or reference the output via
the GITHUB_OUTPUT variable) so the line uses $version when printing the Target
Version.
- Around line 10-13: The reusable workflow call to validate-actor (uses:
./.github/workflows/validate-actor.yml) is missing the required
RELEASE_PRIVATE_KEY secret; update the caller that defines the validate-actor
invocation so it forwards the secret under the secrets block (e.g., add secrets:
RELEASE_PRIVATE_KEY: ${{ secrets.RELEASE_PRIVATE_KEY }} alongside the existing
with: team_names) so the token generation in validate-actor.yml can access
RELEASE_PRIVATE_KEY.
🧹 Nitpick comments (2)
.github/workflows/prepare-for-prod-ut-deploy.yml (1)

116-117: Consider consolidating git config setup.

Git user configuration is repeated in three separate steps (lines 116-117, 149-150, 169-170). Since all steps operate in the same rudder-devops clone, you could set the config once immediately after cloning.

♻️ Optional consolidation

Add a dedicated step after cloning:

      - name: Configure Git User
        run: |
          cd rudder-devops
          git config user.name "GitHub Actions"
          git config user.email "noreply@github.com"

Then remove the git config lines from the subsequent steps.

Also applies to: 149-150, 169-170

.github/workflows/prepare-for-staging-deploy.yml (1)

113-116: Remove unnecessary checkout step.

The Checkout step at lines 113-116 is not used in the create-pull-request job. The tag names (TAG_NAME, UT_TAG_NAME) are passed via job outputs, and all file operations occur within the separately-cloned rudder-devops repository. Removing this step would reduce workflow execution time and clarify the job's intent.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @.github/workflows/draft-new-release.yml:
- Around line 90-98: The release branch is being created from origin/main
(BASE_SHA) which drops develop/hotfix commits; change the GH API call that
creates the ref for the branch_name (steps.create-release.outputs.branch_name)
to use the current checked-out commit (HEAD) or the computed source_branch_name
instead of origin/main so the new release branch includes the source branch's
commits; locate the block that sets BASE_SHA and the gh api POST (using BASE_SHA
and ref="refs/heads/${{ steps.create-release.outputs.branch_name }}") and
replace BASE_SHA with the SHA of HEAD (or derive the SHA from source_branch_name
output) so the created branch preserves develop/hotfix changes.

In @.github/workflows/prepare-for-prod-dt-deploy.yml:
- Around line 203-205: The checkout currently uses the branch name extracted
from rudder-devops (steps.extract_branch_name.outputs.branch_name) when creating
the new branch in the operator repo (git checkout -b
dedicated-transformer-$TAG_NAME
${{steps.extract_branch_name.outputs.branch_name}}), which will fail if the
operator repo’s default branch differs; change the workflow to determine the
operator repo’s actual default branch after cloning (e.g., run a command to
resolve origin's HEAD or query the repo via GitHub API) and use that resolved
branch name for the git checkout instead of reusing
steps.extract_branch_name.outputs.branch_name, or alternatively ensure both
repos share the same default branch name.

In @.github/workflows/prepare-for-prod-rollback.yml:
- Around line 43-48: The "Get Target Version" step (id: target-version) assigns
version from github.ref_name without quoting (version=${{ github.ref_name }})
which can allow shell metacharacter interpretation; change that and any similar
assignments that interpolate GitHub context or step outputs to use quoted form
(e.g., version="${{ github.ref_name }}") and likewise quote assignments like
branch="${{ steps.<id>.outputs.<name> }}" so interpolated values are treated as
single strings and cannot perform shell injection. Ensure any direct shell
references to these vars later also use quoted expansions (e.g., "$version") to
avoid word-splitting.
🧹 Nitpick comments (7)
.github/workflows/prepare-for-dev-deploy.yml (1)

16-17: Job-level permissions are well-scoped — looks good.

Each job declares only the permissions it actually needs: contents: read for checkout, and id-token: write exclusively on jobs that perform AWS OIDC authentication (build and restart jobs). The generate-tag-names job correctly omits id-token: write since it has no cloud interaction. This aligns well with the least-privilege principle the PR is targeting.

One minor suggestion: consider adding a top-level permissions: {} (empty) as a safety net, so any future job added without explicit permissions defaults to no access rather than the GitHub default (contents: read for most event types, broader for some). This is a common hardening pattern recommended by StepSecurity and the OpenSSF Scorecard.

 on:
   push:
     branches:
       - develop
 
+permissions: {}
+
 concurrency:

,

Also applies to: 45-47, 63-65, 81-83, 107-109

.github/workflows/create-hotfix-branch.yml (1)

44-49: Consider replacing the third-party branch-creation action with a direct GitHub API call.

The PR objectives mention adopting "a simplified pattern that uses the GitHub API for branch creation." Other migrated workflows appear to use this approach. Using gh api or octokit/request-action directly would remove the dependency on peterjgrainger/action-create-branch (which has limited maintenance activity) and keep the approach consistent across workflows.

♻️ Example using the GitHub CLI
      - name: Create Branch
-       uses: peterjgrainger/action-create-branch@10c7d268152480ae859347db45dc69086cef1d9c
-       env:
-         GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
-       with:
-         branch: 'hotfix/${{ inputs.hotfix_name }}'
+       env:
+         GH_TOKEN: ${{ steps.generate-token.outputs.token }}
+       run: |
+         sha=$(gh api repos/${{ github.repository }}/git/ref/heads/main --jq '.object.sha')
+         gh api repos/${{ github.repository }}/git/refs \
+           --method POST \
+           -f ref="refs/heads/hotfix/${{ inputs.hotfix_name }}" \
+           -f sha="$sha"
.github/workflows/prepare-for-staging-deploy.yml (1)

127-162: Consider quoting shell variables to guard against word-splitting.

The shell script works correctly for well-formed version strings, but several variables ($BRANCH_NAME, $TAG_NAME, $UT_TAG_NAME) are used unquoted in contexts where word-splitting or glob expansion could cause unexpected behavior if a value ever contained spaces or special characters. While the current inputs are likely safe (derived from package.json version), defensive quoting is a low-cost improvement.

For example:

Suggested quoting improvements
           BRANCH_NAME="shared-transformer-$TAG_NAME"
           echo $BRANCH_NAME
-          if [ -n "$(git ls-remote --heads origin $BRANCH_NAME 2>/dev/null)" ]
+          if [ -n "$(git ls-remote --heads origin "$BRANCH_NAME" 2>/dev/null)" ]
           then
             echo "Staging deployment branch already exists!"
           else
-            git checkout -b $BRANCH_NAME
+            git checkout -b "$BRANCH_NAME"
             ...
-            git push -u origin $BRANCH_NAME
+            git push -u origin "$BRANCH_NAME"
.github/workflows/prepare-for-prod-rollback.yml (1)

86-93: Redundant git config for the same repo — harmless but worth noting.

Lines 91–92 re-set user.name / user.email in rudder-devops, which was already configured at Lines 59–60 (persisted in .git/config). Not a problem, just slightly redundant. You could extract git config to a shared step or leave it as-is for clarity — up to you.

.github/workflows/prepare-for-prod-ut-deploy.yml (1)

112-143: Consider using signed commits for consistency with the DT deploy workflow.

The prepare-for-prod-dt-deploy.yml workflow uses ryancyq/github-signed-commit for verified commits, whereas this UT workflow still uses traditional git commit + git push. If the goal is to produce verified commits across all production deployment workflows, you may want to align this workflow with the DT approach. If this is intentional (e.g., incremental rollout), no action needed — just flagging the discrepancy.

.github/workflows/prepare-for-prod-dt-deploy.yml (1)

114-157: The git add calls are unnecessary in the signed-commit flow.

Since ryancyq/github-signed-commit reads file contents directly from the working directory and creates the commit via the GitHub GraphQL API, the git add calls (lines 124, 128, 132, 136) have no effect on the resulting commit. They're harmless but add noise. The same applies to the hosted transformer (line 176) and dedicated transformer (line 217) flows.

You could simplify by removing the git add calls and keeping only the yq modifications, since the signed commit action handles the commit entirely through the API.

.github/workflows/publish-new-release.yml (1)

72-86: GH_TOKEN is set but unused in this step.

The check_tag step only runs git fetch and git rev-parse — neither of which use the GH_TOKEN environment variable. The checkout step already configured the remote with the App token, so git fetch authenticates via the stored credentials. Consider removing GH_TOKEN from this step's env block to reduce noise and make it clear which steps actually need the token.

Proposed cleanup
       - name: Check if tag exists
         id: check_tag
         env:
-          GH_TOKEN: ${{ steps.generate-token.outputs.token }}
           RELEASE_VERSION: ${{ steps.extract-version.outputs.release_version }}
         run: |

@lvrach lvrach requested a review from maheshkutty February 11, 2026 14:01
@devops-github-rudderstack
Copy link
Contributor

This PR is considered to be stale. It has been open for 20 days with no further activity thus it is going to be closed in 7 days. To avoid such a case please consider removing the stale label manually or add a comment to the PR.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Migrates GitHub Actions workflows off a long-lived secrets.PAT to either GITHUB_TOKEN (read/non-triggering ops) or short-lived GitHub App installation tokens (write/cross-repo ops), while standardizing on job-level permission scoping and (in some workflows) API-based verified commits/tags.

Changes:

  • Replace PAT usage with GitHub App tokens generated via actions/create-github-app-token, including updates to reusable workflows and their callers.
  • Move permissions: from workflow-level to job-level across workflows to enforce least privilege.
  • Introduce/expand API-based operations (branch creation / verified commits & tags) in release/deploy automation.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
.github/workflows/verify.yml Move permissions to job level for PR formatting/lint job.
.github/workflows/verify-server-start.yml Move permissions to job level for server start check.
.github/workflows/validate-actor.yml Replace PAT with GitHub App token for org/team membership validation.
.github/workflows/ut-tests.yml Move permissions to job level for UT test workflow.
.github/workflows/update-ingestion-service.yml Switch cross-repo + PR/tag operations to GitHub App token; job-level permissions.
.github/workflows/slack-notify.yml Move permissions to job level for reusable Slack notifier.
.github/workflows/publish-new-release.yml Use GitHub App token for tag/release/PR operations; add verified tag creation.
.github/workflows/prepare-for-staging-deploy.yml Use GitHub App token for cross-repo helm chart updates; job-level permissions.
.github/workflows/prepare-for-prod-ut-deploy.yml Use GitHub App token for cross-repo helm chart updates; job-level permissions.
.github/workflows/prepare-for-prod-rollback.yml Use GitHub App token + update validate-actor secret wiring; job-level permissions.
.github/workflows/prepare-for-prod-dt-deploy.yml Use GitHub App token + verified commits for cross-repo deploy PRs; job-level permissions.
.github/workflows/prepare-for-dev-deploy.yml Move permissions to job level for OIDC-based deploy steps.
.github/workflows/integrations_version_audit.yml Move permissions to job level for scheduled audit job.
.github/workflows/ingestion-service-test.yml Replace PAT-based cross-repo clone with App-token-authenticated gh repo clone; update required secrets.
.github/workflows/housekeeping.yml Replace PAT with GITHUB_TOKEN for stale/branch cleanup actions; job-level permissions.
.github/workflows/dt-test-and-report-code-coverage.yml Replace PAT with GITHUB_TOKEN for Sonar scan; job-level permissions.
.github/workflows/draft-new-release.yml Replace PAT with GitHub App token; create branch via API; commit via verified API commit action.
.github/workflows/create-hotfix-branch.yml Replace PAT with GitHub App token for branch creation; update validate-actor secret wiring.
.github/workflows/commitlint.yml Move permissions to job level for commitlint workflow.
.github/workflows/check-pr-title.yml Move permissions to job level for PR title validation.
.github/workflows/build-push-docker-image.yml Move permissions to job level for reusable build/push workflow.
.github/workflows/build-pr-artifacts.yml Update reusable workflow calls/permissions; remove PAT secret passing.
Comments suppressed due to low confidence (1)

.github/workflows/build-pr-artifacts.yml:97

  • ingestion-service-test.yml now requires the RELEASE_PRIVATE_KEY secret, but this reusable workflow call only passes DOCKERHUB_TOKEN. This will fail workflow validation at runtime with a missing required secret. Pass through RELEASE_PRIVATE_KEY: ${{ secrets.RELEASE_PRIVATE_KEY }} (or make the callee secret optional if that’s intended).
    uses: ./.github/workflows/ingestion-service-test.yml
    with:
      build_tag: rudderstack/develop-rudder-transformer:${{ needs.generate-tag-names.outputs.tag_name }}
    secrets:
      DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

lvrach and others added 10 commits March 6, 2026 22:21
The id-token:write permission is only needed for OIDC authentication
(AWS/Azure/GCP). GitHub App token generation uses the App's private key
directly and does not require this permission.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace git push patterns with ryancyq/github-signed-commit action
to create verified commits via GitHub API.

Changes:
- draft-new-release.yml: Replace npm run release + git push with signed commit action
- prepare-for-prod-dt-deploy.yml: Replace git push with signed commit action for cross-repo operations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…C-58]

- Remove unnecessary git config commands from main repo workflows
- Replace git push --set-upstream with GitHub API branch creation in draft-new-release.yml
- Move git config to cloned repos where commits actually happen
- Follows new simplified pattern with ryancyq/github-signed-commit

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add RELEASE_PRIVATE_KEY to workflow_call.secrets in reusable workflows
  (validate-actor.yml, ingestion-service-test.yml, update-ingestion-service.yml)
- Add branch push steps before ryancyq/github-signed-commit actions
  (prepare-for-prod-dt-deploy.yml)
- Fix token scope to include current repo for tag push
  (update-ingestion-service.yml)
- Add x-access-token: prefix to git clone URLs for proper auth
  (prepare-for-prod-dt-deploy.yml, prepare-for-prod-rollback.yml,
   prepare-for-staging-deploy.yml, prepare-for-prod-ut-deploy.yml)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…EC-58]

- Move all workflow-level permissions to job-level across 22 files
- Fix $tag_name undefined variable in prepare-for-prod-rollback.yml
- Declare RELEASE_PRIVATE_KEY in validate-actor.yml workflow_call secrets
- Forward RELEASE_PRIVATE_KEY from all 3 callers of validate-actor
- Add rudder-transformer to token scope in update-ingestion-service.yml
- Wire app token into checkout for tag push in update-ingestion-service.yml
- Add x-access-token: prefix to 6 git clone URLs across 4 files
- Add id-token:write to jobs needing AWS OIDC (build, ECR, k8s restart)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add inline comments to every job-level permissions entry explaining
why the permission is needed and which action/step requires it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@maheshkutty maheshkutty force-pushed the chore/SEC-58-migrate-pat-to-github-app-token branch from f88a7e6 to f7db7c1 Compare March 6, 2026 16:55
@maheshkutty maheshkutty requested a review from Copilot March 6, 2026 16:58
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

.github/workflows/build-pr-artifacts.yml:97

  • The reusable workflow call to ./.github/workflows/ingestion-service-test.yml no longer passes RELEASE_PRIVATE_KEY, but ingestion-service-test.yml declares that secret as required: true for workflow_call. This will cause workflow validation/runtime failure. Pass RELEASE_PRIVATE_KEY: ${{ secrets.RELEASE_PRIVATE_KEY }} here (or make the secret optional in the called workflow if intentional).
    uses: ./.github/workflows/ingestion-service-test.yml
    with:
      build_tag: rudderstack/develop-rudder-transformer:${{ needs.generate-tag-names.outputs.tag_name }}
    secrets:
      DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

.github/workflows/build-pr-artifacts.yml:97

  • The reusable workflow call to ingestion-service-test.yml no longer passes all required secrets. ingestion-service-test.yml now requires RELEASE_PRIVATE_KEY, so this caller will fail validation unless it passes that secret as well (or the called workflow makes it optional).
    with:
      build_tag: rudderstack/develop-rudder-transformer:${{ needs.generate-tag-names.outputs.tag_name }}
    secrets:
      DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

maheshkutty
maheshkutty previously approved these changes Mar 10, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

maheshkutty
maheshkutty previously approved these changes Mar 10, 2026
@sonarqubecloud
Copy link

@maheshkutty maheshkutty merged commit dfc0f3b into develop Mar 10, 2026
36 of 37 checks passed
@maheshkutty maheshkutty deleted the chore/SEC-58-migrate-pat-to-github-app-token branch March 10, 2026 17:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants