AP_SOURCE_ALLOWLIST: which buckets and hosts a remote source may name.
One comma-separated list answers for both remote source types, because the
question is the same one — is this namespace ours? — and the answer differs
only in how a namespace is spelled. AudioProxy.Source.S3 asks about a
bucket, AudioProxy.Source.Https about a host, and neither owns the matcher.
Local sources are not gated here: AP_LOCAL_ROOT is the whole allowlist for
disk (AudioProxy.Source.Local).
The default differs by type, and that is the design
An unset (empty) allowlist accepts S3 sources and rejects HTTPS ones. An S3 bucket the proxy has no credentials for is unreadable whatever this list says, so the credentials are already a gate; an HTTPS URL has no such backstop, and an ungated one is a server-side request forgery primitive pointed at whatever the container can reach. Deny-by-default is the only safe posture for a fetch, so HTTPS sources need the operator to name a host before any of them work.
The wildcards are asymmetric, and the asymmetry is the security property
previews-* bucket: trailing-`*` prefix glob
*.media.example host: leading-`*.` suffix glob, label-anchored
* either: everything
masters either: exactA bucket namespace belongs to the operator: nobody else can create
previews-eu in their account, so a prefix glob hands out nothing. A host
namespace belongs to anyone with a registrar, which is why hosts get the
mirror image. cdn.* is the footgun this forecloses — it reads as "our CDN"
and would mean "any host starting cdn.", cdn.evil.com included. A *
anywhere but its type's documented position matches nothing rather than
matching loosely; a pattern that cannot be honoured exactly is not honoured
approximately.
The host glob is anchored to a label boundary, so *.media.example admits
media.example and cdn.media.example, and refuses media.example.evil.com
— a suffix match on raw bytes would accept it.
Buckets match case-sensitively and hosts fold case, because that is what S3 and DNS respectively do.
An IP-literal host is matched bracketless: the pattern for
https://[::1]/… is ::1, since that is the form URI parses out and the
form AudioProxy.Source.Https normalizes to. The canonical URL shows the
brackets, so this is worth knowing — the mismatch would otherwise fail closed
and silently.
Hosts are matched as written, with no IDN or punycode conversion: an operator allowlisting an internationalized domain writes its punycode form.
Summary
Functions
Decides whether subject — an S3 bucket or an already-normalized host — is
allowlisted.
Whether one pattern admits one subject — authorize/2 without the config
read, which is what makes the grammar testable on its own.
Types
Functions
Decides whether subject — an S3 bucket or an already-normalized host — is
allowlisted.
Returns a verdict for any input, including one a caller built by hand: this is a security gate, and a gate that raises is a gate that can be turned into a 500. Anything that is not a non-empty binary is refused rather than interpreted.
Whether one pattern admits one subject — authorize/2 without the config
read, which is what makes the grammar testable on its own.