fix: Reject URL path segments containing ".." in all request methods#4150
fix: Reject URL path segments containing ".." in all request methods#4150mohammadmseet-hue wants to merge 4 commits intogoogle:masterfrom
Conversation
The existing ErrPathForbidden check in GetContents correctly blocks path traversal via the "path" parameter, but the same validation was missing from: 1. NewRequest, NewFormRequest, NewUploadRequest — allowing traversal via owner, repo, org, user, enterprise, and team parameters across all API methods. 2. CreateFile, UpdateFile, DeleteFile — which use the same URL pattern as GetContents but lacked the ".." check on the path parameter. This adds a centralized containsDotDotPathSegment check in the three request-building methods, which protects all current and future API methods from path traversal via any URL-interpolated parameter. The check only matches ".." as a complete path segment (not substrings like "file..txt") and ignores query strings. Also adds the same strings.Contains(path, "..") guard that GetContents has to CreateFile, UpdateFile, and DeleteFile for defense in depth. Fixes google/go-github#XXXX
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4150 +/- ##
=======================================
Coverage 93.76% 93.76%
=======================================
Files 211 211
Lines 19701 19714 +13
=======================================
+ Hits 18472 18485 +13
Misses 1031 1031
Partials 198 198 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
gmlewis
left a comment
There was a problem hiding this comment.
Thank you, @mohammadmseet-hue!
One minor tweak, please, then we should be ready for a second LGTM+Approval from any other contributor to this repo before merging.
cc: @stevehipwell - @alexandear - @zyfy29 - @Not-Dhananjay-Mishra - @munlicode
Apply review suggestion from @gmlewis to simplify the loop into a single slices.Contains call.
github/github.go
Outdated
| func containsDotDotPathSegment(urlStr string) bool { | ||
| if !strings.Contains(urlStr, "..") { | ||
| return false | ||
| } | ||
| // Only check the path portion, ignore query string. | ||
| path := urlStr | ||
| if i := strings.IndexByte(path, '?'); i >= 0 { | ||
| path = path[:i] | ||
| } | ||
| return slices.Contains(strings.Split(path, "/"), "..") | ||
| } |
There was a problem hiding this comment.
| func containsDotDotPathSegment(urlStr string) bool { | |
| if !strings.Contains(urlStr, "..") { | |
| return false | |
| } | |
| // Only check the path portion, ignore query string. | |
| path := urlStr | |
| if i := strings.IndexByte(path, '?'); i >= 0 { | |
| path = path[:i] | |
| } | |
| return slices.Contains(strings.Split(path, "/"), "..") | |
| } | |
| func containsDotDotPathSegment(urlStr string) bool { | |
| if !strings.Contains(urlStr, "..") { | |
| return false | |
| } | |
| u, err := url.Parse(urlStr) | |
| if err != nil { | |
| // Not a valid URL so no path segment | |
| return false | |
| } | |
| return slices.Contains(u.Path, "..") | |
| } |
Is there a reason you using url.Parse() (which we use elsewhere in this package)?
I'd also suggest changing the name from containsDotDotPathSegment to urlContainsDotDotPathSegment and changing the signature to return an error if the URL can't be parsed.
github/github_test.go
Outdated
| } | ||
| } | ||
|
|
||
| func TestContainsDotDotPathSegment(t *testing.T) { |
There was a problem hiding this comment.
Is it intentional to not have any full URLs in here with a scheme, user or fragment?
|
Thanks for the review @stevehipwell! Good call on using Small note on the code suggestion: Applied all your suggestions:
|
github/github.go
Outdated
| if hasDotDot, err := urlContainsDotDotPathSegment(urlStr); err != nil { | ||
| return nil, err | ||
| } else if hasDotDot { | ||
| return nil, ErrPathForbidden |
There was a problem hiding this comment.
Please move the declaration ErrPathForbidden to this file.
github/github.go
Outdated
There was a problem hiding this comment.
This logic duplicates in NewRequest and NewFormRequest. We should extract into a helper.
github/repos_contents.go
Outdated
| if strings.Contains(path, "..") { | ||
| return nil, nil, ErrPathForbidden | ||
| } |
There was a problem hiding this comment.
This is already handled by s.client.NewRequest. Do we need it here? The same for other RepositoriesService functions: GetContents, UpdateFile, DeleteFile.
github/github.go
Outdated
| // segment (e.g. "a/../b"). It does not match ".." embedded within a segment | ||
| // (e.g. "file..txt"). The check is performed only on the path portion of the | ||
| // URL, ignoring any query string or fragment. | ||
| func urlContainsDotDotPathSegment(urlStr string) (bool, error) { |
There was a problem hiding this comment.
Let's declare this unexported function after the NewFormRequest for readability.
|
Thanks for the review @alexandear! Applied all 4 changes:
Net result: -101 lines added, +47 lines — cleaner and less duplication. |
…rn error Address review feedback from @stevehipwell: - Use url.Parse() for path extraction instead of manual string splitting - Rename containsDotDotPathSegment → urlContainsDotDotPathSegment - Change signature to return (bool, error) - Add full URL test cases (scheme, userinfo, fragment)
- Move ErrPathForbidden declaration to github.go - Simplify urlContainsDotDotPathSegment → checkURLPathTraversal returning error directly, eliminating duplicated calling pattern - Move checkURLPathTraversal after NewFormRequest for readability - Remove redundant ".." checks from GetContents, CreateFile, UpdateFile, DeleteFile — NewRequest already handles path traversal validation
8a102a0 to
33a356c
Compare
| if err := checkURLPathTraversal(urlStr); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| u, err := c.BaseURL.Parse(urlStr) |
There was a problem hiding this comment.
Here we are calling url.Parse twice. Could we eliminate the redundant parse?
There was a problem hiding this comment.
I believe the double url.Parse is actually necessary here. The fast path (strings.Contains) exits without any parsing for 99%+ of calls — url.Parse inside checkURLPathTraversal only runs when .. is present, and in that case we almost always return ErrPathForbidden before reaching c.BaseURL.Parse.
We also can't move the check after c.BaseURL.Parse — it resolves .. segments during URL resolution (e.g. repos/x/../../../admin → /admin), so the .. would be gone and the check would never trigger. The traversal detection has to happen on the raw input before resolution.
The only case where both parses run is something like repos/owner/file..txt (contains .. substring but not as a path segment) — edge case with negligible cost.
Summary
The existing
ErrPathForbiddencheck inGetContents(added in #2805) correctly blocks path traversal via thepathparameter, with the comment:However, the same validation was missing in two places:
NewRequest,NewFormRequest,NewUploadRequest—owner,repo,org,user,enterprise, andteamparameters are interpolated into URL paths viafmt.Sprintfbefore being passed to these methods. A value likex/../../../adminin any of these parameters causes the resolved URL to reach unintended API endpoints, and the client'sAuthorizationheader is sent along with the request.CreateFile,UpdateFile,DeleteFile— these use the samerepos/%v/%v/contents/%vURL pattern asGetContentsbut lacked the..check on thepathparameter.Fix
containsDotDotPathSegmentcheck in all three request-building methods (NewRequest,NewFormRequest,NewUploadRequest). This protects all current and future API methods from path traversal via any URL-interpolated parameter...as a complete path segment (not substrings likefile..txt) and ignores query strings.strings.Contains(path, "..")guard toCreateFile,UpdateFile, andDeleteFilefor defense in depth (consistent withGetContents).Example
Impact
Any application using go-github that passes user-controlled input as
owner,repo, ororgparameters could have its authentication token sent to unintended GitHub API endpoints. This affects all API methods that interpolate these parameters into URL paths.Test plan
TestContainsDotDotPathSegment— unit tests for the helper functionTestNewRequest_pathTraversal— verifiesNewRequestrejects..path segmentsTestNewFormRequest_pathTraversal— verifiesNewFormRequestrejects..path segmentsTestNewUploadRequest_pathTraversal— verifiesNewUploadRequestrejects..path segmentsTestRepositoriesService_CreateFile_PathWithParent— verifiesCreateFilerejects..in pathTestRepositoriesService_UpdateFile_PathWithParent— verifiesUpdateFilerejects..in pathTestRepositoriesService_DeleteFile_PathWithParent— verifiesDeleteFilerejects..in pathgo test ./github/)