Server-side request forgery is not really about requests. It is about position. Your server sits somewhere the person calling it does not — inside a network, with an identity, next to services that trust anything that can reach them — and a handler that fetches a URL it was handed lends that position out.
This fixture network has an inside. There is an admin service on an internal name, a cache on loopback, and a metadata endpoint that hands credentials to anything that asks from within. None of them is reachable from where you are sitting.
GET /fetch?url=https%3A%2F%2Fapi.partner.example%2FstatusA host both builds are happy to call.
GET /fetch?url=http%3A%2F%2F169.254.169.254%2Flatest%2Fmeta-data%2FThe address every cloud provider puts credentials behind. Compare what each build did with it.
The obvious defence is a deny-list: refuse localhost, refuse 127.0.0.1, refuse 169.254. It fails for the reason deny-lists always fail — it has to be right about every address that could ever reach inside, and it is competing with decimal notation, IPv6, DNS names that resolve inward, and a redirect from a host that was on nobody's list.
An allow-list has to be right about the three hosts you actually meant. That is a much smaller thing to be right about, and it is the whole of the difference.
GET /fetch?url=http%3A%2F%2Fredirector.example.com%2FA perfectly ordinary public host. Watch where the request actually ended up.