Kroki diagram proxy fails with private IP Kroki URLs due to inconsistent SSRF filtering between Rails and Workhorse
## Summary
When Kroki is configured with a private/internal IP address (e.g. `http://10.88.0.3:8000`), the Kroki URL saves successfully in Admin settings but diagrams fail to render with a broken image when the diagram proxy is enabled. The Workhorse log shows:
```
SendURL: Do request: Get "http://10.88.0.3:8000/nwdiag/svg/...": dial tcp 10.88.0.3:8000: IP 10.88.0.3 is not allowed: private IPs are not allowed
```
## Root Cause
There are two inconsistent SSRF filtering layers:
**1. Rails `UrlBlocker` (URL validation on save)**
In `app/models/application_setting.rb`, `parsed_kroki_url` calls `UrlBlocker.validate!` without passing `allow_local_network`:
```ruby
def parsed_kroki_url
@parsed_kroki_url ||= Gitlab::HTTP_V2::UrlBlocker.validate!(
kroki_url, schemes: %w[http https],
enforce_sanitization: true,
deny_all_requests_except_allowed: Gitlab::CurrentSettings.deny_all_requests_except_allowed?,
outbound_local_requests_allowlist: Gitlab::CurrentSettings.outbound_local_requests_whitelist)[0]
```
The default for `allow_local_network` in `UrlBlocker` is `true`, so private IP addresses like `10.88.0.3` are **allowed through** at save time.
**2. Workhorse SSRF filter (render time)**
In `app/controllers/banzai/diagram_proxy_controller.rb`, `send_url` is called with `ssrf_filter: true` but no `allowed_endpoints`:
```ruby
headers.store(*Gitlab::Workhorse.send_url(url, allow_redirects: true, ssrf_filter: true))
```
Workhorse has a hardcoded list of private network CIDRs in `workhorse/internal/transport/transport.go` (including `10.0.0.0/8`) and blocks the request at render time. The outbound allowlist configured in Admin settings is never forwarded to Workhorse.
## Impact
- Admins running Kroki on an internal/private network IP cannot use the diagram proxy, even if the IP is in the outbound allowlist
- This includes admins who deploy Kroki on k8s and use `svc.cluster.local` hosts which are private IPs. As a result this feature does not work in k8s.
- The failure is silent from the admin's perspective — the URL saves fine but diagrams silently fail with a broken image icon
- The only working configuration is `localhost`/`127.0.0.1` because Workhorse has a separate `AllowLocalhost` flag that Rails does pass
## Steps to Reproduce
1. Run Kroki locally: `podman run -d --name kroki -p 8000:8000 docker.io/yuzutech/kroki`
2. Get the container IP: `podman inspect kroki --format '{{.NetworkSettings.IPAddress}}'` (e.g. `10.88.0.3`)
3. In **Admin > Settings > General > Kroki**, enable Kroki and set URL to `http://10.88.0.3:8000`
4. Enable the Kroki diagram proxy
5. Add a diagram to an issue or wiki page:
````
```graphviz
digraph { a -> b }
```
````
6. Observe broken image icon
7. Check Workhorse log: `sudo gitlab-ctl tail gitlab-workhorse/current` — see `private IPs are not allowed` error
## Expected Behaviour
If a private IP is in the outbound local requests allowlist, the diagram proxy should be able to reach it. The `DiagramProxyController` should forward the allowlist to Workhorse as `allowed_endpoints`, similar to how the dependency proxy does it:
```ruby
# ee/lib/api/concerns/dependency_proxy/packages_helpers.rb
Gitlab::Workhorse.send_url(
url,
ssrf_filter: true,
allowed_endpoints: allowed_endpoints
)
```
## Proposed Fix
In `app/controllers/banzai/diagram_proxy_controller.rb`, pass the outbound allowlist to `send_url`:
```ruby
headers.store(*Gitlab::Workhorse.send_url(
url,
allow_redirects: true,
ssrf_filter: true,
allowed_endpoints: Gitlab::CurrentSettings.outbound_local_requests_whitelist
))
```
## Environment
- GitLab 18.10.4
- Omnibus installation
- Kroki running in Podman container on private network IP
issue