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
47 changes: 15 additions & 32 deletions sentry_sdk/integrations/ariadne.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@
from sentry_sdk.integrations import DidNotEnable, Integration, _check_minimum_version
from sentry_sdk.integrations._wsgi_common import request_body_within_bounds
from sentry_sdk.integrations.logging import ignore_logger_for_events
from sentry_sdk.scope import should_send_default_pii
from sentry_sdk.utils import (
capture_internal_exceptions,
ensure_integration_enabled,
event_from_exception,
has_data_collection_enabled,
package_version,
)

Expand Down Expand Up @@ -138,32 +136,21 @@ def inner(event: "Event", hint: "dict[str, Any]") -> "Event":
return event
client_options = sentry_sdk.get_client().options

if has_data_collection_enabled(client_options):
dc_graphql = client_options["data_collection"]["graphql"]
collect_variables = dc_graphql["variables"]
collect_document = dc_graphql[
"document"
] and request_body_within_bounds(get_client(), content_length)

if collect_document or collect_variables:
request_info = event.setdefault("request", {})
request_info["api_target"] = "graphql"
request_info["data"] = {
key: value
for key, value in data.items()
if (key != "query" or collect_document)
and (key != "variables" or collect_variables)
}
elif event.get("request", {}).get("data"):
del event["request"]["data"]

elif should_send_default_pii() and request_body_within_bounds(
dc_graphql = client_options["data_collection"]["graphql"]
collect_variables = dc_graphql["variables"]
collect_document = dc_graphql["document"] and request_body_within_bounds(
get_client(), content_length
):
)

if collect_document or collect_variables:
request_info = event.setdefault("request", {})
request_info["api_target"] = "graphql"
request_info["data"] = data

request_info["data"] = {
key: value
for key, value in data.items()
if (key != "query" or collect_document)
and (key != "variables" or collect_variables)
}
elif event.get("request", {}).get("data"):
del event["request"]["data"]

