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
This commit is contained in:
Bjørn Erik Pedersen
2026-05-01 14:17:06 +02:00
parent ff22c62a32
commit 62cef3678b
2 changed files with 28 additions and 2 deletions
+2 -1
View File
@@ -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?://[^/?#]*@`,
),
+26 -1
View File
@@ -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)