|
28 | 28 | // New tokens: ghp_ (personal), ghs_ (server), ghr_ (refresh), gho_ (OAuth), ghu_ (user-to-server) followed by base62 chars. |
29 | 29 | // Fine-grained tokens: github_pat_ followed by base62 chars. |
30 | 30 | githubTokenRegex = regexp.MustCompile(`^[a-f0-9]{40}$|^gh[psoru]_[A-Za-z0-9]{36,251}$|^github_pat_[A-Za-z0-9]{82}$`) |
31 | | - |
32 | | - // githubPRURLRegex validates strict GitHub PR URL format for auto-opening. |
33 | | - // Must match: https://github.com/{owner}/{repo}/pull/{number} |
34 | | - // Owner and repo follow GitHub naming rules, number is digits only. |
35 | | - githubPRURLRegex = regexp.MustCompile(`^https://github\.com/[a-zA-Z0-9][a-zA-Z0-9-]{0,38}/[a-zA-Z0-9][a-zA-Z0-9._-]{0,99}/pull/[1-9][0-9]{0,9}$`) |
36 | 31 | ) |
37 | 32 |
|
38 | 33 | // validateGitHubUsername validates a GitHub username. |
@@ -93,95 +88,3 @@ func sanitizeForLog(s string) string { |
93 | 88 |
|
94 | 89 | return s |
95 | 90 | } |
96 | | - |
97 | | -// validateURL performs strict validation on URLs. |
98 | | -func validateURL(rawURL string) error { |
99 | | - if rawURL == "" { |
100 | | - return errors.New("URL cannot be empty") |
101 | | - } |
102 | | - |
103 | | - // Check for null bytes or control characters |
104 | | - for _, r := range rawURL { |
105 | | - if r < minPrintableChar || r == deleteChar { |
106 | | - return errors.New("URL contains control characters") |
107 | | - } |
108 | | - } |
109 | | - |
110 | | - // Ensure URL starts with https:// |
111 | | - if !strings.HasPrefix(rawURL, "https://") { |
112 | | - return errors.New("URL must use HTTPS") |
113 | | - } |
114 | | - |
115 | | - // Check for URL length limits |
116 | | - if len(rawURL) > maxURLLength { |
117 | | - return errors.New("URL too long") |
118 | | - } |
119 | | - |
120 | | - return nil |
121 | | -} |
122 | | - |
123 | | -// validateGitHubPRURL performs strict validation for GitHub PR URLs used in auto-opening. |
124 | | -// This ensures the URL follows the exact pattern: https://github.com/{owner}/{repo}/pull/{number} |
125 | | -// with no additional path segments, fragments, or suspicious characters. |
126 | | -// The URL may optionally have ?goose=<action> parameter which we add for tracking. |
127 | | -func validateGitHubPRURL(rawURL string) error { |
128 | | - // First do basic URL validation |
129 | | - if err := validateURL(rawURL); err != nil { |
130 | | - return err |
131 | | - } |
132 | | - |
133 | | - // Strip the ?goose parameter if present for pattern validation |
134 | | - urlToValidate := rawURL |
135 | | - if idx := strings.Index(rawURL, "?goose="); idx != -1 { |
136 | | - urlToValidate = rawURL[:idx] |
137 | | - } |
138 | | - |
139 | | - // Check against strict GitHub PR URL pattern |
140 | | - if !githubPRURLRegex.MatchString(urlToValidate) { |
141 | | - return fmt.Errorf("URL does not match GitHub PR pattern: %s", urlToValidate) |
142 | | - } |
143 | | - |
144 | | - // Additional security checks |
145 | | - // Reject URLs with @ (potential credential injection) |
146 | | - if strings.Contains(rawURL, "@") { |
147 | | - return errors.New("URL contains @ character") |
148 | | - } |
149 | | - |
150 | | - // Reject URLs with URL encoding (could hide malicious content) |
151 | | - // Exception: %3D which is = in URL encoding, only as part of ?goose parameter |
152 | | - if strings.Contains(rawURL, "%") { |
153 | | - // Allow URL encoding only in the goose parameter value |
154 | | - idx := strings.Index(rawURL, "?goose=") |
155 | | - if idx == -1 { |
156 | | - return errors.New("URL contains encoded characters") |
157 | | - } |
158 | | - // Check if encoding is only in the goose parameter |
159 | | - if strings.Contains(rawURL[:idx], "%") { |
160 | | - return errors.New("URL contains encoded characters outside goose parameter") |
161 | | - } |
162 | | - } |
163 | | - |
164 | | - // Reject URLs with fragments |
165 | | - if strings.Contains(rawURL, "#") { |
166 | | - return errors.New("URL contains fragments") |
167 | | - } |
168 | | - |
169 | | - // Allow only ?goose=<value> query parameter, nothing else |
170 | | - if strings.Contains(rawURL, "?") { |
171 | | - // Check if it's the goose parameter |
172 | | - if idx := strings.Index(rawURL, "?goose="); idx == -1 { |
173 | | - return errors.New("URL contains unexpected query parameters") |
174 | | - } |
175 | | - // Ensure no additional parameters after goose |
176 | | - if strings.Contains(rawURL[strings.Index(rawURL, "?goose=")+7:], "&") { |
177 | | - return errors.New("URL contains additional query parameters") |
178 | | - } |
179 | | - } |
180 | | - |
181 | | - // Reject URLs with double slashes (except after https:) |
182 | | - if strings.Contains(rawURL[8:], "//") { |
183 | | - return errors.New("URL contains double slashes") |
184 | | - } |
185 | | - |
186 | | - return nil |
187 | | -} |
0 commit comments