Skip to content

Commit c3ace17

Browse files
Merge pull request #254 from getsentry/scrub-cookies
Scrub Cookies
2 parents 86b903f + 710e890 commit c3ace17

File tree

5 files changed

+120
-123
lines changed

5 files changed

+120
-123
lines changed

.travis.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,28 @@ elixir:
33
- 1.3.4
44
- 1.4
55
- 1.5
6-
- 1.6.0
6+
- 1.6
77
otp_release:
88
- 18.3
99
- 19.3
10-
- 20.0
10+
- 20.2
1111
env:
1212
- MIX_FORMAT=true
1313
- MIX_FORMAT=false
1414
matrix:
1515
exclude:
16-
- elixir: 1.6.0
16+
- elixir: 1.6
1717
env: MIX_FORMAT=false
1818
- elixir: 1.3.4
1919
env: MIX_FORMAT=true
2020
- elixir: 1.4
2121
env: MIX_FORMAT=true
2222
- elixir: 1.5
2323
env: MIX_FORMAT=true
24-
- elixir: 1.6.0
24+
- elixir: 1.6
2525
otp_release: 18.3
2626
- elixir: 1.3.4
27-
otp_release: 20.0
27+
otp_release: 20.2
2828
notifications:
2929
email:
3030

lib/sentry/plug.ex

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
if Code.ensure_loaded?(Plug) do
22
defmodule Sentry.Plug do
33
@default_scrubbed_param_keys ["password", "passwd", "secret"]
4-
@default_scrubbed_header_keys ["authorization", "authentication"]
4+
@default_scrubbed_header_keys ["authorization", "authentication", "cookie"]
55
@credit_card_regex ~r/^(?:\d[ -]*?){13,16}$/
66
@scrubbed_value "*********"
77

@@ -68,9 +68,15 @@ if Code.ensure_loaded?(Plug) do
6868
6969
use Sentry.Plug, header_scrubber: {MyModule, :scrub_headers}
7070
71-
To configure scrubbing body and header data, we can set both configuration keys:
71+
### Cookie Scrubber
7272
73-
use Sentry.Plug, header_scrubber: &scrub_headers/1, body_scrubber: &scrub_params/1
73+
By default Sentry will scrub all cookies before sending events.
74+
It can be configured similarly to the headers scrubber, but is configured with the `:cookie_scrubber` key.
75+
76+
To configure scrubbing, we can set all configuration keys:
77+
78+
use Sentry.Plug, header_scrubber: &scrub_headers/1,
79+
body_scrubber: &scrub_params/1, cookie_scrubber: &scrub_cookies/1
7480
7581
### Including Request Identifiers
7682
@@ -89,6 +95,7 @@ if Code.ensure_loaded?(Plug) do
8995
body_scrubber = Keyword.get(env, :body_scrubber, {__MODULE__, :default_body_scrubber})
9096

9197
header_scrubber = Keyword.get(env, :header_scrubber, {__MODULE__, :default_header_scrubber})
98+
cookie_scrubber = Keyword.get(env, :cookie_scrubber, {__MODULE__, :default_cookie_scrubber})
9299

93100
request_id_header = Keyword.get(env, :request_id_header)
94101

@@ -109,6 +116,7 @@ if Code.ensure_loaded?(Plug) do
109116
opts = [
110117
body_scrubber: unquote(body_scrubber),
111118
header_scrubber: unquote(header_scrubber),
119+
cookie_scrubber: unquote(cookie_scrubber),
112120
request_id_header: unquote(request_id_header)
113121
]
114122

@@ -130,6 +138,7 @@ if Code.ensure_loaded?(Plug) do
130138
def build_request_interface_data(%Plug.Conn{} = conn, opts) do
131139
body_scrubber = Keyword.get(opts, :body_scrubber)
132140
header_scrubber = Keyword.get(opts, :header_scrubber)
141+
cookie_scrubber = Keyword.get(opts, :cookie_scrubber)
133142
request_id = Keyword.get(opts, :request_id_header) || @default_plug_request_id_header
134143

135144
conn =
@@ -141,7 +150,7 @@ if Code.ensure_loaded?(Plug) do
141150
method: conn.method,
142151
data: handle_data(conn, body_scrubber),
143152
query_string: conn.query_string,
144-
cookies: conn.req_cookies,
153+
cookies: handle_data(conn, cookie_scrubber),
145154
headers: handle_data(conn, header_scrubber),
146155
env: %{
147156
"REMOTE_ADDR" => remote_address(conn.remote_ip),
@@ -171,6 +180,11 @@ if Code.ensure_loaded?(Plug) do
171180
fun.(conn)
172181
end
173182

183+
@spec default_cookie_scrubber(Plug.Conn.t()) :: map()
184+
def default_cookie_scrubber(_conn) do
185+
%{}
186+
end
187+
174188
@spec default_header_scrubber(Plug.Conn.t()) :: map()
175189
def default_header_scrubber(conn) do
176190
Enum.into(conn.req_headers, %{})

test/plug_test.exs

Lines changed: 84 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -17,137 +17,62 @@ defmodule Sentry.PlugTest do
1717
)
1818
end
1919

