Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -884,14 +884,12 @@ func (c *Client) NewFormRequest(ctx context.Context, urlStr string, body io.Read
}

// checkURLPathTraversal returns ErrPathForbidden if urlStr contains ".." as a
// path segment (e.g. "a/../b"), preventing path traversal attacks. 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.
// path segment (e.g. "a/../b"), preventing path traversal attacks. Percent-
// encoded equivalents such as "%2e%2e" are also rejected because url.Parse
// decodes them to ".." before the check runs. 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 checkURLPathTraversal(urlStr string) error {
if !strings.Contains(urlStr, "..") {
return nil
}
u, err := url.Parse(urlStr)
if err != nil {
return err
Expand Down
6 changes: 6 additions & 0 deletions github/github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1680,6 +1680,12 @@ func TestCheckURLPathTraversal(t *testing.T) {
// URL with userinfo.
{"https://user:pass@api.github.com/repos/../admin", ErrPathForbidden},
{"https://user:pass@api.github.com/repos/o/r", nil},
// Percent-encoded dots (%2e%2e) — url.Parse decodes them to ".." in Path.
{"repos/%2e%2e/admin", ErrPathForbidden},
{"repos/%2E%2E/admin", ErrPathForbidden},
{"repos/x/%2e%2e/%2e%2e/%2e%2e/admin", ErrPathForbidden},
{"x/%2e%2e/%2e%2e/%2e%2e/admin/users", ErrPathForbidden},
{"repos/o/r/contents/file%2e%2etxt", nil},
}
for _, tt := range tests {
err := checkURLPathTraversal(tt.input)
Expand Down
Loading