Check the origin's host and port; cap the size at 1 GiB (closes #61)
check / check (push) Successful in 2m33s
check / check (push) Successful in 2m33s
access_control_allow_origin now needs a host name (ASCII letters, digits, hyphens, dots) or an IP address, and a port, when given, from 1 to 65535, read with net/url, net/netip and strconv. Two hosts, an empty port, no host, a bad port or a non-ASCII host name abort startup, as a * inside the value already did. upstream_max_response_size above 1 GiB aborts startup: the image processor reads one byte past the limit, which wrapped negative at the largest 64-bit value, and a response is held whole in memory. config.example.yml states the maximum. Model: opus-5-5
This commit is contained in:
+67
-14
@@ -78,6 +78,7 @@ var (
|
||||
errEmptyEntry = errors.New("contains an empty entry")
|
||||
errNotAValidURL = errors.New("not a valid URL")
|
||||
errPortOutOfRange = errors.New("outside the valid port range")
|
||||
errSizeOutOfRange = errors.New("outside the accepted range")
|
||||
errTooFewConnections = errors.New("must be at least 1")
|
||||
errValueTooShort = errors.New("value too short")
|
||||
errPlaceholderKey = errors.New(
|
||||
@@ -578,10 +579,9 @@ func (c *Config) validate() error {
|
||||
settingName(keyCacheMaxBytes), c.CacheMaxBytes, errMustNotBeNegative)
|
||||
}
|
||||
|
||||
if c.UpstreamMaxResponseSize <= 0 {
|
||||
return fmt.Errorf("%s: value %d %w",
|
||||
settingName(keyUpstreamMaxResponseSize), c.UpstreamMaxResponseSize,
|
||||
errMustBePositive)
|
||||
err = c.validateUpstreamMaxResponseSize()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, host := range c.AllowlistHosts {
|
||||
@@ -608,29 +608,82 @@ func (c *Config) validate() error {
|
||||
return c.validateAccessControlAllowOrigin()
|
||||
}
|
||||
|
||||
// validateUpstreamMaxResponseSize checks that upstream_max_response_size
|
||||
// is from 1 byte to 1 GiB. An upstream response is read whole into
|
||||
// memory, and the image processor reads one byte past this limit, which
|
||||
// must not overflow.
|
||||
func (c *Config) validateUpstreamMaxResponseSize() error {
|
||||
const maxUpstreamMaxResponseSize = 1 << 30 // 1 GiB
|
||||
if c.UpstreamMaxResponseSize < 1 ||
|
||||
c.UpstreamMaxResponseSize > maxUpstreamMaxResponseSize {
|
||||
return fmt.Errorf("%s: value %d is %w 1-%d",
|
||||
settingName(keyUpstreamMaxResponseSize), c.UpstreamMaxResponseSize,
|
||||
errSizeOutOfRange, maxUpstreamMaxResponseSize)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateAccessControlAllowOrigin checks that access_control_allow_origin
|
||||
// is "*" or one origin, a scheme and host with nothing after them, as
|
||||
// browsers send it in the Origin header. Anything else, such as a bare
|
||||
// hostname or a trailing slash, would match no request. An origin with a
|
||||
// "*" inside, such as https://*example.com, is refused because the CORS
|
||||
// middleware reads that "*" as a pattern and would let other sites read
|
||||
// responses.
|
||||
// is "*" or one origin as browsers send it in the Origin header: a scheme,
|
||||
// a host name or IP address, and optionally a port from 1 to 65535, with
|
||||
// nothing after them. Anything else, such as a bare hostname or a trailing
|
||||
// slash, would match no request. A "*" is not allowed in a host name, so
|
||||
// https://*example.com is refused; the CORS middleware would read that
|
||||
// "*" as a pattern and let other sites read responses.
|
||||
func (c *Config) validateAccessControlAllowOrigin() error {
|
||||
origin := c.AccessControlAllowOrigin
|
||||
if origin == "*" {
|
||||
return nil
|
||||
}
|
||||
|
||||
errOrigin := fmt.Errorf("%s: value %q is %w",
|
||||
settingName(keyAccessControlAllowOrigin), origin, errNotAnOrigin)
|
||||
|
||||
// url.Parse accepts an empty port, as in https://example.com:, and
|
||||
// then reports no port, so a trailing colon is refused here.
|
||||
parsed, err := url.Parse(origin)
|
||||
if err != nil || parsed.Host == "" || parsed.Scheme+"://"+parsed.Host != origin ||
|
||||
strings.Contains(origin, "*") {
|
||||
return fmt.Errorf("%s: value %q is %w",
|
||||
settingName(keyAccessControlAllowOrigin), origin, errNotAnOrigin)
|
||||
if err != nil || parsed.Scheme+"://"+parsed.Host != origin ||
|
||||
strings.HasSuffix(origin, ":") {
|
||||
return errOrigin
|
||||
}
|
||||
|
||||
host := parsed.Hostname()
|
||||
|
||||
_, err = netip.ParseAddr(host)
|
||||
if err != nil && !isHostName(host) {
|
||||
return errOrigin
|
||||
}
|
||||
|
||||
// A port is a 16-bit number, and 0 is not a port a browser sends.
|
||||
if parsed.Port() != "" {
|
||||
port, err := strconv.ParseUint(parsed.Port(), 10, 16)
|
||||
if err != nil || port == 0 {
|
||||
return errOrigin
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isHostName reports whether host is not empty and has only ASCII
|
||||
// letters, digits, hyphens and dots.
|
||||
func isHostName(host string) bool {
|
||||
if host == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, r := range host {
|
||||
isLetterOrDigit := 'a' <= r && r <= 'z' || 'A' <= r && r <= 'Z' ||
|
||||
'0' <= r && r <= '9'
|
||||
if !isLetterOrDigit && r != '-' && r != '.' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// validateAllowlistHost checks that an allowlist_hosts entry is a bare
|
||||
// hostname, optionally with a leading dot for suffix matching. URLs,
|
||||
// paths, and whitespace indicate a misconfigured entry. An entry with
|
||||
|
||||
Reference in New Issue
Block a user