Repository navigation
Description
Problem Statement
With send_default_pii (data_collection.user_info in 7.x), Event#rack_env= sets user[:ip_address] from Utils::RealIp. It puts Client-IP and X-Real-IP first, then X-Forwarded-For from the right, then REMOTE_ADDR, and takes the first address that isn't a trusted proxy.
Some platforms pass those headers through from the client. Heroku's router, for one, appends the address it saw to X-Forwarded-For and passes Client-IP and X-Real-IP on as the client sent them. A client that sends Client-IP: 198.51.100.2 gets that address on its events, and the address the router saw is never used. trusted_proxies can't help: it only removes the listed proxies' addresses from those candidates, and the Client-IP value is whatever the client wrote, not a proxy, so it stays first.
real_ip.rb says spoofing "only makes data inaccurate, not insecure", and accuracy is the problem here: on such a platform the IP on an event is whatever the client chose. Only the app knows its topology, and so which header to read: the last X-Forwarded-For entry on Heroku, Fly-Client-IP on Fly.io, CF-Connecting-IP behind Cloudflare.
The only way we found that keeps the SDK's own rules is to override the private Event#calculate_real_ip_from_rack(env):
module ClientIP
private
def calculate_real_ip_from_rack(env)
env["HTTP_X_FORWARDED_FOR"]&.split(",")&.last&.strip # Heroku
end
end
Sentry::Event.prepend(ClientIP)The SDK still decides when an event gets an address, and keeps one set with Sentry.set_user (#2350). But the method is private: a rename quietly brings back the header-based address, and a new argument makes every event from a web request fail to build, so capture_event drops it. The public alternatives fall short. A global event processor has to repeat the SDK's rules for each version, and in 5.x it also receives LogEvents, which have no request. Sentry.set_user(ip_address: ...) is replaced by any later set_user call in the app.
Solution Brainstorm
A supported way for the app to supply the address, called where calculate_real_ip_from_rack is today, so the SDK's checks stay as they are. For example, a callable:
Sentry.init do |config|
config.send_default_pii = true
# Called with the Rack env when the SDK adds the user's IP address
config.real_ip = ->(env) { env["HTTP_X_FORWARDED_FOR"]&.split(",")&.last&.strip }
endThat would also cover #2592: an app whose clients are on a private network could return their address without changing how trusted_proxies and LOCAL_ADDRESSES work for everyone else. A narrower option that picks the headers RealIp reads would fix the precedence, but not #2592.
We checked sentry-ruby 5.25, 5.28, 6.7 and 7.2, which all work out the address this way.
Activity
linear-code commentedon Nov 4, 2026
RUBY-242
sl0thentr0py commentedon Nov 4, 2026
thx @dentarg, yea we can certainly add a callback for this