Skip to content

Commit 2c0bf8d

Browse files
vishrclaude
andcommitted
fix(static): use path.Clean for fs.FS resolution to block Windows backslash bypass
CI on windows-latest caught that StaticDirectoryHandler resolved the static file name with filepath.Clean + filepath.ToSlash, which is OS-specific. An encoded backslash (%5C) decodes to a literal '\' that the router does not treat as a separator (it matches on the raw/canonical path), but on Windows filepath.Clean then interprets '\' as a path separator and resolves a file across a boundary the route never authorized -- serving /admin%5Csecret.txt as admin/secret.txt (the GHSA-pgvm-wxw2-hrv9 Windows backslash traversal class). On Linux '\' stays literal, which is why local tests passed. fs.FS paths are always forward-slash, so use path.Clean (OS-independent), matching what middleware/static.go already does. The HasEncodedPathSeparator guard still covers the %2F case where the param stays percent-encoded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 840cb19 commit 2c0bf8d

1 file changed

Lines changed: 6 additions & 2 deletions

File tree

echo.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import (
5353
"net/url"
5454
"os"
5555
"os/signal"
56+
"path"
5657
"path/filepath"
5758
"strings"
5859
"sync"
@@ -606,8 +607,11 @@ func StaticDirectoryHandler(fileSystem fs.FS, disablePathUnescaping bool) Handle
606607
p = tmpPath
607608
}
608609

609-
// fs.FS.Open() already assumes that file names are relative to FS root path and considers name with prefix `/` as invalid
610-
name := filepath.ToSlash(filepath.Clean(strings.TrimPrefix(p, "/")))
610+
// fs.FS.Open() already assumes that file names are relative to FS root path and considers name with prefix `/` as invalid.
611+
// Use path.Clean (not filepath.Clean): fs.FS paths are always forward-slash, so a backslash must stay a literal
612+
// character rather than being interpreted as a separator on Windows (which would resolve a file across a boundary
613+
// the router never matched on, the same Windows backslash traversal class as GHSA-pgvm-wxw2-hrv9).
614+
name := path.Clean(strings.TrimPrefix(p, "/"))
611615
fi, err := fs.Stat(fileSystem, name)
612616
if err != nil {
613617
return ErrNotFound

0 commit comments

Comments
 (0)