From 68ba3ae45ac9c936ab1c887f4ada35543d7cd2b0 Mon Sep 17 00:00:00 2001 From: Rodin Date: Fri, 1 May 2026 21:03:41 -0700 Subject: [PATCH] fix: address review findings (path restriction, login cross-check, README) - 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 --- README.md | 2 +- cmd/review-bot/main.go | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6512eec..ee43bd4 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/cmd/review-bot/main.go b/cmd/review-bot/main.go index a99270e..313efe6 100644 --- a/cmd/review-bot/main.go +++ b/cmd/review-bot/main.go @@ -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 {