mirror of
https://github.com/henrygd/beszel.git
synced 2026-09-26 18:04:38 +00:00
hub: return 404 status for unknown frontend routes (#2414)
This commit is contained in:
@@ -28,6 +28,32 @@ func modifyIndexHTML(hub *Hub, html []byte) string {
|
||||
return strings.Replace(htmlContent, "\"{info}\"", string(content), 1)
|
||||
}
|
||||
|
||||
// isAppRoute reports whether urlPath matches a frontend route, so unknown paths
|
||||
// can be served with a 404 status. The base path prefix is optional because
|
||||
// reverse proxies may or may not strip it before forwarding.
|
||||
//
|
||||
// Keep in sync with routes in internal/site/src/components/router.tsx.
|
||||
func isAppRoute(urlPath, basePath string) bool {
|
||||
urlPath = strings.ToLower(urlPath)
|
||||
if base := strings.TrimSuffix(strings.ToLower(basePath), "/"); base != "" {
|
||||
if rest, ok := strings.CutPrefix(urlPath, base); ok && (rest == "" || rest[0] == '/') {
|
||||
urlPath = rest
|
||||
}
|
||||
}
|
||||
urlPath = strings.TrimSuffix(urlPath, "/")
|
||||
switch urlPath {
|
||||
case "", "/containers", "/smart", "/monitors", "/settings", "/forgot-password", "/request-otp":
|
||||
return true
|
||||
}
|
||||
// routes with a single required (/system/:id) or optional (/settings/:name?) param
|
||||
for _, prefix := range [...]string{"/system/", "/settings/"} {
|
||||
if param, ok := strings.CutPrefix(urlPath, prefix); ok {
|
||||
return param != "" && !strings.Contains(param, "/")
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getPublicAppInfo(hub *Hub) PublicAppInfo {
|
||||
parsedURL, _ := url.Parse(hub.appURL)
|
||||
info := PublicAppInfo{
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
func (h *Hub) startServer(se *core.ServeEvent) error {
|
||||
indexFile, _ := fs.ReadFile(site.DistDirFS, "index.html")
|
||||
html := modifyIndexHTML(h, indexFile)
|
||||
basePath := getPublicAppInfo(h).BASE_PATH
|
||||
// set up static asset serving
|
||||
staticPaths := [2]string{"/static/", "/assets/"}
|
||||
serveStatic := apis.Static(site.DistDirFS, false)
|
||||
@@ -36,7 +37,13 @@ func (h *Hub) startServer(se *core.ServeEvent) error {
|
||||
e.Response.Header().Del("X-Frame-Options")
|
||||
e.Response.Header().Set("Content-Security-Policy", csp)
|
||||
}
|
||||
return e.HTML(http.StatusOK, html)
|
||||
// still serve the app for unknown paths (it renders a 404 page),
|
||||
// but with a 404 status so scanners and fail2ban see the miss
|
||||
status := http.StatusOK
|
||||
if !isAppRoute(e.Request.URL.Path, basePath) {
|
||||
status = http.StatusNotFound
|
||||
}
|
||||
return e.HTML(status, html)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
//go:build testing
|
||||
|
||||
package hub
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsAppRoute(t *testing.T) {
|
||||
tests := []struct {
|
||||
path string
|
||||
basePath string
|
||||
want bool
|
||||
}{
|
||||
// known routes
|
||||
{"/", "/", true},
|
||||
{"/containers", "/", true},
|
||||
{"/containers/", "/", true},
|
||||
{"/Containers", "/", true},
|
||||
{"/smart", "/", true},
|
||||
{"/monitors", "/", true},
|
||||
{"/forgot-password", "/", true},
|
||||
{"/request-otp", "/", true},
|
||||
{"/system/abc123", "/", true},
|
||||
{"/system/abc123/", "/", true},
|
||||
{"/settings", "/", true},
|
||||
{"/settings/general", "/", true},
|
||||
|
||||
// unknown paths
|
||||
{"/.env", "/", false},
|
||||
{"/phpinfo.php", "/", false},
|
||||
{"/wp-admin/", "/", false},
|
||||
{"/.git/config", "/", false},
|
||||
{"/system", "/", false},
|
||||
{"/system/", "/", false},
|
||||
{"/system/abc/def", "/", false},
|
||||
{"/settings/general/extra", "/", false},
|
||||
{"/containersx", "/", false},
|
||||
|
||||
// base path, prefix not stripped by proxy
|
||||
{"/beszel", "/beszel/", true},
|
||||
{"/beszel/", "/beszel/", true},
|
||||
{"/beszel/containers", "/beszel/", true},
|
||||
{"/beszel/system/abc123", "/beszel/", true},
|
||||
{"/beszel/.env", "/beszel/", false},
|
||||
{"/beszelx", "/beszel/", false},
|
||||
|
||||
// base path, prefix stripped by proxy
|
||||
{"/", "/beszel/", true},
|
||||
{"/containers", "/beszel/", true},
|
||||
{"/.env", "/beszel/", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(t, tt.want, isAppRoute(tt.path, tt.basePath), "path=%q basePath=%q", tt.path, tt.basePath)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user