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 5 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