Skip to content

Commit f8094b5

Browse files
authored
Merge pull request #5049 from DataDog/appsec-add-security-response-id-to-blocking-response
Add security response id to AppSec blocking response
2 parents f33bab7 + 682849d commit f8094b5

File tree

7 files changed

+86
-17
lines changed

7 files changed

+86
-17
lines changed

lib/datadog/appsec/assets.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def waf_scanners
2121
end
2222

2323
def blocked(format: :html)
24-
(@blocked ||= {})[format] ||= read("blocked.#{format}")
24+
(@blocked ||= {})[format] ||= read("blocked.#{format}").freeze
2525
end
2626

2727
def path

lib/datadog/appsec/assets/blocked.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,20 @@
8282
footer p {
8383
font-size: 16px
8484
}
85+
86+
.security-response-id {
87+
font-size:14px;
88+
color:#999;
89+
margin-top:20px;
90+
font-family:monospace
91+
}
8592
</style>
8693
</head>
8794

8895
<body>
8996
<main>
9097
<p>Sorry, you cannot access this page. Please contact the customer service team.</p>
98+
<p class="security-response-id">Security Response ID: [security_response_id]</p>
9199
</main>
92100
<footer>
93101
<p>Security provided by <a
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"errors": [{"title": "You've been blocked", "detail": "Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}]}
1+
{"errors":[{"title":"You've been blocked","detail":"Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}],"security_response_id":"[security_response_id]"}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
You've been blocked
1+
You've been blocked.
22

33
Sorry, you cannot access this page. Please contact the customer service team.
44

5+
Security Response ID: [security_response_id]
6+
57
Security provided by Datadog.

lib/datadog/appsec/response.rb

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ module Datadog
77
module AppSec
88
# AppSec response
99
class Response
10+
SECURITY_RESPONSE_ID_PLACEHOLDER = '[security_response_id]'
11+
1012
attr_reader :status, :headers, :body
1113

1214
def initialize(status:, headers: {}, body: [])
@@ -37,7 +39,12 @@ def block_response(interrupt_params, http_accept_header)
3739
Response.new(
3840
status: interrupt_params['status_code']&.to_i || 403,
3941
headers: {'Content-Type' => content_type},
40-
body: [content(content_type)],
42+
body: [
43+
content(
44+
security_response_id: interrupt_params['security_response_id'],
45+
content_type: content_type
46+
)
47+
],
4148
)
4249
end
4350

@@ -82,16 +89,18 @@ def content_type(http_accept_header)
8289
DEFAULT_CONTENT_TYPE
8390
end
8491

85-
def content(content_type)
92+
def content(security_response_id:, content_type:)
8693
content_format = CONTENT_TYPE_TO_FORMAT[content_type]
8794

8895
using_default = Datadog.configuration.appsec.block.templates.using_default?(content_format)
8996

90-
if using_default
97+
template = if using_default
9198
Datadog::AppSec::Assets.blocked(format: content_format)
9299
else
93100
Datadog.configuration.appsec.block.templates.send(content_format)
94101
end
102+
103+
template.gsub(SECURITY_RESPONSE_ID_PLACEHOLDER, security_response_id.to_s)
95104
end
96105
end
97106
end

sig/datadog/appsec/response.rbs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
module Datadog
22
module AppSec
33
class Response
4+
SECURITY_RESPONSE_ID_PLACEHOLDER: ::String
5+
46
attr_reader status: ::Integer
57
attr_reader headers: ::Hash[::String, ::String]
68
attr_reader body: ::Array[::String]
@@ -21,7 +23,7 @@ module Datadog
2123
def self.redirect_response: (::Hash[::String, ::String] interrupt_params) -> Response
2224

2325
def self.content_type: (::String) -> ::String
24-
def self.content: (::String) -> ::String
26+
def self.content: (security_response_id: ::String, content_type: ::String) -> ::String
2527
end
2628
end
2729
end

spec/datadog/appsec/response_spec.rb

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
let(:interrupt_params) do
1010
{
1111
'type' => type,
12-
'status_code' => status_code
12+
'status_code' => status_code,
13+
'security_response_id' => security_response_id
1314
}
1415
end
1516

1617
let(:type) { 'html' }
1718
let(:status_code) { '100' }
19+
let(:security_response_id) { '73bb7b99-52f6-43ea-998c-6cbc6b80f520' }
1820

1921
context 'status_code' do
2022
subject(:status) { described_class.from_interrupt_params(interrupt_params, http_accept_header).status }
@@ -31,13 +33,25 @@
3133
context 'body' do
3234
subject(:body) { described_class.from_interrupt_params(interrupt_params, http_accept_header).body }
3335

34-
it { is_expected.to eq [Datadog::AppSec::Assets.blocked(format: :html)] }
36+
it 'includes security response ID in the response body' do
37+
expect(body).to match_array([include(security_response_id)])
38+
end
3539

3640
context 'type is auto it uses the HTTP_ACCEPT to decide the result' do
3741
let(:type) { 'auto' }
3842
let(:http_accept_header) { 'application/json' }
3943

40-
it { is_expected.to eq [Datadog::AppSec::Assets.blocked(format: :json)] }
44+
it 'includes security response ID in the response body' do
45+
expect(body).to match_array([include(security_response_id)])
46+
end
47+
48+
it 'returns the response body with correct content type' do
49+
expect(body).to eq([
50+
Datadog::AppSec::Assets
51+
.blocked(format: :json)
52+
.gsub(Datadog::AppSec::Response::SECURITY_RESPONSE_ID_PLACEHOLDER, security_response_id)
53+
])
54+
end
4155
end
4256
end
4357

@@ -60,11 +74,14 @@
6074
let(:interrupt_params) { {} }
6175
subject(:response) { described_class.from_interrupt_params(interrupt_params, http_accept_header) }
6276

63-
it 'uses default response' do
77+
it 'uses default response replaces placeholders in the template' do
6478
expect(response.status).to eq 403
65-
expect(response.body).to eq [Datadog::AppSec::Assets.blocked(format: :html)]
6679
expect(response.headers['Content-Type']).to eq 'text/html'
6780
end
81+
82+
it 'does not render security response ID placeholders' do
83+
expect(response.body).not_to match_array([include(Datadog::AppSec::Response::SECURITY_RESPONSE_ID_PLACEHOLDER)])
84+
end
6885
end
6986
end
7087

@@ -116,7 +133,14 @@
116133
end
117134

118135
describe '.body' do
119-
subject(:body) { described_class.from_interrupt_params({}, http_accept_header).body }
136+
let(:security_response_id) { SecureRandom.uuid }
137+
138+
subject(:body) do
139+
described_class.from_interrupt_params(
140+
{'security_response_id' => security_response_id},
141+
http_accept_header
142+
).body
143+
end
120144

121145
shared_examples_for 'with custom response body' do |type|
122146
before do
@@ -135,29 +159,53 @@
135159
context 'with unsupported Accept headers' do
136160
let(:http_accept_header) { 'application/xml' }
137161

138-
it { is_expected.to eq [Datadog::AppSec::Assets.blocked(format: :json)] }
162+
it 'returns default json template with security response ID' do
163+
expect(body).to eq([
164+
Datadog::AppSec::Assets
165+
.blocked(format: :json)
166+
.gsub(Datadog::AppSec::Response::SECURITY_RESPONSE_ID_PLACEHOLDER, security_response_id)
167+
])
168+
end
139169
end
140170

141171
context('with Accept: text/html') do
142172
let(:http_accept_header) { 'text/html' }
143173

144-
it { is_expected.to eq [Datadog::AppSec::Assets.blocked(format: :html)] }
174+
it 'returns default html template with security response ID' do
175+
expect(body).to eq([
176+
Datadog::AppSec::Assets
177+
.blocked(format: :html)
178+
.gsub(Datadog::AppSec::Response::SECURITY_RESPONSE_ID_PLACEHOLDER, security_response_id)
179+
])
180+
end
145181

146182
it_behaves_like 'with custom response body', :html
147183
end
148184

149185
context('with Accept: application/json') do
150186
let(:http_accept_header) { 'application/json' }
151187

152-
it { is_expected.to eq [Datadog::AppSec::Assets.blocked(format: :json)] }
188+
it 'returns default json template with security response ID' do
189+
expect(body).to eq([
190+
Datadog::AppSec::Assets
191+
.blocked(format: :json)
192+
.gsub(Datadog::AppSec::Response::SECURITY_RESPONSE_ID_PLACEHOLDER, security_response_id)
193+
])
194+
end
153195

154196
it_behaves_like 'with custom response body', :json
155197
end
156198

157199
context('with Accept: text/plain') do
158200
let(:http_accept_header) { 'text/plain' }
159201

160-
it { is_expected.to eq [Datadog::AppSec::Assets.blocked(format: :text)] }
202+
it 'returns default text template with security response ID' do
203+
expect(body).to eq([
204+
Datadog::AppSec::Assets
205+
.blocked(format: :text)
206+
.gsub(Datadog::AppSec::Response::SECURITY_RESPONSE_ID_PLACEHOLDER, security_response_id)
207+
])
208+
end
161209

162210
it_behaves_like 'with custom response body', :text
163211
end

0 commit comments

Comments
 (0)