From f17e4684e0029307c2447c6dd32a153aa53341cd Mon Sep 17 00:00:00 2001 From: Sanaei Date: Thu, 3 Sep 2026 18:06:35 +0200 Subject: [PATCH] fix(sub): apply the device limit to ?view=raw subJsons and subClashs served the raw body and returned before enforceHwid ran, so appending ?view=raw to a JSON or Clash subscription URL handed out a complete, client-consumable config however many devices were already registered. The branch exists to stop a browser's Accept: text/html from being answered with the info page, not to skip the gate. Gate the raw branch and leave the other gate where it was, below maybeServeSubPage, so the HTML info page stays ungated as before. --- internal/sub/controller.go | 9 ++++++++- internal/sub/hwid_controller_test.go | 28 ++++++++++++++++++++-------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/internal/sub/controller.go b/internal/sub/controller.go index 70f3dd335..3f592d495 100644 --- a/internal/sub/controller.go +++ b/internal/sub/controller.go @@ -705,9 +705,13 @@ func (a *SUBController) loadSubTemplate(themeDir string) (*template.Template, er return tmpl, nil } -// subJsons handles HTTP requests for JSON subscription configurations. +// subJsons handles HTTP requests for JSON subscription configurations. The +// device limit is enforced on every body route, ?view=raw included (#GHSA-7ww3). func (a *SUBController) subJsons(c *gin.Context) { if strings.EqualFold(c.Query("view"), "raw") { + if !a.enforceHwid(c) { + return + } if !a.serveJsonBody(c, a.jsonAlwaysArray, "application/json; charset=utf-8", true) { writeSubError(c, nil) } @@ -760,6 +764,9 @@ func (a *SUBController) serveJsonBody(c *gin.Context, alwaysReturnArray bool, co func (a *SUBController) subClashs(c *gin.Context) { if strings.EqualFold(c.Query("view"), "raw") { + if !a.enforceHwid(c) { + return + } if !a.serveClashBody(c, true) { writeSubError(c, nil) } diff --git a/internal/sub/hwid_controller_test.go b/internal/sub/hwid_controller_test.go index c2a871198..7e7d3892c 100644 --- a/internal/sub/hwid_controller_test.go +++ b/internal/sub/hwid_controller_test.go @@ -87,7 +87,17 @@ func requestSub(t *testing.T, router *gin.Engine, method string, path string, hw func TestSubscriptionHwidGateAcrossBodyRoutes(t *testing.T) { router, subID := initHwidSubRouter(t, 1) - for _, path := range []string{"/sub/" + subID, "/json/" + subID, "/clash/" + subID} { + // ?view=raw only tells /json/ and /clash/ to serve the body instead of the + // HTML page, so it stays gated like the plain route (#GHSA-7ww3). + bodyRoutes := []string{ + "/sub/" + subID, + "/json/" + subID, + "/clash/" + subID, + "/json/" + subID + "?view=raw", + "/clash/" + subID + "?view=RaW", + } + + for _, path := range bodyRoutes { rec := requestSub(t, router, http.MethodGet, path, "", "") if rec.Code != http.StatusNotFound { t.Fatalf("%s missing HWID status = %d, want 404", path, rec.Code) @@ -102,7 +112,7 @@ func TestSubscriptionHwidGateAcrossBodyRoutes(t *testing.T) { t.Fatalf("HEAD missing HWID = %d %#v", rec.Code, rec.Header()) } - for _, path := range []string{"/sub/" + subID, "/json/" + subID, "/clash/" + subID} { + for _, path := range bodyRoutes { rec = requestSub(t, router, http.MethodGet, path, "device-one", "") if rec.Code != http.StatusOK { t.Fatalf("%s registered HWID status = %d, body=%q", path, rec.Code, rec.Body.String()) @@ -112,12 +122,14 @@ func TestSubscriptionHwidGateAcrossBodyRoutes(t *testing.T) { } } - rec = requestSub(t, router, http.MethodGet, "/json/"+subID, "device-two", "") - if rec.Code != http.StatusNotFound { - t.Fatalf("new HWID after limit status = %d, want 404", rec.Code) - } - if rec.Header().Get("X-Hwid-Max-Devices-Reached") != "true" || rec.Header().Get("X-Hwid-Limit") != "true" { - t.Fatalf("limit headers missing: %#v", rec.Header()) + for _, path := range bodyRoutes { + rec = requestSub(t, router, http.MethodGet, path, "device-two", "") + if rec.Code != http.StatusNotFound { + t.Fatalf("%s new HWID after limit status = %d, want 404", path, rec.Code) + } + if rec.Header().Get("X-Hwid-Max-Devices-Reached") != "true" || rec.Header().Get("X-Hwid-Limit") != "true" { + t.Fatalf("%s limit headers missing: %#v", path, rec.Header()) + } } }