diff --git a/cmd/review-bot/main.go b/cmd/review-bot/main.go index 109436f..d4409e2 100644 --- a/cmd/review-bot/main.go +++ b/cmd/review-bot/main.go @@ -168,7 +168,15 @@ func main() { if !strings.HasPrefix(promptPath, absWorkspace+string(filepath.Separator)) && promptPath != absWorkspace { log.Fatalf("system-prompt-file resolves outside workspace (got %q, workspace %q)", promptPath, absWorkspace) } - data, err := os.ReadFile(promptPath) + // Resolve symlinks and re-validate to prevent symlink traversal + resolvedPath, err := filepath.EvalSymlinks(promptPath) + if err != nil { + log.Fatalf("Failed to resolve system prompt file %q: %v", promptPath, err) + } + if !strings.HasPrefix(resolvedPath, absWorkspace+string(filepath.Separator)) && resolvedPath != absWorkspace { + log.Fatalf("system-prompt-file symlink resolves outside workspace (got %q, workspace %q)", resolvedPath, absWorkspace) + } + data, err := os.ReadFile(resolvedPath) if err != nil { log.Fatalf("Failed to read system prompt file %q: %v", promptPath, err) } @@ -226,21 +234,19 @@ func main() { return } - // Worst-wins: if we're about to APPROVE but a sibling review from the same - // user already has REQUEST_CHANGES, post as REQUEST_CHANGES too so we don't - // override the blocking state. + sentinel := fmt.Sprintf("", *reviewerName) + + // Worst-wins: if we would APPROVE but a sibling bot review (same token, + // different role) already has REQUEST_CHANGES, escalate to REQUEST_CHANGES. + // We identify sibling bot reviews by the ", *reviewerName) - if !strings.Contains(r.Body, sentinelCheck) { - log.Printf("Sibling review %d has REQUEST_CHANGES; escalating to REQUEST_CHANGES", r.ID) - event = "REQUEST_CHANGES" - break - } + if !r.Stale && r.State == "REQUEST_CHANGES" && strings.Contains(r.Body, "", *reviewerName) if *updateExisting && *reviewerName != "" { reviews, err := giteaClient.ListReviews(ctx, owner, repoName, prNumber) if err != nil {