Skip to content

Commit 294e87c

Browse files
sallyomclaude
andcommitted
PR Review suggestions
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> Signed-off-by: sallyom <[email protected]>
1 parent 9b7b5a7 commit 294e87c

File tree

12 files changed

+263
-53
lines changed

12 files changed

+263
-53
lines changed

components/backend/crd/bugfix.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package crd
33
import (
44
"context"
55
"fmt"
6+
"log"
67

78
"ambient-code-backend/types"
89

@@ -113,8 +114,16 @@ func GetProjectBugFixWorkflowCR(dyn dynamic.Interface, project, id string) (*typ
113114

114115
spec, found, _ := unstructured.NestedMap(obj.Object, "spec")
115116
if found {
117+
// Parse githubIssueNumber with type safety - handle both int64 and float64
118+
// JSON unmarshaling can produce either depending on the source
116119
if val, ok := spec["githubIssueNumber"].(int64); ok {
117120
workflow.GithubIssueNumber = int(val)
121+
} else if val, ok := spec["githubIssueNumber"].(float64); ok {
122+
workflow.GithubIssueNumber = int(val)
123+
} else if spec["githubIssueNumber"] != nil {
124+
// Log warning if type assertion fails - helps debug unexpected types
125+
log.Printf("Warning: githubIssueNumber has unexpected type %T in BugFixWorkflow %s/%s",
126+
spec["githubIssueNumber"], project, id)
118127
}
119128
if val, ok := spec["githubIssueURL"].(string); ok {
120129
workflow.GithubIssueURL = val
@@ -144,16 +153,22 @@ func GetProjectBugFixWorkflowCR(dyn dynamic.Interface, project, id string) (*typ
144153
workflow.LastSyncedAt = &val
145154
}
146155

147-
// Parse implementationRepo
156+
// Parse implementationRepo (required field)
148157
if implMap, ok := spec["implementationRepo"].(map[string]interface{}); ok {
149158
repo := types.GitRepository{}
150-
if url, ok := implMap["url"].(string); ok {
159+
if url, ok := implMap["url"].(string); ok && url != "" {
151160
repo.URL = url
161+
} else {
162+
// Critical field missing - return error instead of zero value
163+
return nil, fmt.Errorf("BugFixWorkflow %s/%s missing required field: implementationRepo.url", project, id)
152164
}
153165
if branch, ok := implMap["branch"].(string); ok && branch != "" {
154166
repo.Branch = &branch
155167
}
156168
workflow.ImplementationRepo = repo
169+
} else {
170+
// implementationRepo is required
171+
return nil, fmt.Errorf("BugFixWorkflow %s/%s missing required field: implementationRepo", project, id)
157172
}
158173
}
159174

components/backend/git/operations.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,11 @@ func CheckRepoSeeding(ctx context.Context, repoURL string, branch *string, githu
206206

207207
// ParseGitHubURL extracts owner and repo from a GitHub URL
208208
func ParseGitHubURL(gitURL string) (owner, repo string, err error) {
209+
// Only HTTPS URLs are supported (no SSH git@ URLs)
210+
if !strings.HasPrefix(gitURL, "https://") {
211+
return "", "", fmt.Errorf("invalid URL scheme: must be https://")
212+
}
213+
209214
gitURL = strings.TrimSuffix(gitURL, ".git")
210215

211216
if strings.Contains(gitURL, "github.com") {
@@ -218,7 +223,15 @@ func ParseGitHubURL(gitURL string) (owner, repo string, err error) {
218223
if len(pathParts) < 2 {
219224
return "", "", fmt.Errorf("invalid GitHub URL path")
220225
}
221-
return pathParts[0], pathParts[1], nil
226+
227+
// Validate owner and repo are non-empty
228+
owner = pathParts[0]
229+
repo = pathParts[1]
230+
if owner == "" || repo == "" {
231+
return "", "", fmt.Errorf("owner and repository name cannot be empty")
232+
}
233+
234+
return owner, repo, nil
222235
}
223236

224237
return "", "", fmt.Errorf("not a GitHub URL")

components/backend/handlers/bugfix/create.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"log"
66
"net/http"
7+
"regexp"
78
"strings"
89
"time"
910

@@ -24,6 +25,31 @@ var (
2425
GetProjectSettingsResource func() schema.GroupVersionResource
2526
)
2627

28+
// validBranchNameRegex defines allowed characters in branch names
29+
// Allows: letters, numbers, hyphens, underscores, forward slashes, and dots
30+
// Prevents: shell metacharacters, backticks, quotes, semicolons, pipes, etc.
31+
var validBranchNameRegex = regexp.MustCompile(`^[a-zA-Z0-9/_.-]+$`)
32+
33+
// validateBranchName checks if a branch name is safe to use in git operations
34+
// Returns error if the branch name contains potentially dangerous characters
35+
func validateBranchName(branchName string) error {
36+
if branchName == "" {
37+
return fmt.Errorf("branch name cannot be empty")
38+
}
39+
if !validBranchNameRegex.MatchString(branchName) {
40+
return fmt.Errorf("branch name contains invalid characters (allowed: a-z, A-Z, 0-9, /, _, -, .)")
41+
}
42+
// Prevent branch names that start with special characters
43+
if strings.HasPrefix(branchName, ".") || strings.HasPrefix(branchName, "-") {
44+
return fmt.Errorf("branch name cannot start with '.' or '-'")
45+
}
46+
// Prevent branch names with ".." (path traversal) or "//" (double slashes)
47+
if strings.Contains(branchName, "..") || strings.Contains(branchName, "//") {
48+
return fmt.Errorf("branch name cannot contain '..' or '//'")
49+
}
50+
return nil
51+
}
52+
2753
// CreateProjectBugFixWorkflow handles POST /api/projects/:projectName/bugfix-workflows
2854
// Creates a new BugFix Workspace from either GitHub Issue URL or text description
2955
func CreateProjectBugFixWorkflow(c *gin.Context) {
@@ -162,9 +188,14 @@ func CreateProjectBugFixWorkflow(c *gin.Context) {
162188
}
163189

164190
// Auto-generate branch name if not provided
165-
branch := "main"
191+
var branch string
166192
if req.BranchName != nil && *req.BranchName != "" {
167193
branch = *req.BranchName
194+
// Validate user-provided branch name for security
195+
if err := validateBranchName(branch); err != nil {
196+
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid branch name", "details": err.Error()})
197+
return
198+
}
168199
} else {
169200
// Auto-generate branch name: bugfix/gh-{issue-number}
170201
branch = fmt.Sprintf("bugfix/gh-%d", githubIssue.Number)

components/backend/handlers/bugfix/jira_sync.go

Lines changed: 14 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"fmt"
88
"io"
9+
"log"
910
"net/http"
1011
"strings"
1112
"time"
@@ -152,6 +153,7 @@ func SyncProjectBugFixWorkflowToJira(c *gin.Context) {
152153
req.Header.Set("Content-Type", "application/json")
153154
req.Header.Set("Accept", "application/json")
154155

156+
// Use context from request for proper cancellation propagation
155157
client := &http.Client{Timeout: 30 * time.Second}
156158
resp, err := client.Do(req)
157159
if err != nil {
@@ -187,26 +189,29 @@ func SyncProjectBugFixWorkflowToJira(c *gin.Context) {
187189
return
188190
case 404:
189191
websocket.BroadcastBugFixJiraSyncFailed(workflowID, workflow.GithubIssueNumber, "Jira project not found")
190-
c.JSON(http.StatusBadRequest, gin.H{"error": "Jira project not found", "details": string(body)})
192+
// Don't expose Jira error details to user - may contain sensitive info
193+
log.Printf("Jira 404 error details: %s", string(body))
194+
c.JSON(http.StatusBadRequest, gin.H{"error": "Jira project not found"})
191195
return
192196
default:
193-
websocket.BroadcastBugFixJiraSyncFailed(workflowID, workflow.GithubIssueNumber, fmt.Sprintf("Jira API error: %s", string(body)))
194-
c.JSON(http.StatusServiceUnavailable, gin.H{"error": fmt.Sprintf("Failed to create Jira issue (status %d)", resp.StatusCode), "details": string(body)})
197+
websocket.BroadcastBugFixJiraSyncFailed(workflowID, workflow.GithubIssueNumber, "Jira API error")
198+
// Log details for debugging, but don't expose to user
199+
log.Printf("Jira API error (status %d): %s", resp.StatusCode, string(body))
200+
c.JSON(http.StatusServiceUnavailable, gin.H{"error": fmt.Sprintf("Failed to create Jira issue (status %d)", resp.StatusCode)})
195201
return
196202
}
197203

198204
// Parse JSON response
199205
var result map[string]interface{}
200206
if err := json.Unmarshal(body, &result); err != nil {
201-
// Log the raw response for debugging
202-
fmt.Printf("ERROR: Failed to parse Jira response as JSON: %v\n", err)
207+
// Log the raw response for debugging (server-side only)
208+
log.Printf("ERROR: Failed to parse Jira response as JSON: %v", err)
203209
bodyLen := len(body)
204-
fmt.Printf("Response body (first 500 chars): %s\n", string(body[:min(500, bodyLen)]))
210+
log.Printf("Response body (first 500 chars): %s", string(body[:min(500, bodyLen)]))
205211
websocket.BroadcastBugFixJiraSyncFailed(workflowID, workflow.GithubIssueNumber, "Invalid Jira response")
212+
// Don't expose Jira response body to user - may contain sensitive details
206213
c.JSON(http.StatusInternalServerError, gin.H{
207-
"error": "Failed to parse Jira response",
208-
"details": err.Error(),
209-
"responsePreview": string(body[:min(200, bodyLen)]),
214+
"error": "Failed to parse Jira response",
210215
})
211216
return
212217
}
@@ -422,40 +427,6 @@ func formatGitHubJiraLinkComment(jiraTaskKey, jiraTaskURL string, workflow *type
422427
return comment.String()
423428
}
424429

425-
// formatGitHubJiraUpdateComment formats the comment to post on GitHub Issue when updating Jira task
426-
func formatGitHubJiraUpdateComment(jiraTaskKey, jiraTaskURL string, workflow *types.BugFixWorkflow) string {
427-
var comment strings.Builder
428-
429-
comment.WriteString("## 🔄 Jira Task Updated\n\n")
430-
comment.WriteString(fmt.Sprintf("Jira task [**%s**](%s) has been updated with the latest information.\n\n", jiraTaskKey, jiraTaskURL))
431-
432-
// Add links to analysis documents if available
433-
if workflow.Annotations != nil {
434-
hasGists := false
435-
if bugReviewGist := workflow.Annotations["bug-review-gist-url"]; bugReviewGist != "" {
436-
if !hasGists {
437-
comment.WriteString("### 📄 Latest Analysis\n\n")
438-
hasGists = true
439-
}
440-
comment.WriteString(fmt.Sprintf("- [Bug Review & Assessment](%s)\n", bugReviewGist))
441-
}
442-
if implGist := workflow.Annotations["implementation-gist-url"]; implGist != "" {
443-
if !hasGists {
444-
comment.WriteString("### 📄 Latest Analysis\n\n")
445-
hasGists = true
446-
}
447-
comment.WriteString(fmt.Sprintf("- [Implementation Details](%s)\n", implGist))
448-
}
449-
if hasGists {
450-
comment.WriteString("\n")
451-
}
452-
}
453-
454-
comment.WriteString("*Synchronized by vTeam BugFix Workspace*")
455-
456-
return comment.String()
457-
}
458-
459430
// getSuccessMessage returns appropriate success message
460431
func getSuccessMessage(created bool, jiraTaskKey string) string {
461432
if created {

components/backend/handlers/bugfix/session_webhook.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -447,8 +447,8 @@ func formatImplementationSummary(gistURL, sessionID, branchName, repoURL string,
447447
comment.WriteString("**Create a Pull Request** to merge these changes:\n\n")
448448
branchURL := fmt.Sprintf("%s/tree/%s", strings.TrimSuffix(repoURL, ".git"), branchName)
449449
comment.WriteString(fmt.Sprintf("1. 🌿 View changes: [%s](%s)\n", branchName, branchURL))
450-
comment.WriteString(fmt.Sprintf("2. 🔀 Click \"Contribute\"\"Open pull request\" on GitHub\n"))
451-
comment.WriteString(fmt.Sprintf("3. 📝 Review the changes and submit the PR\n\n"))
450+
comment.WriteString("2. 🔀 Click \"Contribute\"\"Open pull request\" on GitHub\n")
451+
comment.WriteString("3. 📝 Review the changes and submit the PR\n\n")
452452
}
453453

454454
comment.WriteString("**To review locally:**\n")
@@ -488,8 +488,8 @@ func formatImplementationComment(summary, sessionID, branchName, repoURL string,
488488
comment.WriteString("**Create a Pull Request** to merge these changes:\n\n")
489489
branchURL := fmt.Sprintf("%s/tree/%s", strings.TrimSuffix(repoURL, ".git"), branchName)
490490
comment.WriteString(fmt.Sprintf("1. 🌿 View changes: [%s](%s)\n", branchName, branchURL))
491-
comment.WriteString(fmt.Sprintf("2. 🔀 Click \"Contribute\"\"Open pull request\" on GitHub\n"))
492-
comment.WriteString(fmt.Sprintf("3. 📝 Review the changes and submit the PR\n\n"))
491+
comment.WriteString("2. 🔀 Click \"Contribute\"\"Open pull request\" on GitHub\n")
492+
comment.WriteString("3. 📝 Review the changes and submit the PR\n\n")
493493
}
494494

495495
comment.WriteString("**To review locally:**\n")

components/backend/handlers/bugfix/sessions.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package bugfix
22

33
import (
4-
"context"
54
"fmt"
65
"log"
76
"net/http"
@@ -427,7 +426,7 @@ func CreateProjectBugFixWorkflowSession(c *gin.Context) {
427426
},
428427
}
429428

430-
created, err := reqDyn.Resource(gvr).Namespace(project).Create(context.TODO(), sessionObj, v1.CreateOptions{})
429+
created, err := reqDyn.Resource(gvr).Namespace(project).Create(c.Request.Context(), sessionObj, v1.CreateOptions{})
431430
if err != nil {
432431
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create session", "details": err.Error()})
433432
return
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use client';
2+
3+
import { useEffect } from 'react';
4+
import { Button } from '@/components/ui/button';
5+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
6+
import { AlertCircle } from 'lucide-react';
7+
8+
export default function BugFixWorkflowDetailError({
9+
error,
10+
reset,
11+
}: {
12+
error: Error & { digest?: string };
13+
reset: () => void;
14+
}) {
15+
useEffect(() => {
16+
console.error('BugFix workflow detail error:', error);
17+
}, [error]);
18+
19+
return (
20+
<div className="container mx-auto p-6">
21+
<Card className="max-w-lg mx-auto mt-12">
22+
<CardHeader>
23+
<div className="flex items-center gap-2">
24+
<AlertCircle className="h-5 w-5 text-destructive" />
25+
<CardTitle>Failed to load BugFix workflow</CardTitle>
26+
</div>
27+
<CardDescription>
28+
{error.message || 'An unexpected error occurred while loading this workflow.'}
29+
</CardDescription>
30+
</CardHeader>
31+
<CardContent>
32+
<Button onClick={reset}>Try again</Button>
33+
</CardContent>
34+
</Card>
35+
</div>
36+
);
37+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Skeleton } from '@/components/ui/skeleton';
2+
import { Card, CardContent, CardHeader } from '@/components/ui/card';
3+
4+
export default function BugFixWorkflowDetailLoading() {
5+
return (
6+
<div className="container mx-auto p-6 space-y-6">
7+
{/* Header skeleton */}
8+
<Card>
9+
<CardHeader>
10+
<Skeleton className="h-8 w-3/4" />
11+
<Skeleton className="h-4 w-1/2 mt-2" />
12+
</CardHeader>
13+
</Card>
14+
15+
{/* Tabs skeleton */}
16+
<div className="flex gap-2">
17+
<Skeleton className="h-10 w-24" />
18+
<Skeleton className="h-10 w-24" />
19+
<Skeleton className="h-10 w-24" />
20+
</div>
21+
22+
{/* Content skeleton */}
23+
<Card>
24+
<CardHeader>
25+
<Skeleton className="h-6 w-48" />
26+
</CardHeader>
27+
<CardContent className="space-y-3">
28+
<Skeleton className="h-4 w-full" />
29+
<Skeleton className="h-4 w-5/6" />
30+
<Skeleton className="h-4 w-4/6" />
31+
</CardContent>
32+
</Card>
33+
</div>
34+
);
35+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use client';
2+
3+
import { useEffect } from 'react';
4+
import { Button } from '@/components/ui/button';
5+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
6+
import { AlertCircle } from 'lucide-react';
7+
8+
export default function BugFixWorkflowsError({
9+
error,
10+
reset,
11+
}: {
12+
error: Error & { digest?: string };
13+
reset: () => void;
14+
}) {
15+
useEffect(() => {
16+
console.error('BugFix workflows page error:', error);
17+
}, [error]);
18+
19+
return (
20+
<div className="container mx-auto p-6">
21+
<Card className="max-w-lg mx-auto mt-12">
22+
<CardHeader>
23+
<div className="flex items-center gap-2">
24+
<AlertCircle className="h-5 w-5 text-destructive" />
25+
<CardTitle>Failed to load BugFix workflows</CardTitle>
26+
</div>
27+
<CardDescription>
28+
{error.message || 'An unexpected error occurred while loading BugFix workflows.'}
29+
</CardDescription>
30+
</CardHeader>
31+
<CardContent>
32+
<Button onClick={reset}>Try again</Button>
33+
</CardContent>
34+
</Card>
35+
</div>
36+
);
37+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { TableSkeleton } from '@/components/skeletons';
2+
3+
export default function BugFixWorkflowsLoading() {
4+
return <TableSkeleton rows={8} columns={5} />;
5+
}

0 commit comments

Comments
 (0)