feat: sync secrets workflow for sibling repositories#825
feat: sync secrets workflow for sibling repositories#825frostebite wants to merge 1 commit intomainfrom
Conversation
Adds a manually-triggered workflow that copies secrets from unity-builder (repo + org level) to target repos like orchestrator and cli. Uses GIT_PRIVATE_TOKEN for cross-repo API access. Secrets synced: UNITY_EMAIL, UNITY_PASSWORD, UNITY_SERIAL, GIT_PRIVATE_TOKEN, LOCALSTACK_AUTH_TOKEN, GOOGLE_SERVICE_ACCOUNT_EMAIL, GOOGLE_SERVICE_ACCOUNT_KEY, CODECOV_TOKEN. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughA new GitHub Actions workflow has been added that enables manual synchronization of secrets to target repositories. The workflow accepts configuration inputs for the target repository and a dry-run mode, then processes a predefined list of environment variables, syncing each as a GitHub secret or logging skipped entries. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
| name: Sync secrets to ${{ inputs.target_repo }} | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Sync secrets | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN }} | ||
| TARGET_REPO: ${{ inputs.target_repo }} | ||
| DRY_RUN: ${{ inputs.dry_run }} | ||
| # Secrets to sync — values come from repo + org secrets available here | ||
| SECRET_UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | ||
| SECRET_UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | ||
| SECRET_UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | ||
| SECRET_GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN }} | ||
| SECRET_LOCALSTACK_AUTH_TOKEN: ${{ secrets.LOCALSTACK_AUTH_TOKEN }} | ||
| SECRET_GOOGLE_SERVICE_ACCOUNT_EMAIL: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_EMAIL }} | ||
| SECRET_GOOGLE_SERVICE_ACCOUNT_KEY: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_KEY }} | ||
| SECRET_CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} | ||
| run: | | ||
| SECRETS=( | ||
| "UNITY_EMAIL:SECRET_UNITY_EMAIL" | ||
| "UNITY_PASSWORD:SECRET_UNITY_PASSWORD" | ||
| "UNITY_SERIAL:SECRET_UNITY_SERIAL" | ||
| "GIT_PRIVATE_TOKEN:SECRET_GIT_PRIVATE_TOKEN" | ||
| "LOCALSTACK_AUTH_TOKEN:SECRET_LOCALSTACK_AUTH_TOKEN" | ||
| "GOOGLE_SERVICE_ACCOUNT_EMAIL:SECRET_GOOGLE_SERVICE_ACCOUNT_EMAIL" | ||
| "GOOGLE_SERVICE_ACCOUNT_KEY:SECRET_GOOGLE_SERVICE_ACCOUNT_KEY" | ||
| "CODECOV_TOKEN:SECRET_CODECOV_TOKEN" | ||
| ) | ||
|
|
||
| synced=0 | ||
| skipped=0 | ||
|
|
||
| for entry in "${SECRETS[@]}"; do | ||
| name="${entry%%:*}" | ||
| env_var="${entry##*:}" | ||
| value="${!env_var}" | ||
|
|
||
| if [ -z "$value" ]; then | ||
| echo "⏭ SKIP: $name (not available in this repo's context)" | ||
| skipped=$((skipped + 1)) | ||
| continue | ||
| fi | ||
|
|
||
| if [ "$DRY_RUN" = "true" ]; then | ||
| echo "🔍 DRY RUN: would sync $name → $TARGET_REPO" | ||
| else | ||
| echo "$value" | gh secret set "$name" -R "$TARGET_REPO" --body - | ||
| echo "✅ SYNCED: $name → $TARGET_REPO" | ||
| fi | ||
| synced=$((synced + 1)) | ||
| done | ||
|
|
||
| echo "" | ||
| echo "=== Summary ===" | ||
| echo "Synced: $synced" | ||
| echo "Skipped (not available): $skipped" | ||
| echo "Target: $TARGET_REPO" | ||
| if [ "$DRY_RUN" = "true" ]; then | ||
| echo "Mode: DRY RUN (no secrets were written)" | ||
| fi |
Check warning
Code scanning / CodeQL
Workflow does not contain permissions Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 14 days ago
In general, the fix is to add an explicit permissions block that limits the GITHUB_TOKEN to the least privileges required. This should be added either at the top level of the workflow (to apply to all jobs) or at the job level (for sync-secrets). Since this workflow only has a single job and uses a separate secret (GIT_PRIVATE_TOKEN) for writing secrets, GITHUB_TOKEN does not appear to need any write permissions. The safest minimal configuration is permissions: contents: read at the job or workflow level.
The single best way to fix this without affecting existing behavior is to add a permissions block under the sync-secrets job, right after runs-on: ubuntu-latest. Given the script only calls gh secret set using GH_TOKEN from a custom secret and does not interact with repository contents, issues, or pull requests via GITHUB_TOKEN, we can set permissions: contents: read. This keeps GITHUB_TOKEN usable for basic checkout or read-only operations if added later, while preventing unintended write access.
Concretely, in .github/workflows/sync-secrets.yml, modify the sync-secrets job so that:
sync-secrets:
name: Sync secrets to ${{ inputs.target_repo }}
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Sync secrets
...No new imports or libraries are needed, as this is purely a YAML configuration change.
| @@ -21,6 +21,8 @@ | ||
| sync-secrets: | ||
| name: Sync secrets to ${{ inputs.target_repo }} | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| steps: | ||
| - name: Sync secrets | ||
| env: |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #825 +/- ##
=======================================
Coverage 31.23% 31.23%
=======================================
Files 84 84
Lines 4565 4565
Branches 1054 1054
=======================================
Hits 1426 1426
Misses 3139 3139 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
.github/workflows/sync-secrets.yml (1)
27-29: Add auth preflight for clearer non-dry-run failures.Right now, auth/scope problems only surface during
gh secret setexecution. A small preflight makes failures immediate and clearer for operators.Suggested patch
- name: Sync secrets + shell: bash env: GH_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN }} TARGET_REPO: ${{ inputs.target_repo }} DRY_RUN: ${{ inputs.dry_run }} @@ run: | + if [ "$DRY_RUN" != "true" ]; then + if [ -z "${GH_TOKEN:-}" ]; then + echo "::error::GIT_PRIVATE_TOKEN is required for non-dry-run sync." + exit 1 + fi + gh auth status >/dev/null 2>&1 || { + echo "::error::gh authentication failed (missing/invalid token or scopes)." + exit 1 + } + fi + SECRETS=(Also applies to: 39-69
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/sync-secrets.yml around lines 27 - 29, Add an auth preflight step that runs only when DRY_RUN is false to fail early on token/scope problems: use the existing GH_TOKEN and conditionally execute a lightweight check (for example running `gh auth status` or `gh api /user`) in a step named like "auth-preflight" or with id "auth_preflight" before any `gh secret set` calls so operators get immediate, clear failures instead of errors surfacing later during secret operations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/sync-secrets.yml:
- Around line 20-24: Add an explicit permissions block at the workflow root to
restrict the default GITHUB_TOKEN (follow least privilege). Insert a top-level
permissions section (above jobs) such as "permissions: contents: read" or
"permissions: none" depending on whether the workflow needs repo contents; this
will limit GITHUB_TOKEN access for the "sync-secrets" job while you continue to
use the explicit GH_TOKEN for gh secret set.
- Around line 31-38: The sync secrets allowlist is missing UNITY_LICENSE which
is used by downstream workflows (e.g., build-tests-windows.yml); update the
secret mappings in .github/workflows/sync-secrets.yml by adding a mapping for
UNITY_LICENSE (e.g., SECRET_UNITY_LICENSE -> ${{ secrets.UNITY_LICENSE }})
alongside the existing SECRET_UNITY_* entries so the workflow can sync that
secret for downstream use.
---
Nitpick comments:
In @.github/workflows/sync-secrets.yml:
- Around line 27-29: Add an auth preflight step that runs only when DRY_RUN is
false to fail early on token/scope problems: use the existing GH_TOKEN and
conditionally execute a lightweight check (for example running `gh auth status`
or `gh api /user`) in a step named like "auth-preflight" or with id
"auth_preflight" before any `gh secret set` calls so operators get immediate,
clear failures instead of errors surfacing later during secret operations.
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: c1a893b8-5835-481a-b1bd-bf59fd200cdd
📒 Files selected for processing (1)
.github/workflows/sync-secrets.yml
| jobs: | ||
| sync-secrets: | ||
| name: Sync secrets to ${{ inputs.target_repo }} | ||
| runs-on: ubuntu-latest | ||
| steps: |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n .github/workflows/sync-secrets.ymlRepository: game-ci/unity-builder
Length of output: 3535
🏁 Script executed:
find .github/workflows -name "*.yml" -o -name "*.yaml" | head -5 | xargs grep -l "permissions:" 2>/dev/nullRepository: game-ci/unity-builder
Length of output: 49
🏁 Script executed:
find .github/workflows -name "*.yml" -o -name "*.yaml" | xargs head -30 | grep -A 5 "^jobs:" | head -20Repository: game-ci/unity-builder
Length of output: 547
🏁 Script executed:
find .github/workflows -type f \( -name "*.yml" -o -name "*.yaml" \) | wc -lRepository: game-ci/unity-builder
Length of output: 67
🏁 Script executed:
find .github/workflows -type f \( -name "*.yml" -o -name "*.yaml" \) -exec grep -l "^permissions:" {} \;Repository: game-ci/unity-builder
Length of output: 195
🏁 Script executed:
grep -A 2 "^permissions:" .github/workflows/integrity-check.yml .github/workflows/orchestrator-async-checks.yml .github/workflows/orchestrator-integrity.ymlRepository: game-ci/unity-builder
Length of output: 585
🏁 Script executed:
# Verify the syntax of permissions: {} in GitHub Actions documentation context
# Check if there are any other cross-repo secret workflows to compare
grep -r "gh secret set" .github/workflows/Repository: game-ci/unity-builder
Length of output: 178
Add explicit workflow permissions (least privilege).
The workflow lacks an explicit permissions block, leaving default GITHUB_TOKEN permissions implicit. While this workflow uses an explicit GH_TOKEN for gh secret set, the default token should still be restricted to follow least-privilege principle and align with the repository's existing security pattern (other workflows declare explicit permissions).
Suggested patch
name: Sync Secrets to Repositories
on:
workflow_dispatch:
+ inputs: type: boolean
+
+permissions: {}
jobs:
sync-secrets:🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/sync-secrets.yml around lines 20 - 24, Add an explicit
permissions block at the workflow root to restrict the default GITHUB_TOKEN
(follow least privilege). Insert a top-level permissions section (above jobs)
such as "permissions: contents: read" or "permissions: none" depending on
whether the workflow needs repo contents; this will limit GITHUB_TOKEN access
for the "sync-secrets" job while you continue to use the explicit GH_TOKEN for
gh secret set.
| SECRET_UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | ||
| SECRET_UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | ||
| SECRET_UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} | ||
| SECRET_GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN }} | ||
| SECRET_LOCALSTACK_AUTH_TOKEN: ${{ secrets.LOCALSTACK_AUTH_TOKEN }} | ||
| SECRET_GOOGLE_SERVICE_ACCOUNT_EMAIL: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_EMAIL }} | ||
| SECRET_GOOGLE_SERVICE_ACCOUNT_KEY: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_KEY }} | ||
| SECRET_CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd /tmp && git ls-files | grep -E "\.github/workflows/" | head -20Repository: game-ci/unity-builder
Length of output: 133
🏁 Script executed:
cat -n .github/workflows/sync-secrets.yml | head -60Repository: game-ci/unity-builder
Length of output: 2698
🏁 Script executed:
cat -n .github/workflows/build-tests-windows.yml | grep -i "unity_license" -A 2 -B 2Repository: game-ci/unity-builder
Length of output: 826
🏁 Script executed:
cat .github/workflows/orchestrator-integrity.yml | grep -E "AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY" -A 2 -B 2Repository: game-ci/unity-builder
Length of output: 4497
🏁 Script executed:
cat .github/workflows/orchestrator-integrity.yml | grep -E "secrets\." -i | head -20Repository: game-ci/unity-builder
Length of output: 1172
🏁 Script executed:
rg "secrets\.AWS_" .github/workflows/ -A 1 -B 1Repository: game-ci/unity-builder
Length of output: 436
🏁 Script executed:
cat .github/workflows/orchestrator-async-checks.yml | grep -A 10 -B 10 "AWS_ACCESS_KEY_ID.*secrets"Repository: game-ci/unity-builder
Length of output: 830
🏁 Script executed:
cat -n .github/workflows/sync-secrets.yml | tail -30Repository: game-ci/unity-builder
Length of output: 1239
Add UNITY_LICENSE to the secrets allowlist for sync.
The mapping omits UNITY_LICENSE, which is actively referenced in .github/workflows/build-tests-windows.yml (lines 109, 136, 161). This secret is necessary for downstream workflows and should be included in the sync.
Note: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are not referenced as actual secrets in the workflows—they are hardcoded test values (in orchestrator-integrity.yml) or commented out (in orchestrator-async-checks.yml), so they should not be added to the sync allowlist.
Suggested patch
SECRET_UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
+ SECRET_UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
SECRET_GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN }} "UNITY_SERIAL:SECRET_UNITY_SERIAL"
+ "UNITY_LICENSE:SECRET_UNITY_LICENSE"
"GIT_PRIVATE_TOKEN:SECRET_GIT_PRIVATE_TOKEN"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/sync-secrets.yml around lines 31 - 38, The sync secrets
allowlist is missing UNITY_LICENSE which is used by downstream workflows (e.g.,
build-tests-windows.yml); update the secret mappings in
.github/workflows/sync-secrets.yml by adding a mapping for UNITY_LICENSE (e.g.,
SECRET_UNITY_LICENSE -> ${{ secrets.UNITY_LICENSE }}) alongside the existing
SECRET_UNITY_* entries so the workflow can sync that secret for downstream use.

Summary
sync-secrets.yml) that copies secrets from unity-builder to sibling repos (orchestrator, cli)GIT_PRIVATE_TOKENfor cross-repo GitHub API access viagh secret setSecrets synced
UNITY_EMAILUNITY_PASSWORDUNITY_SERIALGIT_PRIVATE_TOKENLOCALSTACK_AUTH_TOKENGOOGLE_SERVICE_ACCOUNT_EMAILGOOGLE_SERVICE_ACCOUNT_KEYCODECOV_TOKENUsage
Context
The orchestrator repo has zero secrets configured, causing all LocalStack-based integration tests to fail. This workflow provides a convenient way to keep secrets in sync across game-ci repos without manual copying.
🤖 Generated with Claude Code
Summary by CodeRabbit