Skip to content

Commit bea1d00

Browse files
solnicclaude
andcommitted
ref: Extract scrubbing logic into Sentry.Scrubber module
Move the recursive map/list scrubbing logic and default sensitive parameter keys out of Sentry.PlugContext and into a new framework-agnostic Sentry.Scrubber module so it can be reused by other parts of the SDK (for example, Sentry.LiveViewHook). PlugContext now delegates to Sentry.Scrubber.scrub_map/1 from default_body_scrubber/1. No behavior change. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 386c9d6 commit bea1d00

2 files changed

Lines changed: 69 additions & 33 deletions

File tree

lib/sentry/plug_context.ex

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,7 @@ defmodule Sentry.PlugContext do
153153
end
154154
end
155155

156-
@default_scrubbed_param_keys ["password", "passwd", "secret"]
157156
@default_scrubbed_header_keys ["authorization", "authentication", "cookie"]
158-
@scrubbed_value "*********"
159157
@default_plug_request_id_header "x-request-id"
160158

161159
@doc false
@@ -264,39 +262,10 @@ defmodule Sentry.PlugContext do
264262
265263
The default scrubbed keys are:
266264
267-
#{Enum.map_join(@default_scrubbed_param_keys, "\n", &"* `#{&1}`")}
265+
#{Enum.map_join(Sentry.Scrubber.default_scrubbed_param_keys(), "\n", &"* `#{&1}`")}
268266
"""
269267
@spec default_body_scrubber(Plug.Conn.t()) :: map()
270268
def default_body_scrubber(conn) do
271-
scrub_map(conn.params, @default_scrubbed_param_keys)
269+
Sentry.Scrubber.scrub_map(conn.params)
272270
end
273-
274-
defp scrub_map(map, scrubbed_keys) do
275-
Map.new(map, fn {key, value} ->
276-
value =
277-
cond do
278-
key in scrubbed_keys -> @scrubbed_value
279-
is_binary(value) and value =~ credit_card_regex() -> @scrubbed_value
280-
is_struct(value) -> value |> Map.from_struct() |> scrub_map(scrubbed_keys)
281-
is_map(value) -> scrub_map(value, scrubbed_keys)
282-
is_list(value) -> scrub_list(value, scrubbed_keys)
283-
true -> value
284-
end
285-
286-
{key, value}
287-
end)
288-
end
289-
290-
defp scrub_list(list, scrubbed_keys) do
291-
Enum.map(list, fn value ->
292-
cond do
293-
is_struct(value) -> value |> Map.from_struct() |> scrub_map(scrubbed_keys)
294-
is_map(value) -> scrub_map(value, scrubbed_keys)
295-
is_list(value) -> scrub_list(value, scrubbed_keys)
296-
true -> value
297-
end
298-
end)
299-
end
300-
301-
defp credit_card_regex, do: ~r/^(?:\d[ -]*?){13,16}$/
302271
end

lib/sentry/scrubber.ex

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
defmodule Sentry.Scrubber do
2+
@moduledoc """
3+
Provides scrubbing of sensitive data from maps before they are sent to Sentry.
4+
5+
This module is used internally by `Sentry.PlugContext` and `Sentry.LiveViewHook`
6+
to redact sensitive values like passwords and credit card numbers before they
7+
appear in Sentry events and breadcrumbs.
8+
"""
9+
10+
@moduledoc since: "10.9.0"
11+
12+
@default_scrubbed_param_keys ["password", "passwd", "secret"]
13+
@scrubbed_value "*********"
14+
15+
@doc """
16+
Returns the list of parameter keys scrubbed by default.
17+
"""
18+
@spec default_scrubbed_param_keys() :: [String.t()]
19+
def default_scrubbed_param_keys, do: @default_scrubbed_param_keys
20+
21+
@doc """
22+
Returns the value used to replace scrubbed data.
23+
"""
24+
@spec scrubbed_value() :: String.t()
25+
def scrubbed_value, do: @scrubbed_value
26+
27+
@doc """
28+
Scrubs a map of sensitive parameter values using the default scrubbed keys.
29+
30+
Values whose key matches one of the default sensitive keys (`"password"`, `"passwd"`,
31+
`"secret"`), or whose value matches a credit card number pattern, are replaced with
32+
`"*********"`. The function recurses into nested maps and lists.
33+
"""
34+
@spec scrub_map(map()) :: map()
35+
def scrub_map(map) when is_map(map) do
36+
scrub_map(map, @default_scrubbed_param_keys)
37+
end
38+
39+
defp scrub_map(map, scrubbed_keys) do
40+
Map.new(map, fn {key, value} ->
41+
value =
42+
cond do
43+
key in scrubbed_keys -> @scrubbed_value
44+
is_binary(value) and value =~ credit_card_regex() -> @scrubbed_value
45+
is_struct(value) -> value |> Map.from_struct() |> scrub_map(scrubbed_keys)
46+
is_map(value) -> scrub_map(value, scrubbed_keys)
47+
is_list(value) -> scrub_list(value, scrubbed_keys)
48+
true -> value
49+
end
50+
51+
{key, value}
52+
end)
53+
end
54+
55+
defp scrub_list(list, scrubbed_keys) do
56+
Enum.map(list, fn value ->
57+
cond do
58+
is_struct(value) -> value |> Map.from_struct() |> scrub_map(scrubbed_keys)
59+
is_map(value) -> scrub_map(value, scrubbed_keys)
60+
is_list(value) -> scrub_list(value, scrubbed_keys)
61+
true -> value
62+
end
63+
end)
64+
end
65+
66+
defp credit_card_regex, do: ~r/^(?:\d[ -]*?){13,16}$/
67+
end

0 commit comments

Comments
 (0)