fix(sub): prevent default profile page URL disclosure (#6538)

* fix(sub): prevent default profile page URL disclosure

Add explicit none, builtin, and custom profile page modes.
Preserve existing custom URLs and warn before exposing the built-in page.
Cover mode selection, legacy settings, and subscription response headers.

* fix(subscription): add profile page link options and upgrade notes
This commit is contained in:
NgaiYeanCoi
2026-09-16 03:13:29 +08:00
committed by GitHub
parent 3fa44915c1
commit 1d85ef138e
36 changed files with 747 additions and 47 deletions
+16 -6
View File
@@ -53,6 +53,7 @@ type cachedSubTemplate struct {
type SUBController struct {
subTitle string
subSupportUrl string
subProfileMode string
subProfileUrl string
subAnnounce string
subEnableRouting bool
@@ -115,6 +116,7 @@ type subControllerConfig struct {
subTitle string
subSupportURL string
subProfileMode string
subProfileURL string
subAnnounce string
subEnableRouting bool
@@ -224,6 +226,10 @@ func WithSUBProfileURL(value string) SUBControllerOption {
return func(config *subControllerConfig) { config.subProfileURL = value }
}
func WithSUBProfileMode(value string) SUBControllerOption {
return func(config *subControllerConfig) { config.subProfileMode = value }
}
func WithSUBAnnounce(value string) SUBControllerOption {
return func(config *subControllerConfig) { config.subAnnounce = value }
}
@@ -260,6 +266,7 @@ func defaultSUBControllerConfig() subControllerConfig {
subEncrypt: true,
remarkTemplate: service.DefaultRemarkTemplate,
updateInterval: "12",
subProfileMode: service.SubProfileModeNone,
}
}
@@ -277,6 +284,7 @@ func NewSUBController(g *gin.RouterGroup, options ...SUBControllerOption) *SUBCo
a := &SUBController{
subTitle: config.subTitle,
subSupportUrl: config.subSupportURL,
subProfileMode: config.subProfileMode,
subProfileUrl: config.subProfileURL,
subAnnounce: config.subAnnounce,
subEnableRouting: config.subEnableRouting,
@@ -485,8 +493,7 @@ func (a *SUBController) subs(c *gin.Context) {
// Add headers
header := subReq.subscriptionUserinfo(traffic)
profileURL := fmt.Sprintf("%s://%s%s", scheme, hostWithPort, c.Request.RequestURI)
metadata := a.metadataForSubRequest(func() *SubService { return subReq }, subId, profileURL)
metadata := a.metadataForSubRequest(func() *SubService { return subReq }, subId, builtinProfileURL(c, scheme, hostWithPort))
a.ApplyCommonHeaders(c, header, a.updateInterval, metadata.Title, metadata.SupportURL, metadata.ProfileURL, metadata.Announce, a.subEnableRouting, a.subRoutingRules, a.subHideSettings)
if a.subIncyEnableRouting && a.subIncyRoutingRules != "" {
@@ -818,14 +825,13 @@ func (a *SUBController) serveJsonBody(c *gin.Context, alwaysReturnArray bool, co
if len(jsonSub) == 0 && header == "" {
return false
}
profileURL := fmt.Sprintf("%s://%s%s", scheme, hostWithPort, c.Request.RequestURI)
var subReq *SubService
metadata := a.metadataForSubRequest(func() *SubService {
if subReq == nil {
subReq = a.subService.ForRequest(host)
}
return subReq
}, subId, profileURL)
}, subId, builtinProfileURL(c, scheme, hostWithPort))
a.ApplyCommonHeaders(c, header, a.updateInterval, metadata.Title, metadata.SupportURL, metadata.ProfileURL, metadata.Announce, a.subEnableRouting, a.subRoutingRules, a.subHideSettings)
if rawDownload {
c.Writer.Header().Set("Content-Disposition", `attachment; filename="subscription.json"`)
@@ -887,14 +893,13 @@ func (a *SUBController) serveClashBody(c *gin.Context, rawDownload bool, legacy
if len(clashSub) == 0 && header == "" {
return false
}
profileURL := fmt.Sprintf("%s://%s%s", scheme, hostWithPort, c.Request.RequestURI)
var subReq *SubService
metadata := a.metadataForSubRequest(func() *SubService {
if subReq == nil {
subReq = a.subService.ForRequest(host)
}
return subReq
}, subId, profileURL)
}, subId, builtinProfileURL(c, scheme, hostWithPort))
a.ApplyCommonHeaders(c, header, a.updateInterval, metadata.Title, metadata.SupportURL, metadata.ProfileURL, metadata.Announce, a.subEnableRouting, a.subRoutingRules, a.subHideSettings)
if rawDownload {
c.Writer.Header().Set("Content-Disposition", `attachment; filename="subscription.yaml"`)
@@ -906,6 +911,11 @@ func (a *SUBController) serveClashBody(c *gin.Context, rawDownload bool, legacy
return true
}
func builtinProfileURL(c *gin.Context, scheme, hostWithPort string) string {
// Drop download/format selectors so the opt-in link always opens the HTML page.
return fmt.Sprintf("%s://%s%s?html=1", scheme, hostWithPort, c.Request.URL.EscapedPath())
}
// ApplyCommonHeaders sets common HTTP headers for subscription responses including user info, update interval, and profile title.
func (a *SUBController) ApplyCommonHeaders(
c *gin.Context,