diff --git a/lib/datadog/appsec/contrib/rack/request_middleware.rb b/lib/datadog/appsec/contrib/rack/request_middleware.rb
index 2c1f70379a5..7b9866c0d45 100644
--- a/lib/datadog/appsec/contrib/rack/request_middleware.rb
+++ b/lib/datadog/appsec/contrib/rack/request_middleware.rb
@@ -23,7 +23,7 @@ def initialize(app, opt = {})
             @oneshot_tags_sent = false
           end
 
-          # rubocop:disable Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity,Metrics/MethodLength
+          # rubocop:disable Metrics/AbcSize,Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity,Metrics/MethodLength
           def call(env)
             return @app.call(env) unless Datadog::AppSec.enabled?
 
@@ -33,6 +33,11 @@ def call(env)
             ready = false
             context = nil
 
+            # For a given request, keep using the first Rack stack context for
+            # nested apps. Don't set `context` local variable so that on popping
+            # out of this nested stack we don't finalize the parent's context
+            return @app.call(env) if env['datadog.waf.context']
+
             Datadog::AppSec.reconfigure_lock do
               processor = Datadog::AppSec.processor
 
@@ -88,7 +93,7 @@ def call(env)
               processor.deactivate_context
             end
           end
-          # rubocop:enable Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity,Metrics/MethodLength
+          # rubocop:enable Metrics/AbcSize,Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity,Metrics/MethodLength
 
           private
 
diff --git a/spec/datadog/appsec/contrib/rails/integration_test_spec.rb b/spec/datadog/appsec/contrib/rails/integration_test_spec.rb
index fe3a575a595..7a26f0b64f5 100644
--- a/spec/datadog/appsec/contrib/rails/integration_test_spec.rb
+++ b/spec/datadog/appsec/contrib/rails/integration_test_spec.rb
@@ -32,6 +32,7 @@
   let(:appsec_ip_denylist) { nil }
   let(:appsec_user_id_denylist) { nil }
   let(:appsec_ruleset) { :recommended }
+  let(:nested_app) { false }
 
   let(:crs_942_100) do
     {
@@ -92,7 +93,7 @@
       c.appsec.user_id_denylist = appsec_user_id_denylist
       c.appsec.ruleset = appsec_ruleset
 
-      # TODO: test with c.appsec.instrument :rack
+      c.appsec.instrument :rack if nested_app
     end
   end
 
@@ -461,6 +462,81 @@ def set_user
           end
         end
       end
+
+      describe 'Nested apps' do
+        let(:nested_app) { true }
+        let(:middlewares) do
+          [
+            Datadog::Tracing::Contrib::Rack::TraceMiddleware,
+            Datadog::AppSec::Contrib::Rack::RequestMiddleware
+          ]
+        end
+
+        let(:rack_app) do
+          app_middlewares = middlewares
+
+          Rack::Builder.new do
+            app_middlewares.each { |m| use m }
+            map '/' do
+              run(proc { |_env| [200, { 'Content-Type' => 'text/html' }, ['OK']] })
+            end
+          end.to_app
+        end
+
+        let(:routes) do
+          {
+            [:mount, rack_app] => '/api',
+          }
+        end
+
+        context 'GET request' do
+          subject(:response) { get url, params, env }
+
+          let(:url) { '/api' }
+          let(:params) { {} }
+          let(:headers) { {} }
+          let(:env) { { 'REMOTE_ADDR' => remote_addr }.merge!(headers) }
+
+          context 'with a non-event-triggering request' do
+            it { is_expected.to be_ok }
+
+            it_behaves_like 'a GET 200 span'
+            it_behaves_like 'a trace with AppSec tags'
+            it_behaves_like 'a trace without AppSec events'
+          end
+
+          context 'with an event-triggering request in headers' do
+            let(:headers) { { 'HTTP_USER_AGENT' => 'Nessus SOAP' } }
+
+            it { is_expected.to be_ok }
+            it { expect(triggers).to be_a Array }
+
+            it_behaves_like 'a GET 200 span'
+            it_behaves_like 'a trace with AppSec tags'
+            it_behaves_like 'a trace with AppSec events'
+          end
+
+          context 'with an event-triggering request in query string' do
+            let(:params) { { q: '1 OR 1;' } }
+
+            it { is_expected.to be_ok }
+
+            it_behaves_like 'a GET 200 span'
+            it_behaves_like 'a trace with AppSec tags'
+            it_behaves_like 'a trace with AppSec events'
+
+            context 'and a blocking rule' do
+              let(:appsec_ruleset) { crs_942_100 }
+
+              it { is_expected.to be_forbidden }
+
+              it_behaves_like 'a GET 403 span'
+              it_behaves_like 'a trace with AppSec tags'
+              it_behaves_like 'a trace with AppSec events'
+            end
+          end
+        end
+      end
     end
   end
 end