Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions lib/sentry/scrubber.ex
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ defmodule Sentry.Scrubber do

alias Sentry.Callback

@scrubbed_value "*********"
@scrubbed_value "[Filtered]"
@scrubber_pdict_key {__MODULE__, :scrubber}
@scrubber_names [:body_scrubber, :header_scrubber, :cookie_scrubber, :url_scrubber]

Expand Down Expand Up @@ -245,7 +245,9 @@ defmodule Sentry.Scrubber do
]

@doc """
The placeholder string used to replace scrubbed values.
Returns the placeholder that replaces redacted values: `"[Filtered]"`.

*Changed in 14.0.0:* returns `"[Filtered]"` instead of `"*********"`.
"""
@doc since: "13.1.0"
@spec scrubbed_value() :: String.t()
Expand Down
8 changes: 4 additions & 4 deletions test/plug_capture_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ defmodule Sentry.PlugCaptureTest do

assert [exception] = event.exception
assert exception.type == "Phoenix.ActionClauseError"
assert exception.value =~ ~s(params: %{"password" => "*********"})
assert exception.value =~ ~s(params: %{"password" => "[Filtered]"})

refute exception.value =~ ~s(query_string: "password=secret"),
"query_string leaked into exception value: #{exception.value}"
Expand All @@ -313,7 +313,7 @@ defmodule Sentry.PlugCaptureTest do
assert [exception] = event.exception

# Sensitive data is still scrubbed (proves the scrubber ran)...
assert exception.value =~ ~s(params: %{"password" => "*********"})
assert exception.value =~ ~s(params: %{"password" => "[Filtered]"})

# ...routing metadata in the allow-list survives...
assert exception.value =~ "phoenix_controller"
Expand Down Expand Up @@ -411,7 +411,7 @@ defmodule Sentry.PlugCaptureTest do

assert [exception] = event.exception
assert exception.type == "Phoenix.ActionClauseError"
assert exception.value =~ ~s(params: %{"password" => "*********"})
assert exception.value =~ ~s(params: %{"password" => "[Filtered]"})
refute exception.value =~ ~s(query_string: "password=secret")

assert log =~ ~r/domain=(\w+\.)*sentry \[error\]\s+:scrubber callback failed/
Expand All @@ -430,7 +430,7 @@ defmodule Sentry.PlugCaptureTest do

assert [exception] = event.exception
assert exception.type == "Phoenix.ActionClauseError"
assert exception.value =~ ~s(%{"password" => "*********"})
assert exception.value =~ ~s(%{"password" => "[Filtered]"})
refute exception.value =~ "secret"
end

Expand Down
2 changes: 1 addition & 1 deletion test/sentry/integrations/oban/error_reporter_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ defmodule Sentry.Integrations.Oban.ErrorReporterTest do

event = assert_sentry_report(:event, fingerprint: [@worker_as_string, "{{ default }}"])

assert event.extra[:args]["password"] == "*********"
assert event.extra[:args]["password"] == "[Filtered]"
assert event.extra[:args]["id"] == "123"
assert event.extra[:args]["entity"] == "user"
end
Expand Down
8 changes: 4 additions & 4 deletions test/sentry/live_view_hook_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,8 @@ defmodule Sentry.LiveViewHookTest do
event: "login",
params: %{
"email" => "user@example.com",
"password" => "*********",
"card" => "*********"
"password" => "[Filtered]",
"card" => "[Filtered]"
}
}
end
Expand All @@ -250,7 +250,7 @@ defmodule Sentry.LiveViewHookTest do
breadcrumbs = get_sentry_context(view).breadcrumbs
mount_breadcrumb = Enum.find(breadcrumbs, &(&1.category == "web.live_view.mount"))

assert mount_breadcrumb.data == %{"password" => "*********", "visible" => "ok"}
assert mount_breadcrumb.data == %{"password" => "[Filtered]", "visible" => "ok"}
end

test "scrubs sensitive query params from URI in handle_params breadcrumb", %{conn: conn} do
Expand Down Expand Up @@ -329,7 +329,7 @@ defmodule Sentry.LiveViewHookTest do
assert event_breadcrumb.data == %{
event: "submit",
params: %{
"api_key" => "*********",
"api_key" => "[Filtered]",
"other" => "not-redacted"
}
}
Expand Down
26 changes: 13 additions & 13 deletions test/sentry/plug_context_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ defmodule Sentry.PlugContextTest do
def query_url_scrubber(conn) do
conn
|> Plug.Conn.request_url()
|> String.replace(~r/api_key=[^&]+/, "api_key=*********")
|> String.replace(~r/api_key=[^&]+/, "api_key=[Filtered]")
end

