From 4bd84f05be377141b6e951dcab36ccb95396180c Mon Sep 17 00:00:00 2001 From: Vishnu Narayanan Date: Tue, 5 May 2026 22:59:27 +0530 Subject: [PATCH] fix: add widget conversation throttle by ip + website_token Replaces the per-IP 6-per-12h throttle (disabled in cloud as too aggressive for shared NAT IPs) with a per-(IP, website_token) 30-per-minute burst throttle. Distinct widgets behind a shared NAT IP get separate buckets so the original NAT pain is preserved while a single source can no longer create unbounded conversations against one widget. Adds ENABLE_RACK_ATTACK_WIDGET_CONVERSATIONS as an independent kill switch so this throttle can be toggled without re-enabling the legacy widget throttles. RATE_LIMIT_WIDGET_CONVERSATIONS controls the per-minute cap. --- config/initializers/rack_attack.rb | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index 15e78af9b..905bcb6b9 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -170,15 +170,25 @@ class Rack::Attack ###-----------Widget API Throttling---------------### ###-----------------------------------------------### + ## Burst protection on widget conversation creation. Keyed on (IP, website_token) + ## so distinct widgets behind a shared NAT IP get separate buckets. Independent + ## kill switch so it can be toggled without touching the legacy ENABLE_RACK_ATTACK_WIDGET_API. + if ActiveModel::Type::Boolean.new.cast(ENV.fetch('ENABLE_RACK_ATTACK_WIDGET_CONVERSATIONS', true)) + throttle('api/v1/widget/conversations', + limit: ENV.fetch('RATE_LIMIT_WIDGET_CONVERSATIONS', '30').to_i, + period: 1.minute) do |req| + next unless req.path_without_extentions == '/api/v1/widget/conversations' && req.post? + + token = req.params['website_token'].presence || + ActionDispatch::Request.new(req.env).params['website_token'].presence + "#{req.ip}:#{token}" if token + end + end + # Rack attack on widget APIs can be disabled by setting ENABLE_RACK_ATTACK_WIDGET_API to false # For clients using the widgets in specific conditions like inside and iframe # TODO: Deprecate this feature in future after finding a better solution if ActiveModel::Type::Boolean.new.cast(ENV.fetch('ENABLE_RACK_ATTACK_WIDGET_API', true)) - ## Prevent Conversation Bombing on Widget APIs ### - throttle('api/v1/widget/conversations', limit: 6, period: 12.hours) do |req| - req.ip if req.path_without_extentions == '/api/v1/widget/conversations' && req.post? - end - ## Prevent Contact update Bombing in Widget API ### throttle('api/v1/widget/contacts', limit: 60, period: 1.hour) do |req| req.ip if req.path_without_extentions == '/api/v1/widget/contacts' && (req.patch? || req.put?)