fix: address review findings (path restriction, login cross-check, README)
CI / test (pull_request) Successful in 13s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 24s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m20s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m27s

- system-prompt-file: reject absolute paths and paths containing ".."
  Prevents reading arbitrary files outside the workspace on shared runners.
- Cleanup: cross-check r.User.Login == posted.User.Login before deletion
  Defense-in-depth: only attempt to delete reviews from same author.
  Flagged by both sonnet and security reviewers.
- README: fix wording (cleanup happens after posting, not before)

Issues filed for deferred work:
- #24: Consistent url.PathEscape across all client endpoints
- #25: Binary signature verification for supply-chain hardening
This commit is contained in:
Rodin
2026-05-01 21:03:41 -07:00
parent b8af8306a6
commit 68ba3ae45a
2 changed files with 9 additions and 2 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ AI-powered code review bot for Gitea pull requests. Fetches diff + context, send
- **Multi-provider**: OpenAI-compatible and Anthropic Messages API
- **Context-aware**: Fetches full file content, conventions, language patterns, CI status
- **Smart budget**: Automatically trims context to fit model token limits
- **Idempotent reviews**: Deletes previous review before posting new one (one review per bot)
- **Idempotent reviews**: Posts new review, then cleans up stale ones (one review per bot)
- **Custom prompts**: Load additional instructions from a file (e.g. security-focused review)
- **Zero dependencies**: Go stdlib only
+8 -1
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"time"
@@ -154,6 +155,12 @@ func main() {
// Step 6b: Load additional system prompt if specified
additionalPrompt := ""
if *systemPromptFile != "" {
if filepath.IsAbs(*systemPromptFile) {
log.Fatalf("system-prompt-file must be a relative path (got %q)", *systemPromptFile)
}
if strings.Contains(*systemPromptFile, "..") {
log.Fatalf("system-prompt-file must not contain '..' (got %q)", *systemPromptFile)
}
data, err := os.ReadFile(*systemPromptFile)
if err != nil {
log.Fatalf("Failed to read system prompt file %q: %v", *systemPromptFile, err)
@@ -227,7 +234,7 @@ func main() {
log.Printf("Warning: could not list existing reviews: %v", err)
} else {
for _, r := range reviews {
if r.ID != posted.ID && strings.Contains(r.Body, sentinel) {
if r.ID != posted.ID && r.User.Login == posted.User.Login && strings.Contains(r.Body, sentinel) {
if err := giteaClient.DeleteReview(ctx, owner, repoName, prNumber, r.ID); err != nil {
log.Printf("Warning: could not delete old review %d: %v", r.ID, err)
} else {