def remote_address_reader(conn) do
Expand Down Expand Up @@ -250,10 +250,10 @@ defmodule Sentry.PlugContextTest do
conn = conn(:get, "/test?api_key=sk_live_secret123&hello=world")
call(conn, url_scrubber: {__MODULE__, :query_url_scrubber})

assert "http://www.example.com/test?api_key=*********&hello=world" ==
assert "http://www.example.com/test?api_key=[Filtered]&hello=world" ==
Sentry.Context.get_all().request.url

assert "api_key=*********&hello=world" == Sentry.Context.get_all().request.query_string
assert "api_key=[Filtered]&hello=world" == Sentry.Context.get_all().request.query_string
end

test "url_scrubber: nil falls back to the request URL unchanged" do
Expand Down Expand Up @@ -301,16 +301,16 @@ defmodule Sentry.PlugContextTest do
assert request_context.cookies == %{}

assert request_context.data == %{
"another_cc" => "*********",
"cc" => "*********",
"another_cc" => "[Filtered]",
"cc" => "[Filtered]",
"count" => 334,
"credit_card" => "*********",
"passwd" => "*********",
"password" => "*********",
"secret" => "*********",
"user" => %{"password" => "*********"},
"credit_card" => "[Filtered]",
"passwd" => "[Filtered]",
"password" => "[Filtered]",
"secret" => "[Filtered]",
"user" => %{"password" => "[Filtered]"},
"payments" => [
%{"yet_another_cc" => "*********"}
%{"yet_another_cc" => "[Filtered]"}
]
}
end
Expand All @@ -322,7 +322,7 @@ defmodule Sentry.PlugContextTest do
call(conn, [])

