You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: refuse an avatar_url that points at the runner's own network (#20)
What comes back from an avatar fetch is base64-embedded into a file the run then
commits, so where it came from is a policy question rather than a transport
detail. `safe_redirect` already treated it as one — but it only ever saw the
second request onward. The first URL, the one written in the config, was fetched
with nothing looking at it: a loopback address, a private range, a cloud metadata
endpoint. Whatever answered was labelled `image/png` regardless of its real
content type and embedded. On a workflow that builds a mural from a pull request,
that address is the contributor's to choose.
Verified end to end before the fix: a config naming a local server had that
server's JSON response base64-embedded in the committed SVG, exit code 0. After,
the fetch is refused, nothing is written, and the run fails with the reason.
Two holes in the existing redirect rule went with it:
- A host was judged by how it was spelled rather than by where it points, so a
name aimed at an internal address walked past — as did the decimal form of one,
since `2130706433` is not a literal to `Socket::IPAddress` but is 127.0.0.1 to
the resolver. Names are resolved now, and every address they answer with is
judged. Answers are cached per host, so a wall of a few thousand faces asks the
resolver once per host rather than once per face.
- `URI#host` keeps the brackets around an IPv6 literal, so `[::1]` was read as an
unrecognised hostname and allowed. `#hostname` is the one that strips them.
This raises the bar rather than sealing it, and the comment says so: the
connection resolves the name again for itself, so a name that answers differently
each time can still get through. Closing that means connecting to an
already-vetted address and carrying the `Host` header by hand, which is a
different piece of work.
The reason a face is missing now reaches the log. Every failure — a 404, a
timeout, a refused address — was reported as "avatar could not be fetched", which
does not tell anyone whether to fix a typo, a permission or a URL. The fetcher
had already worked it out; `Embedder` was dropping it on the floor. This is what
makes the new rule diagnosable rather than mysterious for anyone whose avatar
host really is internal.
Note for review: that last case is a behaviour change for a self-hosted runner
pulling avatars from an internal host — GitHub Enterprise Server, say. It fails
now, with a message naming the address. If that is worth supporting, an opt-out
input is the shape, and I would rather it be your call than my guess.
0 commit comments