Expand All @@ -178,13 +165,9 @@ def _make_response_event_processor(response: "Dict[str, Any]") -> "EventProcesso
def inner(event: "Event", hint: "dict[str, Any]") -> "Event":
client_options = sentry_sdk.get_client().options
with capture_internal_exceptions():
if has_data_collection_enabled(client_options):
collect_response = (
"outgoing_response"
in client_options["data_collection"]["http_bodies"]
)
else:
collect_response = should_send_default_pii()
collect_response = (
"outgoing_response" in client_options["data_collection"]["http_bodies"]
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Responses collected without PII opt-in

High Severity

GraphQL error responses are now attached whenever outgoing_response is in http_bodies. If data_collection is unset, that list is always fully populated, so responses that used to require send_default_pii are sent by default and can include query result data.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d384e14. Configure here.


if collect_response and response.get("errors"):
contexts = event.setdefault("contexts", {})
Expand Down
225 changes: 37 additions & 188 deletions tests/integrations/ariadne/test_ariadne.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,153 +45,9 @@ def resolve_name(obj, *_):
return make_executable_schema(type_defs, query)


def test_capture_request_and_response_if_send_pii_is_on_async(
sentry_init, capture_events
):
sentry_init(
send_default_pii=True,
integrations=[
AriadneIntegration(),
FastApiIntegration(),
StarletteIntegration(),
],
)
events = capture_events()

schema = schema_factory()

async_app = FastAPI()
async_app.mount("/graphql/", GraphQL(schema))

query = {"query": "query ErrorQuery {error}"}
client = TestClient(async_app)
client.post("/graphql", json=query)

assert len(events) == 1

(event,) = events
assert event["exception"]["values"][0]["mechanism"]["type"] == "ariadne"
assert event["contexts"]["response"] == {
"data": {
"data": {"error": None},
"errors": [
{
"locations": [{"column": 19, "line": 1}],
"message": "resolver failed",
"path": ["error"],
}
],
}
}
assert event["request"]["api_target"] == "graphql"
assert event["request"]["data"] == query


def test_capture_request_and_response_if_send_pii_is_on_sync(
sentry_init, capture_events
):
sentry_init(
send_default_pii=True,
integrations=[AriadneIntegration(), FlaskIntegration()],
)
events = capture_events()

schema = schema_factory()

sync_app = Flask(__name__)

@sync_app.route("/graphql", methods=["POST"])
def graphql_server():
data = request.get_json()
success, result = graphql_sync(schema, data)
return jsonify(result), 200

query = {"query": "query ErrorQuery {error}"}
client = sync_app.test_client()
client.post("/graphql", json=query)

assert len(events) == 1

(event,) = events
assert event["exception"]["values"][0]["mechanism"]["type"] == "ariadne"
assert event["contexts"]["response"] == {
"data": {
"data": {"error": None},
"errors": [
{
"locations": [{"column": 19, "line": 1}],
"message": "resolver failed",
"path": ["error"],
}
],
}
}
assert event["request"]["api_target"] == "graphql"
assert event["request"]["data"] == query


def test_do_not_capture_request_and_response_if_send_pii_is_off_async(
sentry_init, capture_events
):
sentry_init(
integrations=[
AriadneIntegration(),
FastApiIntegration(),
StarletteIntegration(),
],
)
events = capture_events()

schema = schema_factory()

async_app = FastAPI()
async_app.mount("/graphql/", GraphQL(schema))

query = {"query": "query ErrorQuery {error}"}
client = TestClient(async_app)
client.post("/graphql", json=query)

assert len(events) == 1

(event,) = events
assert event["exception"]["values"][0]["mechanism"]["type"] == "ariadne"
assert "data" not in event["request"]
assert "response" not in event["contexts"]


def test_do_not_capture_request_and_response_if_send_pii_is_off_sync(
sentry_init, capture_events
):
sentry_init(
integrations=[AriadneIntegration(), FlaskIntegration()],
)
events = capture_events()

schema = schema_factory()

sync_app = Flask(__name__)

@sync_app.route("/graphql", methods=["POST"])
def graphql_server():
data = request.get_json()
success, result = graphql_sync(schema, data)
return jsonify(result), 200

query = {"query": "query ErrorQuery {error}"}
client = sync_app.test_client()
client.post("/graphql", json=query)

assert len(events) == 1

(event,) = events
assert event["exception"]["values"][0]["mechanism"]["type"] == "ariadne"
assert "data" not in event["request"]
assert "response" not in event["contexts"]


def test_capture_validation_error(sentry_init, capture_events):
sentry_init(
send_default_pii=True,
data_collection={},
integrations=[
AriadneIntegration(),
FastApiIntegration(),
Expand Down Expand Up @@ -234,6 +90,7 @@ def test_no_event_if_no_errors_async(sentry_init, capture_events):
FastApiIntegration(),
StarletteIntegration(),
],
data_collection={},
)
events = capture_events()

Expand All @@ -255,6 +112,7 @@ def test_no_event_if_no_errors_async(sentry_init, capture_events):
def test_no_event_if_no_errors_sync(sentry_init, capture_events):
sentry_init(
integrations=[AriadneIntegration(), FlaskIntegration()],
data_collection={},
)
events = capture_events()

Expand Down Expand Up @@ -310,80 +168,58 @@ def graphql_server():
return make_client


def _init_all_integrations(sentry_init, **kwargs):
sentry_init(
integrations=[
AriadneIntegration(),
FlaskIntegration(),
FastApiIntegration(),
StarletteIntegration(),
],
**kwargs,
)


@pytest.mark.parametrize(
"init_kwargs,expect_query,expect_variables",
"data_collection,expect_query,expect_variables",
[
pytest.param(
{"data_collection": {}},
{},
True,
True,
id="data_collection_defaults",
),
pytest.param(
{"data_collection": {"graphql": {"document": True, "variables": True}}},
{"graphql": {"document": True, "variables": True}},
True,
True,
id="document_on_variables_on",
),
pytest.param(
{"data_collection": {"graphql": {"document": False}}},
{"graphql": {"document": False}},
False,
True,
id="document_off_variables_on",
),
pytest.param(
{"data_collection": {"graphql": {"variables": False}}},
{"graphql": {"variables": False}},
True,
False,
id="document_on_variables_off",
),
pytest.param(
{"data_collection": {"graphql": {"document": False, "variables": False}}},
{"graphql": {"document": False, "variables": False}},
None,
None,
id="document_off_variables_off",
),
pytest.param(
{
"send_default_pii": True,
"data_collection": {"graphql": {"document": False}},
},
False,
True,
id="data_collection_takes_precedence_over_send_default_pii_on",
),
pytest.param(
{
"send_default_pii": False,
"data_collection": {"graphql": {"document": True}},
},
True,
True,
id="data_collection_takes_precedence_over_send_default_pii_off",
),
],
)
def test_request_data_collection(
sentry_init,
capture_events,
graphql_client,
init_kwargs,
data_collection,
expect_query,
expect_variables,
):
_init_all_integrations(sentry_init, **init_kwargs)
sentry_init(
integrations=[
AriadneIntegration(),
FlaskIntegration(),
FastApiIntegration(),
StarletteIntegration(),
],
data_collection=data_collection,
)
events = capture_events()

graphql_client().post("/graphql", json=ERROR_QUERY_WITH_VARIABLES)
Expand Down Expand Up @@ -411,10 +247,15 @@ def test_request_data_collection_body_out_of_bounds_still_collects_variables(
dropped but variables (which are not subject to the bounds check) are
still collected.
"""
_init_all_integrations(
sentry_init,
max_request_body_size="small",
sentry_init(
integrations=[
AriadneIntegration(),
FlaskIntegration(),
FastApiIntegration(),
StarletteIntegration(),
],
data_collection={},
max_request_body_size="small",
)
events = capture_events()

Expand Down Expand Up @@ -443,8 +284,16 @@ def test_request_data_collection_body_out_of_bounds_still_collects_variables(
def test_response_data_collection(
sentry_init, capture_events, graphql_client, http_bodies, expect_response
):
data_collection = {} if http_bodies is None else {"http_bodies": http_bodies}
_init_all_integrations(sentry_init, data_collection=data_collection)
sentry_init(
integrations=[
AriadneIntegration(),
FlaskIntegration(),
FastApiIntegration(),
StarletteIntegration(),
],
data_collection={} if http_bodies is None else {"http_bodies": http_bodies},
)

events = capture_events()

graphql_client().post("/graphql", json={"query": "query ErrorQuery {error}"})
Expand Down
Loading