20-
test "exception makes call to Sentry API" do
20+
test "default data scrubbing" do
21+
Code.compile_string("""
22+
defmodule DefaultConfigApp do
23+
use Plug.Router
24+
use Plug.ErrorHandler
25+
use Sentry.Plug
26+
plug :match
27+
plug :dispatch
28+
forward("/", to: Sentry.ExampleApp)
29+
end
30+
""")
31+
2132
bypass = Bypass.open()
2233

2334
Bypass.expect(bypass, fn conn ->
2435
{:ok, body, conn} = Plug.Conn.read_body(conn)
25-
assert body =~ "RuntimeError"
26-
assert body =~ "ExampleApp"
27-
assert conn.request_path == "/api/1/store/"
28-
assert conn.method == "POST"
36+
json = Poison.decode!(body)
37+
assert json["request"]["cookies"] == %{}
38+
assert json["request"]["headers"] == %{"content-type" => "application/json"}
2939
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
3040
end)
3141

3242
modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1")
3343

3444
assert_raise(RuntimeError, "Error", fn ->
35-
conn(:get, "/error_route")
36-
|> Sentry.ExampleApp.call([])
37-
end)
38-
end
39-
40-
test "builds request data" do
41-
conn =
42-
conn(:get, "/error_route?key=value")
43-
|> put_req_cookie("cookie_key", "cookie_value")
44-
|> put_req_header("accept-language", "en-US")
45-
46-
request_data =
47-
Sentry.Plug.build_request_interface_data(
48-
conn,
49-
header_scrubber: &Sentry.Plug.default_header_scrubber/1
50-
)
51-
52-
assert request_data[:url] =~ ~r/\/error_route$/
53-
assert request_data[:method] == "GET"
54-
assert request_data[:data] == %{}
55-
56-
assert request_data[:headers] == %{
57-
"cookie" => "cookie_key=cookie_value",
58-
"accept-language" => "en-US"
59-
}
60-
61-
assert request_data[:cookies] == %{"cookie_key" => "cookie_value"}
62-
assert request_data[:query_string] == "key=value"
63-
assert is_binary(request_data[:env]["REMOTE_ADDR"])
64-
assert is_integer(request_data[:env]["REMOTE_PORT"])
65-
assert is_binary(request_data[:env]["SERVER_NAME"])
66-
assert is_integer(request_data[:env]["SERVER_PORT"])
67-
end
68-
69-
test "handles data scrubbing" do
70-
conn =
71-
conn(:post, "/error_route", %{
72-
"hello" => "world",
73-
"password" => "test",
74-
"cc" => "4242424242424242"
75-
})
76-
|> put_req_cookie("cookie_key", "cookie_value")
77-
|> put_req_header("accept-language", "en-US")
78-
|> put_req_header("authorization", "ignorme")
79-
80-
scrubber = fn conn ->
81-
conn.params
82-
|> Enum.filter(fn {key, val} ->
83-
# Matches Credit Cards
84-
!(key in ~w(password passwd secret credit_card) ||
85-
Regex.match?(~r/^(?:\d[ -]*?){13,16}$/, val))
86-
end)
87-
|> Enum.into(%{})
88-
end
89-
90-
options = [body_scrubber: scrubber, header_scrubber: &Sentry.Plug.default_header_scrubber/1]
91-
request_data = Sentry.Plug.build_request_interface_data(conn, options)
92-
assert request_data[:method] == "POST"
93-
assert request_data[:data] == %{"hello" => "world"}
94-
95-
assert request_data[:headers] == %{
96-
"cookie" => "cookie_key=cookie_value",
97-
"accept-language" => "en-US",
98-
"content-type" => "multipart/mixed; boundary=plug_conn_test"
99-
}
100-
101-
assert request_data[:cookies] == %{"cookie_key" => "cookie_value"}
102-
end
103-
104-
test "gets request_id" do
105-
conn =
106-
conn(:get, "/error_route")
107-
|> Plug.Conn.put_resp_header("x-request-id", "my_request_id")
108-
109-
request_data =
110-
Sentry.Plug.build_request_interface_data(conn, request_id_header: "x-request-id")
111-
112-
assert request_data[:env]["REQUEST_ID"] == "my_request_id"
113-
end
114-
115-
test "default data scrubbing" do
116-
conn =
11745
conn(:post, "/error_route", %{
11846
"secret" => "world",
11947
"password" => "test",
12048
"passwd" => "4242424242424242",
12149
"credit_card" => "4197 7215 7810 8280",
12250
"count" => 334,
123-
"is_admin" => false,
12451
"cc" => "4197-7215-7810-8280",
12552
"another_cc" => "4197721578108280",
12653
"user" => %{"password" => "mypassword"}
12754
})
128-
129-
request_data =
130-
Sentry.Plug.build_request_interface_data(
131-
conn,
132-
body_scrubber: &Sentry.Plug.default_body_scrubber/1
133-
)
134-
135-
assert request_data[:method] == "POST"
136-
137-
assert request_data[:data] == %{
138-
"secret" => "*********",
139-
"password" => "*********",
140-
"count" => 334,
141-
"is_admin" => false,
142-
"passwd" => "*********",
143-
"credit_card" => "*********",
144-
"cc" => "*********",
145-
"another_cc" => "*********",
146-
"user" => %{"password" => "*********"}
147-
}
55+
|> update_req_cookie("secret", "secretvalue")
56+
|> update_req_cookie("regular", "value")
57+
|> put_req_header("authorization", "secrets")
58+
|> put_req_header("authentication", "secrets")
59+
|> put_req_header("content-type", "application/json")
60+
|> DefaultConfigApp.call([])
61+
end)
14862
end
14963

