forked from keithah/multi-provider-code-review
-
Notifications
You must be signed in to change notification settings - Fork 0
231 lines (221 loc) · 11.7 KB
/
Copy pathreviewrouter.yml
File metadata and controls
231 lines (221 loc) · 11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
name: ReviewRouter
on:
pull_request:
types: [opened, synchronize, reopened]
# SECURITY: pull_request trigger (not pull_request_target) prevents fork PRs
# from accessing repository secrets (OPENROUTER_API_KEY, etc.) by default.
# GitHub runs fork PR workflows with empty secrets UNLESS the repository
# setting "Send secrets to workflows from pull requests" is enabled.
#
# CRITICAL ASSUMPTION: This workflow requires that "Send secrets to workflows
# from pull requests" is DISABLED in repository settings (default/recommended).
# If enabled, fork PRs could access secrets, which is a security risk.
# See: Settings → Actions → General → Fork pull request workflows
#
# Additional security layers in this workflow:
# - Explicit fork detection and secret validation (line 97-110)
# - Conditional job execution that skips fork PRs (line 132)
# - Provider allowlist validation for user-supplied model names
# Note: pull_request_review trigger removed to prevent duplicate runs
# when the bot posts its own review comment
workflow_dispatch:
inputs:
pr_number:
description: "Pull request number to review"
required: true
review_providers:
description: "Comma-separated list of model providers (validated against allowed patterns)"
required: false
type: string
# Cancel in-progress runs for the same commit to prevent duplicate reviews
# Strategy: Use PR number + head SHA for pull requests, github.sha for push
# - For pull_request: group by PR number and head commit SHA
# - For push: group by pushed commit SHA
# - For workflow_dispatch: group by input PR number and current SHA
# This ensures proper isolation and prevents race conditions between different PRs
concurrency:
# Use distinct prefixes for different event types to prevent cross-event cancellation
# - pull_request: pr-{number}-{head_sha}
# - push: push-{sha}
# - workflow_dispatch: dispatch-{pr_number}-{sha}
group: ${{ github.event_name == 'pull_request' && format('pr-{0}-{1}', github.event.pull_request.number, github.event.pull_request.head.sha) || github.event_name == 'workflow_dispatch' && format('dispatch-{0}-{1}', inputs.pr_number, github.sha) || format('push-{0}', github.sha) }}
cancel-in-progress: true
# Permissions: Principle of least privilege
# Only grant the minimum permissions required for code review functionality
# Security rationale:
# - contents: read - Required to checkout code and read PR diffs (minimal read access)
# - pull-requests: write - Required to post review comments and inline suggestions (no merge access)
# - actions: read - Required for concurrency control to prevent duplicate reviews (read-only)
# NOT granted: contents: write (cannot modify code), issues: write (cannot create issues)
permissions:
contents: read # Read repository code and PR diffs
pull-requests: write # Post review comments and summaries
actions: read # Check workflow status (for concurrency control)
jobs:
review:
runs-on: ubuntu-latest
env:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0 # Fetch full history for incremental review git diff
- name: Resolve PR number
id: resolve-pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: ${{ github.ref_name }}
run: |
pr="${{ github.event.pull_request.number || '' }}"
if [ -n "$pr" ]; then
echo "pr=$pr" >> "$GITHUB_OUTPUT"
exit 0
fi
pr="${{ inputs.pr_number || '' }}"
if [ -n "$pr" ]; then
echo "pr=$pr" >> "$GITHUB_OUTPUT"
exit 0
fi
if [ "${{ github.event_name }}" = "push" ]; then
pr=$(gh pr list --state open --head "$BRANCH" --json number --jq '.[0].number')
if [ -n "$pr" ]; then
echo "pr=$pr" >> "$GITHUB_OUTPUT"
exit 0
fi
fi
echo "No PR found for this event/branch; skipping review."
echo "pr=" >> "$GITHUB_OUTPUT"
- name: Validate Review Providers Input
# Only validate if providers are explicitly specified (not empty and not null)
# GitHub Actions treats missing inputs as empty string, so this condition is correct
if: ${{ inputs.review_providers != '' && inputs.review_providers != null }}
run: node scripts/validate-providers.js "${{ inputs.review_providers }}"
- name: Validate Secrets and Configuration
run: |
# SECURITY: Check for fork PRs with unexpected secret access
# If a fork PR has OPENROUTER_API_KEY, the repository setting
# "Send secrets to workflows from pull requests" is likely enabled (security risk)
if [ "${{ github.event.pull_request.head.repo.fork }}" = "true" ] && [ "${{ github.event_name }}" = "pull_request" ]; then
if [ -z "$OPENROUTER_API_KEY" ]; then
echo "✅ Fork PR detected without OPENROUTER_API_KEY (expected behavior)."
echo " Fork PR review steps will be skipped for security (see job condition at line 142)."
else
echo "🔴 SECURITY ALERT: Fork PR has access to OPENROUTER_API_KEY!"
echo " This indicates 'Send secrets to workflows from pull requests' is enabled."
echo " This is a security risk. Failing workflow to prevent secret exposure."
echo " To fix: Settings → Actions → General → Disable 'Send secrets to workflows from pull requests'"
exit 1
fi
fi
if [ -z "$OPENROUTER_API_KEY" ]; then
echo "⚠️ Warning: OPENROUTER_API_KEY not set. OpenRouter providers will be skipped; free OpenCode providers will be used."
else
echo "✅ OPENROUTER_API_KEY available - full provider access enabled"
fi
# Log provider discovery strategy
if [ -n "${{ inputs.review_providers }}" ]; then
echo "📋 Using explicit providers: ${{ inputs.review_providers }}"
else
echo "🔍 Using dynamic provider discovery (see workflow comments for fallback chain)"
fi
- name: Install CLI Tools
# Version pinning strategy:
# - Pin to latest published npm version known-good for CLI commands
# - Update strategy: Test quarterly (Jan, Apr, Jul, Oct) with full suite + smoke tests
run: |
npm install -g opencode-ai@1.1.40
npm install -g @google/gemini-cli
npm install -g codex-cli
curl -fsSL https://claude.ai/install.sh | bash
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Setup CLI Configuration Files
# Create legacy CLI configuration files where required.
# Claude Code subscription OAuth is passed as CLAUDE_CODE_OAUTH_TOKEN
# and must not be converted into ANTHROPIC_API_KEY or credentials.json.
id: setup-cli-configs
run: |
# Validate Claude Code subscription token presence
if [ -n "${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}" ]; then
echo "✅ Claude Code OAuth token configured"
else
echo "⚠️ CLAUDE_CODE_OAUTH_TOKEN not set, Claude providers will be skipped"
fi
# Setup Codex credentials
if [ -n "${{ secrets.CODEX_AUTH_JSON }}" ]; then
mkdir -p ~/.codex
echo '${{ secrets.CODEX_AUTH_JSON }}' > ~/.codex/auth.json
chmod 600 ~/.codex/auth.json
# Create config.toml if provided
if [ -n "${{ secrets.CODEX_CONFIG_TOML }}" ]; then
echo '${{ secrets.CODEX_CONFIG_TOML }}' > ~/.codex/config.toml
chmod 600 ~/.codex/config.toml
fi
# Also set environment variable as fallback
CODEX_TOKEN=$(echo '${{ secrets.CODEX_AUTH_JSON }}' | jq -r '.tokens.access_token // empty')
if [ -n "$CODEX_TOKEN" ]; then
echo "OPENAI_API_KEY=$CODEX_TOKEN" >> $GITHUB_ENV
echo "::add-mask::$CODEX_TOKEN"
fi
echo "✅ Codex credentials configured"
else
echo "⚠️ CODEX_AUTH_JSON not set, Codex providers will be skipped"
fi
# Setup Gemini credentials
if [ -n "${{ secrets.GEMINI_OAUTH_CREDS }}" ]; then
mkdir -p ~/.gemini
echo '${{ secrets.GEMINI_OAUTH_CREDS }}' > ~/.gemini/oauth_creds.json
chmod 600 ~/.gemini/oauth_creds.json
# Create settings.json if provided, otherwise create minimal one
if [ -n "${{ secrets.GEMINI_SETTINGS }}" ]; then
echo '${{ secrets.GEMINI_SETTINGS }}' > ~/.gemini/settings.json
else
# Create minimal settings.json pointing to oauth_creds.json
echo "{\"oauth_credentials_path\": \"$HOME/.gemini/oauth_creds.json\"}" > ~/.gemini/settings.json
fi
chmod 600 ~/.gemini/settings.json
# Also set environment variable as fallback
GEMINI_TOKEN=$(echo '${{ secrets.GEMINI_OAUTH_CREDS }}' | jq -r '.access_token // empty')
if [ -n "$GEMINI_TOKEN" ]; then
echo "GEMINI_API_KEY=$GEMINI_TOKEN" >> $GITHUB_ENV
echo "::add-mask::$GEMINI_TOKEN"
fi
echo "✅ Gemini credentials configured"
else
echo "⚠️ GEMINI_OAUTH_CREDS not set, Gemini providers will be skipped"
fi
- name: ReviewRouter
# Run if PR number resolved AND (not a fork PR OR not a pull_request event)
# Fork detection: github.event.pull_request.head.repo.fork only exists for pull_request events
# - For pull_request: Skip if fork (github.event.pull_request.head.repo.fork == true)
# - For push: Always run (no fork concept, secrets available)
# - For workflow_dispatch: Always run (manually triggered, secrets available)
# This prevents fork PRs from running without secrets while allowing push/dispatch events
if: steps.resolve-pr.outputs.pr != '' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork != true)
uses: ./
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.resolve-pr.outputs.pr }}
REVIEW_PROVIDERS: ${{ inputs.review_providers || vars.REVIEW_PROVIDERS }}
# Dynamic Model Discovery:
# By default (when REVIEW_PROVIDERS is empty or not set), the system automatically
# discovers and selects the best available free models through a multi-tier fallback:
# 1. OpenRouter API (if OPENROUTER_API_KEY is set)
# 2. OpenCode CLI free models (if opencode-ai is installed)
# 3. Hardcoded fallback providers (see src/config/defaults.ts)
#
# OAuth CLI Providers (when credentials are configured):
# If you have OAuth CLI credentials configured as secrets, you can use:
# - claude/sonnet, claude/opus, claude/haiku (Claude Code CLI)
# - codex/gpt-5.5 (Codex CLI)
# - gemini/gemini-2.0-flash, gemini/gemini-1.5-pro (Gemini CLI)
#
# To use specific providers instead of auto-discovery:
# - Set inputs.review_providers for workflow_dispatch
# - Set REVIEW_PROVIDERS environment variable in your fork/config
# - Create .review-router.json in your repository
# See docs/CI_SETUP.md for provider configuration
env:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
OPENAI_API_KEY: ${{ env.OPENAI_API_KEY }}
GEMINI_API_KEY: ${{ env.GEMINI_API_KEY }}