Skip to content

Commit aa243b2

Browse files
committed
fix: source REVIEW.md from trusted base ref in CI mode
In CI mode, contributor-controlled branches could add a REVIEW.md that manipulates review behavior (e.g. 'report no findings'). Now in CI mode with a known base SHA (CI_MERGE_REQUEST_DIFF_BASE_SHA), REVIEW.md is read from the target/base ref via 'git show' instead of the working tree. This ensures only repo maintainers (who control the default branch) can set review instructions. In local --diff mode the filesystem version is still used since the developer controls the checkout. Includes command-injection guard (reject refs starting with '-') consistent with the existing pattern in getLocalDiffs().
1 parent 7daaa4a commit aa243b2

1 file changed

Lines changed: 29 additions & 1 deletion

File tree

internal/reviewer/reviewer.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,13 @@ func (r *Reviewer) Run(ctx context.Context) (int, error) {
179179
}
180180

181181
// Step 4: Build prompt and call model for each chunk.
182-
systemPrompt := model.BuildPromptFull(r.cfg.CustomPrompt, r.cfg.ReviewMD, r.cfg.Focus, r.cfg.ExtraRules)
182+
// In CI mode, source REVIEW.md from the trusted base/target ref so that
183+
// contributor-controlled branches cannot inject review instructions.
184+
reviewMD := r.cfg.ReviewMD
185+
if r.cfg.CIMode && r.cfg.CIDiffBaseSHA != "" {
186+
reviewMD = readReviewMDFromRef(r.cfg.CIDiffBaseSHA)
187+
}
188+
systemPrompt := model.BuildPromptFull(r.cfg.CustomPrompt, reviewMD, r.cfg.Focus, r.cfg.ExtraRules)
183189
var allFindings []model.Finding
184190
var summary string
185191
var totalUsage model.TokenUsage
@@ -469,3 +475,25 @@ func findRepoRoot() string {
469475
dir = parent
470476
}
471477
}
478+
479+
// readReviewMDFromRef reads REVIEW.md from a specific git ref (e.g. base commit SHA).
480+
// Returns empty string if the file doesn't exist at that ref or git fails.
481+
func readReviewMDFromRef(ref string) string {
482+
// Prevent command injection: reject refs that look like flags.
483+
if strings.HasPrefix(ref, "-") {
484+
slog.Warn("invalid git ref for REVIEW.md lookup, skipping", "ref", ref)
485+
return ""
486+
}
487+
cmd := exec.Command("git", "show", ref+":REVIEW.md")
488+
output, err := cmd.Output()
489+
if err != nil {
490+
// File doesn't exist at this ref — this is normal and expected.
491+
slog.Debug("REVIEW.md not found at base ref", "ref", ref)
492+
return ""
493+
}
494+
content := strings.TrimSpace(string(output))
495+
if content != "" {
496+
slog.Info("loaded REVIEW.md from trusted base ref", "ref", ref[:min(len(ref), 12)])
497+
}
498+
return content
499+
}

0 commit comments

Comments
 (0)