15064
test "handles data scrubbing with file upload" do
65+
Code.compile_string("""
66+
defmodule ScrubbingWithFileApp do
67+
use Plug.Router
68+
use Plug.ErrorHandler
69+
use Sentry.Plug
70+
plug :match
71+
plug :dispatch
72+
forward("/", to: Sentry.ExampleApp)
73+
end
74+
""")
75+
15176
bypass = Bypass.open()
15277

15378
Bypass.expect(bypass, fn conn ->
@@ -166,7 +91,57 @@ defmodule Sentry.PlugTest do
16691
|> put_req_cookie("cookie_key", "cookie_value")
16792
|> put_req_header("accept-language", "en-US")
16893
|> put_req_header("authorization", "ignorme")
169-
|> Sentry.ExampleApp.call([])
94+
|> ScrubbingWithFileApp.call([])
95+
end)
96+
end
97+
98+
test "custom cookie scrubbing" do
99+
Code.compile_string("""
100+
defmodule CustomCookieScrubberApp do
101+
use Plug.Router
102+
use Plug.ErrorHandler
103+
use Sentry.Plug, cookie_scrubber: fn(conn) ->
104+
Map.take(conn.req_cookies, ["regular"])
105+
end
106+
plug :match
107+
plug :dispatch
108+
forward("/", to: Sentry.ExampleApp)
109+
end
110+
""")
111+
112+
bypass = Bypass.open()
113+
114+
Bypass.expect(bypass, fn conn ->
115+
{:ok, body, conn} = Plug.Conn.read_body(conn)
116+
json = Poison.decode!(body)
117+
assert json["request"]["cookies"] == %{"regular" => "value"}
118+
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
119+
end)
120+
121+
modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1")
122+
123+
assert_raise(RuntimeError, "Error", fn ->
124+
conn(:get, "/error_route")
125+
|> update_req_cookie("secret", "secretvalue")
126+
|> update_req_cookie("regular", "value")
127+
|> CustomCookieScrubberApp.call([])
170128
end)
171129
end
130+
131+
defp update_req_cookie(conn, name, value) do
132+
req_headers =
133+
conn.req_headers
134+
|> Enum.into(%{})
135+
|> Map.update("cookie", "#{name}=#{value}", fn val ->
136+
Plug.Conn.Cookies.decode(val)
137+
|> Map.put(name, value)
138+
|> Enum.map(fn {cookie_name, cookie_value} ->
139+
"#{cookie_name}=#{cookie_value}"
140+
end)
141+
|> Enum.join("; ")
142+
end)
143+
|> Enum.into([])
144+
145+
%Plug.Conn{conn | req_headers: req_headers}
146+
end
172147
end

test/sources_test.exs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ defmodule Sentry.SourcesTest do
44
import Sentry.TestEnvironmentHelper
55

66
test "exception makes call to Sentry API" do
7+
Code.compile_string("""
8+
defmodule SourcesApp do
9+
use Plug.Router
10+
use Plug.ErrorHandler
11+
use Sentry.Plug
12+
13+
plug :match
14+
plug :dispatch
15+
forward("/", to: Sentry.ExampleApp)
16+
end
17+
""")
18+
719
correct_context = %{
820
"context_line" => " raise RuntimeError, \"Error\"",
921
"post_context" => [" end", "", " post \"/error_route\" do"],
@@ -35,7 +47,7 @@ defmodule Sentry.SourcesTest do
3547

3648
assert_raise(RuntimeError, "Error", fn ->
3749
conn(:get, "/error_route")
38-
|> Sentry.ExampleApp.call([])
50+
|> SourcesApp.call([])
3951
end)
4052
end
4153
end

test/support/test_plug.ex

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
defmodule Sentry.ExampleApp do
22
use Plug.Router
3-
use Plug.ErrorHandler
4-
use Sentry.Plug, request_id_header: "x-request-id"
5-
63

74
plug Plug.Parsers, parsers: [:multipart]
8-
plug Plug.RequestId
95
plug :match
106
plug :dispatch
117

0 commit comments

Comments
 (0)