44 "fmt"
55 "log"
66 "net/http"
7+ "regexp"
78 "strings"
89 "time"
910
2425 GetProjectSettingsResource func () schema.GroupVersionResource
2526)
2627
28+ // Input validation constants to prevent oversized inputs
29+ const (
30+ MaxTitleLength = 200 // GitHub Issue title limit is 256, use 200 for safety
31+ MaxSymptomsLength = 10000 // ~10KB for symptoms description
32+ MaxReproductionStepsLength = 10000 // ~10KB for reproduction steps
33+ MaxExpectedBehaviorLength = 5000 // ~5KB for expected behavior
34+ MaxActualBehaviorLength = 5000 // ~5KB for actual behavior
35+ MaxAdditionalContextLength = 10000 // ~10KB for additional context
36+ MaxBranchNameLength = 255 // Git branch name limit
37+ )
38+
39+ // validBranchNameRegex defines allowed characters in branch names
40+ // Allows: letters, numbers, hyphens, underscores, forward slashes, and dots
41+ // Prevents: shell metacharacters, backticks, quotes, semicolons, pipes, etc.
42+ var validBranchNameRegex = regexp .MustCompile (`^[a-zA-Z0-9/_.-]+$` )
43+
44+ // validateBranchName checks if a branch name is safe to use in git operations
45+ // Returns error if the branch name contains potentially dangerous characters
46+ func validateBranchName (branchName string ) error {
47+ if branchName == "" {
48+ return fmt .Errorf ("branch name cannot be empty" )
49+ }
50+ if ! validBranchNameRegex .MatchString (branchName ) {
51+ return fmt .Errorf ("branch name contains invalid characters (allowed: a-z, A-Z, 0-9, /, _, -, .)" )
52+ }
53+ // Prevent branch names that start with special characters
54+ if strings .HasPrefix (branchName , "." ) || strings .HasPrefix (branchName , "-" ) {
55+ return fmt .Errorf ("branch name cannot start with '.' or '-'" )
56+ }
57+ // Prevent branch names with ".." (path traversal) or "//" (double slashes)
58+ if strings .Contains (branchName , ".." ) || strings .Contains (branchName , "//" ) {
59+ return fmt .Errorf ("branch name cannot contain '..' or '//'" )
60+ }
61+ return nil
62+ }
63+
2764// CreateProjectBugFixWorkflow handles POST /api/projects/:projectName/bugfix-workflows
2865// Creates a new BugFix Workspace from either GitHub Issue URL or text description
2966func CreateProjectBugFixWorkflow (c * gin.Context ) {
@@ -94,18 +131,37 @@ func CreateProjectBugFixWorkflow(c *gin.Context) {
94131 c .JSON (http .StatusBadRequest , gin.H {"error" : "Title is required" })
95132 return
96133 }
97- if len (strings .TrimSpace (td .Title )) < 10 {
134+ titleLen := len (strings .TrimSpace (td .Title ))
135+ if titleLen < 10 {
98136 c .JSON (http .StatusBadRequest , gin.H {"error" : "Title must be at least 10 characters" })
99137 return
100138 }
139+ if titleLen > MaxTitleLength {
140+ c .JSON (http .StatusBadRequest , gin.H {
141+ "error" : fmt .Sprintf ("Title exceeds maximum length of %d characters" , MaxTitleLength ),
142+ "current" : titleLen ,
143+ "max" : MaxTitleLength ,
144+ })
145+ return
146+ }
147+
101148 if strings .TrimSpace (td .Symptoms ) == "" {
102149 c .JSON (http .StatusBadRequest , gin.H {"error" : "Symptoms are required" })
103150 return
104151 }
105- if len (strings .TrimSpace (td .Symptoms )) < 20 {
152+ symptomsLen := len (strings .TrimSpace (td .Symptoms ))
153+ if symptomsLen < 20 {
106154 c .JSON (http .StatusBadRequest , gin.H {"error" : "Symptoms must be at least 20 characters" })
107155 return
108156 }
157+ if symptomsLen > MaxSymptomsLength {
158+ c .JSON (http .StatusBadRequest , gin.H {
159+ "error" : fmt .Sprintf ("Symptoms exceed maximum length of %d characters" , MaxSymptomsLength ),
160+ "current" : symptomsLen ,
161+ "max" : MaxSymptomsLength ,
162+ })
163+ return
164+ }
109165 if strings .TrimSpace (td .TargetRepository ) == "" {
110166 c .JSON (http .StatusBadRequest , gin.H {"error" : "Target repository is required" })
111167 return
@@ -118,6 +174,40 @@ func CreateProjectBugFixWorkflow(c *gin.Context) {
118174 return
119175 }
120176
177+ // Validate optional field lengths
178+ if td .ReproductionSteps != nil && len (* td .ReproductionSteps ) > MaxReproductionStepsLength {
179+ c .JSON (http .StatusBadRequest , gin.H {
180+ "error" : fmt .Sprintf ("Reproduction steps exceed maximum length of %d characters" , MaxReproductionStepsLength ),
181+ "current" : len (* td .ReproductionSteps ),
182+ "max" : MaxReproductionStepsLength ,
183+ })
184+ return
185+ }
186+ if td .ExpectedBehavior != nil && len (* td .ExpectedBehavior ) > MaxExpectedBehaviorLength {
187+ c .JSON (http .StatusBadRequest , gin.H {
188+ "error" : fmt .Sprintf ("Expected behavior exceeds maximum length of %d characters" , MaxExpectedBehaviorLength ),
189+ "current" : len (* td .ExpectedBehavior ),
190+ "max" : MaxExpectedBehaviorLength ,
191+ })
192+ return
193+ }
194+ if td .ActualBehavior != nil && len (* td .ActualBehavior ) > MaxActualBehaviorLength {
195+ c .JSON (http .StatusBadRequest , gin.H {
196+ "error" : fmt .Sprintf ("Actual behavior exceeds maximum length of %d characters" , MaxActualBehaviorLength ),
197+ "current" : len (* td .ActualBehavior ),
198+ "max" : MaxActualBehaviorLength ,
199+ })
200+ return
201+ }
202+ if td .AdditionalContext != nil && len (* td .AdditionalContext ) > MaxAdditionalContextLength {
203+ c .JSON (http .StatusBadRequest , gin.H {
204+ "error" : fmt .Sprintf ("Additional context exceeds maximum length of %d characters" , MaxAdditionalContextLength ),
205+ "current" : len (* td .AdditionalContext ),
206+ "max" : MaxAdditionalContextLength ,
207+ })
208+ return
209+ }
210+
121211 // Generate issue body from template
122212 reproSteps := ""
123213 if td .ReproductionSteps != nil {
@@ -162,9 +252,14 @@ func CreateProjectBugFixWorkflow(c *gin.Context) {
162252 }
163253
164254 // Auto-generate branch name if not provided
165- branch := "main"
255+ var branch string
166256 if req .BranchName != nil && * req .BranchName != "" {
167257 branch = * req .BranchName
258+ // Validate user-provided branch name for security
259+ if err := validateBranchName (branch ); err != nil {
260+ c .JSON (http .StatusBadRequest , gin.H {"error" : "Invalid branch name" , "details" : err .Error ()})
261+ return
262+ }
168263 } else {
169264 // Auto-generate branch name: bugfix/gh-{issue-number}
170265 branch = fmt .Sprintf ("bugfix/gh-%d" , githubIssue .Number )
0 commit comments