From 62cef3678b219d0daf15dad040bd3df9384e5162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Fri, 1 May 2026 14:17:06 +0200 Subject: [PATCH] security: Allow hostnames starting with digits in default http.urls Domains like 1password.com and 37signals.com were blocked by the default allow rule '^https?://[a-z]'. Allow [a-z0-9] for the first hostname char and add an explicit deny for hosts whose first label is all-digit (IP literals like 127.0.0.1) to retain the prior SSRF protections. Fixes #14837 --- config/security/securityConfig.go | 3 ++- config/security/securityConfig_test.go | 27 +++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/config/security/securityConfig.go b/config/security/securityConfig.go index 0e9208790..3ecc67fcf 100644 --- a/config/security/securityConfig.go +++ b/config/security/securityConfig.go @@ -57,7 +57,8 @@ var DefaultConfig = Config{ // foil the obvious SSRF bypass. Public IP literals are collateral // blocks; users who need them can override security.http.urls. URLs: MustNewWhitelist( - `(?i)^https?://[a-z]`, + `(?i)^https?://[a-z0-9]`, + `! ^https?://\d+\.`, `! (?i)localhost`, `! (?i)^https?://[^/?#]*@`, ), diff --git a/config/security/securityConfig_test.go b/config/security/securityConfig_test.go index 4e120a285..da82b0c49 100644 --- a/config/security/securityConfig_test.go +++ b/config/security/securityConfig_test.go @@ -135,7 +135,7 @@ func TestToTOML(t *testing.T) { got := DefaultConfig.ToTOML() c.Assert(got, qt.Equals, - "[security]\n enableInlineShortcodes = false\n\n [security.exec]\n allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^git$', '^node$', '^postcss$', '^tailwindcss$']\n osEnv = ['(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG|SYSTEMDRIVE|PROGRAMDATA)$']\n\n [security.funcs]\n getenv = ['^HUGO_', '^CI$']\n\n [security.http]\n methods = ['(?i)GET|POST']\n urls = ['(?i)^https?://[a-z]', '! (?i)localhost', '! (?i)^https?://[^/?#]*@']\n\n [security.node]\n [security.node.permissions]\n allowAddons = ['tailwindcss']\n allowChildProcess = ['tailwindcss']\n allowRead = ['.']\n allowWorker = ['tailwindcss']\n allowWrite = []\n disable = false", + "[security]\n enableInlineShortcodes = false\n\n [security.exec]\n allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^git$', '^node$', '^postcss$', '^tailwindcss$']\n osEnv = ['(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG|SYSTEMDRIVE|PROGRAMDATA)$']\n\n [security.funcs]\n getenv = ['^HUGO_', '^CI$']\n\n [security.http]\n methods = ['(?i)GET|POST']\n urls = ['(?i)^https?://[a-z0-9]', '! ^https?://\\d+\\.', '! (?i)localhost', '! (?i)^https?://[^/?#]*@']\n\n [security.node]\n [security.node.permissions]\n allowAddons = ['tailwindcss']\n allowChildProcess = ['tailwindcss']\n allowRead = ['.']\n allowWorker = ['tailwindcss']\n allowWrite = []\n disable = false", ) } @@ -273,6 +273,31 @@ func TestCheckAllowedHTTPURLAtInPathIssue14825(t *testing.T) { } } +func TestCheckAllowedHTTPURLDigitHostnameIssue14837(t *testing.T) { + t.Parallel() + c := qt.New(t) + + pc, err := DecodeConfig(config.New()) + c.Assert(err, qt.IsNil) + + for _, u := range []string{ + "https://1password.com/", + "https://37signals.com/foo", + } { + c.Assert(pc.CheckAllowedHTTPURL(u), qt.IsNil, qt.Commentf(u)) + } + + for _, u := range []string{ + "http://127.0.0.1/", + "http://10.0.0.1/", + "http://192.168.1.1/", + "http://0.0.0.0/", + } { + err := pc.CheckAllowedHTTPURL(u) + c.Assert(err, qt.IsNotNil, qt.Commentf(u)) + } +} + func TestDecodeConfigNodePermissions(t *testing.T) { c := qt.New(t)