fix: handle backslash-started paths in cleanPath

Resolves remaining Copilot review concern: paths starting with a bare
backslash (e.g. \evil.com) were not caught by the previous guard and
would be converted to /\evil.com by the 'missing root' logic, still
enabling an open-redirect attack.

Replace the two-part guard (loop for '//' + single-replace for '/\')
with a unified loop that strips any leading run of '/' and '\' down to
exactly one '/', covering:
  - //example.com  → /example.com
  - /\example.com → /example.com
  - \example.com  → /example.com
  - \\example.com → /example.com

Add corresponding test cases.
This commit is contained in:
Nimer@Tornado 2026-07-05 05:04:38 +03:00 committed by Mohamad Nimer
parent d7de89f01f
commit 6deaa8e1a1
2 changed files with 19 additions and 6 deletions

19
path.go
View File

@ -25,12 +25,19 @@ func cleanPath(p string) string {
if p == "" {
return "/"
}
// Prevent scheme-relative ("//...") or backslash-based absolute ("/\\...") paths.
for len(p) > 1 && p[0] == '/' && p[1] == '/' {
p = p[1:]
}
if len(p) > 1 && p[0] == '/' && p[1] == '\\' {
p = "/" + p[2:]
// Prevent scheme-relative or backslash-based absolute redirects by
// normalizing any leading run of '/' and '\' down to exactly one '/'.
if len(p) > 0 && (p[0] == '/' || p[0] == '\\') {
i := 0
for i < len(p) && (p[i] == '/' || p[i] == '\\') {
i++
}
if i == len(p) {
return "/"
}
if i > 1 || p[0] == '\\' {
p = "/" + p[i:]
}
}
// Reasonably sized buffer on stack to avoid allocations in the common case.

View File

@ -42,6 +42,12 @@ var cleanTests = []cleanPathTest{
{"///abc", "/abc"},
{"//abc//", "/abc/"},
// Prevent scheme-relative and backslash-based open redirect (security)
{"/\\evil.com", "/evil.com"},
{"\\evil.com", "/evil.com"},
{"\\\\evil.com", "/evil.com"},
{"/\\/evil.com", "/evil.com"},
// Remove . elements
{".", "/"},
{"./", "/"},