assert Sentry.Context.get_all().request.data == %{
"password" => "*********",
"password" => "[Filtered]",
"image" => %{
content_type: nil,
filename: "my_image.png",
Expand All @@ -344,7 +344,7 @@ defmodule Sentry.PlugContextTest do

assert scrubbed.cookies == %{}
assert scrubbed.req_headers == [{"x-keep", "yes"}]
assert scrubbed.params == %{"password" => "*********", "ok" => "fine"}
assert scrubbed.params == %{"password" => "[Filtered]", "ok" => "fine"}
end

test "honors a custom body_scrubber when scrub/1 is called downstream",
Expand Down
12 changes: 6 additions & 6 deletions test/sentry/scrubber/stacktrace_scrubber_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ defmodule Sentry.Scrubber.StacktraceScrubberTest do
assert [scrubbed_conn, scrubbed_map, "plain", 42] = StacktraceScrubber.scrub_args(args)

# the conn is scrubbed as a conn
assert scrubbed_conn.params == %{"password" => "*********", "name" => "Alice"}
assert scrubbed_conn.params == %{"password" => "[Filtered]", "name" => "Alice"}
assert scrubbed_conn.req_headers == [{"x-keep", "yes"}]

# a plain map is key-scrubbed
assert scrubbed_map == %{"password" => "*********", "ok" => "fine"}
assert scrubbed_map == %{"password" => "[Filtered]", "ok" => "fine"}
end

test "scrubs a non-Plug.Conn struct's fields but keeps its type" do
Expand All @@ -38,8 +38,8 @@ defmodule Sentry.Scrubber.StacktraceScrubberTest do
assert is_struct(scrubbed, Card)
# ...while its fields are scrubbed by value (credit-card heuristic) and by
# name (the atom key :secret matches the sensitive-key list).
assert scrubbed.card_number == "*********"
assert scrubbed.secret == "*********"
assert scrubbed.card_number == "[Filtered]"
assert scrubbed.secret == "[Filtered]"
assert scrubbed.name == "Alice"
end

Expand All @@ -59,7 +59,7 @@ defmodule Sentry.Scrubber.StacktraceScrubberTest do
# the standalone params arg is scrubbed independently (default keys only): the
# "password" value is redacted, but "ssn" (not a default key) is left intact —
# proving the conn's scrubbed params are NOT mirrored onto it.
assert scrubbed_params == %{"password" => "*********", "ssn" => "123-45-6789"}
assert scrubbed_params == %{"password" => "[Filtered]", "ssn" => "123-45-6789"}
end
end

Expand All @@ -71,7 +71,7 @@ defmodule Sentry.Scrubber.StacktraceScrubberTest do
assert %FunctionClauseError{args: [scrubbed_conn, "x"]} =
StacktraceScrubber.scrub(exception)

assert scrubbed_conn.params == %{"password" => "*********", "name" => "Alice"}
assert scrubbed_conn.params == %{"password" => "[Filtered]", "name" => "Alice"}
end

test "applies a custom args_scrubber callback to the exception's args" do
Expand Down
44 changes: 22 additions & 22 deletions test/sentry/scrubber_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ defmodule Sentry.ScrubberTest do
_ = Scrubber.new(body_scrubber: fn _conn -> %{"marker" => "unregistered"} end)

conn = %Plug.Conn{params: %{"password" => "hunter2"}}
assert Scrubber.scrub(conn).params == %{"password" => "*********"}
assert Scrubber.scrub(conn).params == %{"password" => "[Filtered]"}
end
end

describe "scrub/2" do
test "redacts sensitive top-level keys" do
assert Scrubber.scrub(%{"password" => "x", "ok" => 1}) ==
%{"password" => "*********", "ok" => 1}
%{"password" => "[Filtered]", "ok" => 1}
end

test "leaves a key that is not valid UTF-8 alone rather than failing on it" do
Expand All @@ -52,7 +52,7 @@ defmodule Sentry.ScrubberTest do
end

test "redacts a key that contains a sensitive term in any casing" do
assert Scrubber.scrub(%{"X-Auth-Token" => "x"}) == %{"X-Auth-Token" => "*********"}
assert Scrubber.scrub(%{"X-Auth-Token" => "x"}) == %{"X-Auth-Token" => "[Filtered]"}
end

test "leaves a key containing no sensitive term untouched" do
Expand All @@ -61,22 +61,22 @@ defmodule Sentry.ScrubberTest do

test "redacts sensitive keys given as atoms (e.g. struct fields)" do
assert Scrubber.scrub(%{password: "x", ok: 1}) ==
%{password: "*********", ok: 1}
%{password: "[Filtered]", ok: 1}
end

test "recurses into nested maps" do
assert Scrubber.scrub(%{"outer" => %{"secret" => "shh"}}) ==
%{"outer" => %{"secret" => "*********"}}
%{"outer" => %{"secret" => "[Filtered]"}}
end

test "recurses into lists of maps" do
assert Scrubber.scrub(%{"items" => [%{"passwd" => "1"}, %{"ok" => 2}]}) ==
%{"items" => [%{"passwd" => "*********"}, %{"ok" => 2}]}
%{"items" => [%{"passwd" => "[Filtered]"}, %{"ok" => 2}]}
end

test "redacts credit-card-shaped values" do
assert Scrubber.scrub(%{"cc" => "4111111111111111"}) ==
%{"cc" => "*********"}
%{"cc" => "[Filtered]"}
end

test "scrubs structs by converting them to maps" do
Expand All @@ -88,7 +88,7 @@ defmodule Sentry.ScrubberTest do

test "respects custom :keys option" do
assert Scrubber.scrub(%{"api_key" => "x", "password" => "y"}, keys: ["api_key"]) ==
%{"api_key" => "*********", "password" => "y"}
%{"api_key" => "[Filtered]", "password" => "y"}
end

test "leaves non-sensitive values untouched" do
Expand Down Expand Up @@ -222,8 +222,8 @@ defmodule Sentry.ScrubberTest do
test "leaves the placeholder readable rather than percent-encoding it" do
scrubbed = Scrubber.scrub_query_string("password=hunter2")

assert scrubbed == "password=#{Scrubber.scrubbed_value()}"
refute scrubbed =~ "%2A"
assert scrubbed == "password=[Filtered]"
refute scrubbed =~ "%5B"
end

test "passes through params it keeps byte for byte" do
Expand Down Expand Up @@ -323,8 +323,8 @@ defmodule Sentry.ScrubberTest do

test "scrubs params with default sensitive keys", %{scrubbed: scrubbed} do
assert scrubbed.params == %{
"user" => %{"email" => "alice@example.com", "password" => "*********"},
"_csrf_token" => "*********"
"user" => %{"email" => "alice@example.com", "password" => "[Filtered]"},
"_csrf_token" => "[Filtered]"
}
end

Expand All @@ -338,7 +338,7 @@ defmodule Sentry.ScrubberTest do
test "scrubs query_params with default sensitive keys", %{scrubbed: scrubbed} do
assert scrubbed.query_params == %{
"redirect_to" => "/dashboard",
"secret" => "*********"
"secret" => "[Filtered]"
}
end

Expand Down Expand Up @@ -367,7 +367,7 @@ defmodule Sentry.ScrubberTest do
end

test "scrubs path_params with default sensitive keys", %{scrubbed: scrubbed} do
assert scrubbed.path_params == %{"id" => "42", "secret" => "*********"}
assert scrubbed.path_params == %{"id" => "42", "secret" => "[Filtered]"}
end

test "scrubs sensitive params out of query_string", %{scrubbed: scrubbed} do
Expand Down Expand Up @@ -447,7 +447,7 @@ defmodule Sentry.ScrubberTest do
scrubbed = Scrubber.scrub(conn, assigns: :clear, private: :clear)

# default fields still scrubbed
assert scrubbed.params == %{"password" => "*********"}
assert scrubbed.params == %{"password" => "[Filtered]"}
# overridden fields cleared wholesale
assert scrubbed.assigns == %{}
assert scrubbed.private == %{}
Expand All @@ -462,10 +462,10 @@ defmodule Sentry.ScrubberTest do
scrubbed = Scrubber.scrub(conn, body_params: :params, query_params: :params)

assert scrubbed.body_params == %{
"user" => %{"password" => "*********", "email" => "a@b.c"}
"user" => %{"password" => "[Filtered]", "email" => "a@b.c"}
}

assert scrubbed.query_params == %{"secret" => "*********", "page" => "1"}
assert scrubbed.query_params == %{"secret" => "[Filtered]", "page" => "1"}
end

test ":params override leaves %Plug.Conn.Unfetched{} untouched" do
Expand Down Expand Up @@ -635,7 +635,7 @@ defmodule Sentry.ScrubberTest do

scrubbed = Scrubber.scrub(conn)
assert scrubbed.cookies == %{}
assert scrubbed.params == %{"password" => "*********", "name" => "Alice"}
assert scrubbed.params == %{"password" => "[Filtered]", "name" => "Alice"}
assert is_list(scrubbed.req_headers)
assert {"x-keep", "yes"} in scrubbed.req_headers
refute Enum.any?(scrubbed.req_headers, fn {k, _v} -> k == "authorization" end)
Expand All @@ -656,7 +656,7 @@ defmodule Sentry.ScrubberTest do
# The current process never registered a scrubber, so scrub/1 lazily
# initializes defaults instead of inheriting the task's marker scrubber.
scrubbed = Scrubber.scrub(conn)
assert scrubbed.params == %{"password" => "*********"}
assert scrubbed.params == %{"password" => "[Filtered]"}
end

test "validates the opts shape on put" do
Expand All @@ -679,7 +679,7 @@ defmodule Sentry.ScrubberTest do
assert is_struct(scrubbed, Plug.Conn)
assert scrubbed.cookies == %{}
assert scrubbed.req_headers == [{"x-keep", "yes"}]
assert scrubbed.params == %{"password" => "*********"}
assert scrubbed.params == %{"password" => "[Filtered]"}
end

test "honors a registered conn scrubber for the Plug.Conn dispatch path" do
Expand All @@ -696,7 +696,7 @@ defmodule Sentry.ScrubberTest do

test "scrubs a plain map with default sensitive keys" do
assert Scrubber.scrub(%{"password" => "x", "ok" => 1}) ==
%{"password" => "*********", "ok" => 1}
%{"password" => "[Filtered]", "ok" => 1}
end

test "returns integers, atoms, binaries, and lists unchanged" do
Expand All @@ -723,7 +723,7 @@ defmodule Sentry.ScrubberTest do
refute is_struct(scrubbed)
# Once the struct is a map, value-based heuristics reach its fields: the
# credit-card-shaped value is redacted, non-sensitive data is preserved.
assert scrubbed.card_number == "*********"
assert scrubbed.card_number == "[Filtered]"
assert scrubbed.name == "Alice"
end
end
Expand Down
7 changes: 4 additions & 3 deletions test_integrations/tracing/tests/param_scrubbing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ if (REAL_DSN && !process.env.SENTRY_DSN) {
const RUN_ID = process.env.SENTRY_E2E_RUN_ID ?? `run-${Date.now()}`;
const VALUES = probeValues(RUN_ID);
const EXPECTED_QUERY = scrubbedQuery(VALUES, RUN_ID);
const ENCODED_PLACEHOLDER = encodeURIComponent(PLACEHOLDER);

async function openDemoPage(page: Page): Promise<void> {
await page.goto(`${PHOENIX_URL}${demoPagePath(VALUES, RUN_ID)}`);
Expand Down Expand Up @@ -181,10 +182,10 @@ test.describe("parameter scrubbing", () => {

expect(conn.queryString).toBe(EXPECTED_QUERY);
expect(conn.queryString, "the placeholder was form-encoded").not.toContain(
"%2A"
ENCODED_PLACEHOLDER
);
expect(event.request.query_string).not.toContain("%2A");
expect(event.request.url).not.toContain("%2A");
expect(event.request.query_string).not.toContain(ENCODED_PLACEHOLDER);
expect(event.request.url).not.toContain(ENCODED_PLACEHOLDER);

expect(conn.queryString).toContain("note=a%20b~c");
expect(conn.queryString).toContain("&flag&");
Expand Down
Loading
Loading