diff --git a/README.md b/README.md index 1f23d26..8597c53 100644 --- a/README.md +++ b/README.md @@ -396,9 +396,9 @@ and the URL is `images.example.com`, and `example.com` An IP address is matched exactly; write an IPv6 address without brackets. An -entry that is neither a host name (letters, digits, hyphens and dots, with at -most one leading dot) nor an IP address, such as one with a port or a `*.` -wildcard, aborts startup. +entry that is neither a host name (letters, digits, hyphens, underscores and +dots, with at most one leading dot) nor an IP address, such as one with a port +or a `*.` wildcard, aborts startup. ### Configuration diff --git a/TODO.md b/TODO.md index 5eb1d53..9307f32 100644 --- a/TODO.md +++ b/TODO.md @@ -34,15 +34,15 @@ P2: security: per-IP rate limiting on the image routes - 2026-10-04 referer blocklist (closes #90): `referer_blocklist` (`PIXA_REFERER_BLOCKLIST`) lists hosts, written and matched as for `allowlist_hosts` with the same matcher; an entry of either list that is - neither a host name (letters, digits, hyphens and dots, with at most one - leading dot) nor an IP address, such as one with a port or a `*.` wildcard, - aborts startup naming the setting and the entry. Both image routes refuse a - request whose `Referer` names a listed host with 403 and a JSON error before - the signature, the cache and the upstream fetch, so it fetches nothing and is - refused whether or not the image is cached. A request with no `Referer`, or - one that does not parse as a URL with a host, is served, so the list is easily - got around; `README.md` and `configs/config.example.yml` say so. It does not - apply to the login and generator pages. + neither a host name (letters, digits, hyphens, underscores and dots, with at + most one leading dot) nor an IP address, such as one with a port or a `*.` + wildcard, aborts startup naming the setting and the entry. Both image routes + refuse a request whose `Referer` names a listed host with 403 and a JSON error + before the signature, the cache and the upstream fetch, so it fetches nothing + and is refused whether or not the image is cached. A request with no + `Referer`, or one that does not parse as a URL with a host, is served, so the + list is easily got around; `README.md` and `configs/config.example.yml` say + so. It does not apply to the login and generator pages. - 2026-10-04 fewer files in the repository root (closes #97): `config.example.yml` moved unchanged to `configs/config.example.yml`, and `README.md`, the comments in `internal/config/config.go` and the startup error diff --git a/internal/config/config.go b/internal/config/config.go index 0a5991e..f4e0fc4 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -742,14 +742,15 @@ func (c *Config) validateConcurrencyLimits() error { return nil } -// hostNamePattern matches a host name: letters, digits, hyphens and dots, -// optionally after one leading dot. -var hostNamePattern = regexp.MustCompile(`^\.?[A-Za-z0-9-][A-Za-z0-9.-]*$`) +// hostNamePattern matches a host name: letters, digits, hyphens, underscores +// and dots, optionally after one leading dot. +var hostNamePattern = regexp.MustCompile(`^\.?[A-Za-z0-9_-][A-Za-z0-9_.-]*$`) // validateHostPattern checks that an entry of the named key, allowlist_hosts // or referer_blocklist, is an IP address or a host name, the host name // optionally with one leading dot for suffix matching. Anything else, such as -// a URL, a port or a "*." wildcard, can never match a host, so it is refused. +// a URL, a port or a "*." wildcard, can never match a host name that resolves, +// so it is refused. // So is "." alone: the allowlist matcher would match it against any host // written with a trailing dot, which in allowlist_hosts disables URL signing. func validateHostPattern(key, host string) error {