From 6e3c9ca3e16b97e970d791e00d116234e54c9652 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Thu, 23 Jul 2026 16:50:14 -0400 Subject: [PATCH 1/5] feat(asgi): Gate user info behind data_collection config --- sentry_sdk/integrations/asgi.py | 16 +++++++--- tests/integrations/asgi/test_asgi.py | 44 +++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index e1157dda77..d594c3504d 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -47,6 +47,7 @@ _get_installed_modules, capture_internal_exceptions, event_from_exception, + has_data_collection_enabled, logger, nullcontext, qualname_from_function, @@ -253,10 +254,17 @@ async def _run_app( "network.protocol.name": ty, } - if scope.get("client") and should_send_default_pii(): - sentry_scope.set_attribute( - SPANDATA.USER_IP_ADDRESS, _get_ip(scope) - ) + if scope.get("client"): + client_options = sentry_sdk.get_client().options + if has_data_collection_enabled(client_options): + if client_options["data_collection"]["user_info"]: + sentry_scope.set_attribute( + SPANDATA.USER_IP_ADDRESS, _get_ip(scope) + ) + elif should_send_default_pii(): + sentry_scope.set_attribute( + SPANDATA.USER_IP_ADDRESS, _get_ip(scope) + ) if ty in ("http", "websocket"): if ( diff --git a/tests/integrations/asgi/test_asgi.py b/tests/integrations/asgi/test_asgi.py index 321ac91d75..4df9325547 100644 --- a/tests/integrations/asgi/test_asgi.py +++ b/tests/integrations/asgi/test_asgi.py @@ -1446,11 +1446,41 @@ async def test_custom_transaction_name( @pytest.mark.asyncio -@pytest.mark.parametrize("send_default_pii", [True, False]) +@pytest.mark.parametrize( + "init_kwargs, expect_ip", + [ + pytest.param({"send_default_pii": True}, True, id="legacy_pii_true"), + pytest.param({"send_default_pii": False}, False, id="legacy_pii_false"), + pytest.param( + {"_experiments": {"data_collection": {}}}, + True, + id="dc_default_user_info", + ), + pytest.param( + {"_experiments": {"data_collection": {"user_info": True}}}, + True, + id="dc_user_info_true", + ), + pytest.param( + {"_experiments": {"data_collection": {"user_info": False}}}, + False, + id="dc_user_info_false", + ), + pytest.param( + { + "send_default_pii": True, + "_experiments": {"data_collection": {"user_info": False}}, + }, + False, + id="dc_wins_over_pii", + ), + ], +) async def test_user_ip_address_on_all_spans( sentry_init, capture_items, - send_default_pii, + init_kwargs, + expect_ip, ): async def app(scope, receive, send): if scope["type"] == "lifespan": @@ -1474,11 +1504,9 @@ async def app(scope, receive, send): ) await send({"type": "http.response.body", "body": b"Hello, world!"}) - sentry_init( - send_default_pii=send_default_pii, - traces_sample_rate=1.0, - trace_lifecycle="stream", - ) + kwargs = {k: v for k, v in init_kwargs.items() if k != "_experiments"} + experiments = {"trace_lifecycle": "stream", **init_kwargs.get("_experiments", {})} + sentry_init(traces_sample_rate=1.0, _experiments=experiments, **kwargs) sentry_app = SentryAsgiMiddleware(app) async def wrapped_app(scope, receive, send): @@ -1493,7 +1521,7 @@ async def wrapped_app(scope, receive, send): child_span, server_span = [item.payload for item in items] - if send_default_pii: + if expect_ip: assert server_span["attributes"]["user.ip_address"] == "127.0.0.1" assert child_span["attributes"]["user.ip_address"] == "127.0.0.1" else: From 64e29e9172266ca199040aaf14a0d75a83f977a8 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Thu, 23 Jul 2026 16:50:14 -0400 Subject: [PATCH 2/5] feat(quart): Gate user info behind data_collection config --- sentry_sdk/integrations/quart.py | 35 ++++++- tests/integrations/quart/test_quart.py | 139 +++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 4 deletions(-) diff --git a/sentry_sdk/integrations/quart.py b/sentry_sdk/integrations/quart.py index 8c281f5874..444ef93fa5 100644 --- a/sentry_sdk/integrations/quart.py +++ b/sentry_sdk/integrations/quart.py @@ -235,9 +235,25 @@ async def _request_websocket_started(app: "Quart", **kwargs: "Any") -> None: else parsed_url.url, ) - # TODO: Add the user properties that are seen in the branch below here once - # code is added to respect the `user_info` settings within the data collection - # configuration + if client_options["data_collection"]["user_info"]: + user_properties = {} + + if len(request_websocket.access_route) >= 1: + segment.set_attribute( + "client.address", request_websocket.access_route[0] + ) + user_properties["ip_address"] = request_websocket.access_route[ + 0 + ] + + current_user_id = _get_current_user_id_from_quart() + if current_user_id: + user_properties["id"] = current_user_id + + if user_properties: + existing_user_properties = scope._user or {} + scope.set_user({**existing_user_properties, **user_properties}) + elif should_send_default_pii(): segment.set_attribute("url.full", request_websocket.url) segment.set_attribute( @@ -284,7 +300,18 @@ def inner(event: "Event", hint: "dict[str, Any]") -> "Event": request_info["method"] = request.method request_info["headers"] = _filter_headers(dict(request.headers)) - if should_send_default_pii(): + client_options = sentry_sdk.get_client().options + if has_data_collection_enabled(client_options): + if client_options["data_collection"]["user_info"]: + if len(request.access_route) >= 1: + request_info["env"] = {"REMOTE_ADDR": request.access_route[0]} + + current_user_id = _get_current_user_id_from_quart() + if current_user_id: + user_info = event.setdefault("user", {}) + user_info["id"] = current_user_id + + elif should_send_default_pii(): if len(request.access_route) >= 1: request_info["env"] = {"REMOTE_ADDR": request.access_route[0]} diff --git a/tests/integrations/quart/test_quart.py b/tests/integrations/quart/test_quart.py index 286d6aeaff..73f8f42f57 100644 --- a/tests/integrations/quart/test_quart.py +++ b/tests/integrations/quart/test_quart.py @@ -1101,6 +1101,145 @@ async def login(): assert "user.id" not in segment.get("attributes", {}) +QUART_USER_INFO_CASES = [ + pytest.param( + {"_experiments": {"data_collection": {}}}, + True, + id="dc_default_user_info", + ), + pytest.param( + {"_experiments": {"data_collection": {"user_info": True}}}, + True, + id="dc_user_info_true", + ), + pytest.param( + {"_experiments": {"data_collection": {"user_info": False}}}, + False, + id="dc_user_info_false", + ), + pytest.param( + { + "send_default_pii": True, + "_experiments": {"data_collection": {"user_info": False}}, + }, + False, + id="dc_wins_over_pii", + ), +] + + +@pytest.mark.asyncio +@pytest.mark.parametrize("init_kwargs, expect_user_info", QUART_USER_INFO_CASES) +async def test_quart_auth_user_info_data_collection( + sentry_init, + capture_events, + init_kwargs, + expect_user_info, +): + from quart_auth import AuthUser, login_user + + sentry_init(integrations=[quart_sentry.QuartIntegration()], **init_kwargs) + app = quart_app_factory() + + @app.route("/login") + async def login(): + login_user(AuthUser("42")) + return "ok" + + events = capture_events() + + client = app.test_client() + assert (await client.get("/login")).status_code == 200 + assert not events + + assert (await client.get("/message")).status_code == 200 + + (event,) = events + if expect_user_info: + assert event["user"]["id"] == "42" + assert "REMOTE_ADDR" in event["request"]["env"] + else: + assert event.get("user", {}).get("id") is None + assert "env" not in event["request"] + + +@pytest.mark.asyncio +@pytest.mark.parametrize("init_kwargs, expect_user_info", QUART_USER_INFO_CASES) +async def test_span_streaming_quart_auth_user_id_data_collection( + sentry_init, + capture_items, + init_kwargs, + expect_user_info, +): + from quart_auth import AuthUser, login_user + + kwargs = {k: v for k, v in init_kwargs.items() if k != "_experiments"} + sentry_init( + integrations=[quart_sentry.QuartIntegration()], + traces_sample_rate=1.0, + trace_lifecycle="stream", + _experiments=init_kwargs.get("_experiments", {}), + **kwargs, + ) + items = capture_items("span") + + app = quart_app_factory() + + @app.route("/login") + async def login(): + login_user(AuthUser("42")) + return "ok" + + client = app.test_client() + assert (await client.get("/login")).status_code == 200 + assert (await client.get("/message")).status_code == 200 + + sentry_sdk.flush() + + spans = [item.payload for item in items] + assert len(spans) == 2 + + segment = spans[1] + if expect_user_info: + assert segment["attributes"]["user.id"] == "42" + else: + assert "user.id" not in segment.get("attributes", {}) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("init_kwargs, expect_user_info", QUART_USER_INFO_CASES) +async def test_span_streaming_request_attributes_data_collection( + sentry_init, capture_items, init_kwargs, expect_user_info +): + kwargs = {k: v for k, v in init_kwargs.items() if k != "_experiments"} + sentry_init( + integrations=[quart_sentry.QuartIntegration()], + traces_sample_rate=1.0, + trace_lifecycle="stream", + _experiments=init_kwargs.get("_experiments", {}), + **kwargs, + ) + items = capture_items("span") + + app = quart_app_factory() + client = app.test_client() + response = await client.get("/message") + assert response.status_code == 200 + + sentry_sdk.flush() + + spans = [item.payload for item in items] + assert len(spans) == 1 + + segment = spans[0] + if expect_user_info: + assert "client.address" in segment["attributes"] + assert "user.ip_address" in segment["attributes"] + else: + assert "client.address" not in segment["attributes"] + assert "user.ip_address" not in segment["attributes"] + + @pytest.mark.asyncio async def test_span_streaming_sensitive_header_passthrough_with_pii_and_no_data_collection( sentry_init, capture_items From f409f024ec365fe55e34464e38fe780bfaf613d4 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Thu, 23 Jul 2026 16:50:14 -0400 Subject: [PATCH 3/5] feat(starlette): Gate user info behind data_collection config --- sentry_sdk/integrations/starlette.py | 6 +- .../integrations/starlette/test_starlette.py | 97 ++++++++++--------- 2 files changed, 54 insertions(+), 49 deletions(-) diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index 21cdfeecc5..7579c70c11 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -375,7 +375,11 @@ def _add_user_to_sentry_scope(scope: "Dict[str, Any]") -> None: if "user" not in scope: return - if not should_send_default_pii(): + client_options = sentry_sdk.get_client().options + if has_data_collection_enabled(client_options): + if not client_options["data_collection"]["user_info"]: + return + elif not should_send_default_pii(): return user_info: "Dict[str, Any]" = {} diff --git a/tests/integrations/starlette/test_starlette.py b/tests/integrations/starlette/test_starlette.py index bf0d7b0993..8c89c9b399 100644 --- a/tests/integrations/starlette/test_starlette.py +++ b/tests/integrations/starlette/test_starlette.py @@ -984,33 +984,40 @@ def test_catch_exceptions( assert event["exception"]["values"][0]["mechanism"]["type"] == "starlette" -def test_user_information_error(sentry_init, capture_events): - sentry_init( - send_default_pii=True, - integrations=[StarletteIntegration()], - ) - starlette_app = starlette_app_factory( - middleware=[Middleware(AuthenticationMiddleware, backend=BasicAuthBackend())] - ) - events = capture_events() - - client = TestClient(starlette_app, raise_server_exceptions=False) - try: - client.get("/custom_error", auth=("Gabriela", "hello123")) - except Exception: - pass - - (event,) = events - user = event.get("user", None) - assert user - assert "username" in user - assert user["username"] == "Gabriela" +USER_AUTH_CASES = [ + pytest.param({"send_default_pii": True}, True, id="legacy_pii_true"), + pytest.param({"send_default_pii": False}, False, id="legacy_pii_false"), + pytest.param( + {"_experiments": {"data_collection": {}}}, + True, + id="dc_default_user_info", + ), + pytest.param( + {"_experiments": {"data_collection": {"user_info": True}}}, + True, + id="dc_user_info_true", + ), + pytest.param( + {"_experiments": {"data_collection": {"user_info": False}}}, + False, + id="dc_user_info_false", + ), + pytest.param( + { + "send_default_pii": True, + "_experiments": {"data_collection": {"user_info": False}}, + }, + False, + id="dc_wins_over_pii", + ), +] -def test_user_information_error_no_pii(sentry_init, capture_events): +@pytest.mark.parametrize("init_kwargs, expect_user", USER_AUTH_CASES) +def test_user_information_error(sentry_init, capture_events, init_kwargs, expect_user): sentry_init( - send_default_pii=False, integrations=[StarletteIntegration()], + **init_kwargs, ) starlette_app = starlette_app_factory( middleware=[Middleware(AuthenticationMiddleware, backend=BasicAuthBackend())] @@ -1024,35 +1031,23 @@ def test_user_information_error_no_pii(sentry_init, capture_events): pass (event,) = events - assert "user" not in event - - -def test_user_information_transaction(sentry_init, capture_events): - sentry_init( - traces_sample_rate=1.0, - send_default_pii=True, - integrations=[StarletteIntegration()], - ) - starlette_app = starlette_app_factory( - middleware=[Middleware(AuthenticationMiddleware, backend=BasicAuthBackend())] - ) - events = capture_events() - - client = TestClient(starlette_app, raise_server_exceptions=False) - client.get("/message", auth=("Gabriela", "hello123")) - - (_, transaction_event) = events - user = transaction_event.get("user", None) - assert user - assert "username" in user - assert user["username"] == "Gabriela" + if expect_user: + user = event.get("user", None) + assert user + assert "username" in user + assert user["username"] == "Gabriela" + else: + assert "user" not in event -def test_user_information_transaction_no_pii(sentry_init, capture_events): +@pytest.mark.parametrize("init_kwargs, expect_user", USER_AUTH_CASES) +def test_user_information_transaction( + sentry_init, capture_events, init_kwargs, expect_user +): sentry_init( traces_sample_rate=1.0, - send_default_pii=False, integrations=[StarletteIntegration()], + **init_kwargs, ) starlette_app = starlette_app_factory( middleware=[Middleware(AuthenticationMiddleware, backend=BasicAuthBackend())] @@ -1063,7 +1058,13 @@ def test_user_information_transaction_no_pii(sentry_init, capture_events): client.get("/message", auth=("Gabriela", "hello123")) (_, transaction_event) = events - assert "user" not in transaction_event + if expect_user: + user = transaction_event.get("user", None) + assert user + assert "username" in user + assert user["username"] == "Gabriela" + else: + assert "user" not in transaction_event def test_user_information_does_not_clobber_app_set_user(sentry_init, capture_events): From 11fb505b28c594d5ec0c27cb3de75b35e5e93e85 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Thu, 23 Jul 2026 16:50:14 -0400 Subject: [PATCH 4/5] feat(aws_lambda): Gate user info behind data_collection config --- sentry_sdk/integrations/aws_lambda.py | 18 +++++- .../.gitignore | 11 ++++ .../BasicOkDataCollectionUserInfoOff/index.py | 19 ++++++ .../.gitignore | 11 ++++ .../BasicOkDataCollectionUserInfoOn/index.py | 19 ++++++ .../aws_lambda/test_aws_lambda.py | 60 +++++++++++++++++++ 6 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkDataCollectionUserInfoOff/.gitignore create mode 100644 tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkDataCollectionUserInfoOff/index.py create mode 100644 tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkDataCollectionUserInfoOn/.gitignore create mode 100644 tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkDataCollectionUserInfoOn/index.py diff --git a/sentry_sdk/integrations/aws_lambda.py b/sentry_sdk/integrations/aws_lambda.py index 84047a7581..b0873df5b9 100644 --- a/sentry_sdk/integrations/aws_lambda.py +++ b/sentry_sdk/integrations/aws_lambda.py @@ -437,7 +437,23 @@ def event_processor( if "headers" in aws_event: request["headers"] = _filter_headers(aws_event["headers"]) - if should_send_default_pii(): + client_options = sentry_sdk.get_client().options + if has_data_collection_enabled(client_options): + if client_options["data_collection"]["user_info"]: + user_info = sentry_event.setdefault("user", {}) + + identity = aws_event.get("identity") + if identity is None: + identity = {} + + id = identity.get("userArn") + if id is not None: + user_info.setdefault("id", id) + + ip = identity.get("sourceIp") + if ip is not None: + user_info.setdefault("ip_address", ip) + elif should_send_default_pii(): user_info = sentry_event.setdefault("user", {}) identity = aws_event.get("identity") diff --git a/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkDataCollectionUserInfoOff/.gitignore b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkDataCollectionUserInfoOff/.gitignore new file mode 100644 index 0000000000..1c56884372 --- /dev/null +++ b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkDataCollectionUserInfoOff/.gitignore @@ -0,0 +1,11 @@ +# Need to add some ignore rules in this directory, because the unit tests will add the Sentry SDK and its dependencies +# into this directory to create a Lambda function package that contains everything needed to instrument a Lambda function using Sentry. + +# Ignore everything +* + +# But not index.py +!index.py + +# And not .gitignore itself +!.gitignore diff --git a/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkDataCollectionUserInfoOff/index.py b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkDataCollectionUserInfoOff/index.py new file mode 100644 index 0000000000..18c5450196 --- /dev/null +++ b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkDataCollectionUserInfoOff/index.py @@ -0,0 +1,19 @@ +import os + +import sentry_sdk +from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration + +sentry_sdk.init( + dsn=os.environ.get("SENTRY_DSN"), + traces_sample_rate=1.0, + integrations=[AwsLambdaIntegration()], + _experiments={ + "data_collection": { + "user_info": False, + } + }, +) + + +def handler(event, context): + return {"event": event} diff --git a/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkDataCollectionUserInfoOn/.gitignore b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkDataCollectionUserInfoOn/.gitignore new file mode 100644 index 0000000000..1c56884372 --- /dev/null +++ b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkDataCollectionUserInfoOn/.gitignore @@ -0,0 +1,11 @@ +# Need to add some ignore rules in this directory, because the unit tests will add the Sentry SDK and its dependencies +# into this directory to create a Lambda function package that contains everything needed to instrument a Lambda function using Sentry. + +# Ignore everything +* + +# But not index.py +!index.py + +# And not .gitignore itself +!.gitignore diff --git a/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkDataCollectionUserInfoOn/index.py b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkDataCollectionUserInfoOn/index.py new file mode 100644 index 0000000000..07527dad4a --- /dev/null +++ b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkDataCollectionUserInfoOn/index.py @@ -0,0 +1,19 @@ +import os + +import sentry_sdk +from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration + +sentry_sdk.init( + dsn=os.environ.get("SENTRY_DSN"), + traces_sample_rate=1.0, + integrations=[AwsLambdaIntegration()], + _experiments={ + "data_collection": { + "user_info": True, + } + }, +) + + +def handler(event, context): + return {"event": event} diff --git a/tests/integrations/aws_lambda/test_aws_lambda.py b/tests/integrations/aws_lambda/test_aws_lambda.py index 3c03e1510b..cb56561abf 100644 --- a/tests/integrations/aws_lambda/test_aws_lambda.py +++ b/tests/integrations/aws_lambda/test_aws_lambda.py @@ -464,6 +464,66 @@ def test_request_data_with_send_default_pii_true(lambda_client, test_environment "data": None, } + # Legacy send_default_pii=True attaches the user identity. + assert transaction_event["user"] == { + "id": "42", + "ip_address": "213.47.147.207", + } + + +USER_INFO_PAYLOAD = b""" + { + "resource": "/asd", + "path": "/asd", + "httpMethod": "GET", + "headers": { + "Host": "iwsz2c7uwi.execute-api.us-east-1.amazonaws.com", + "User-Agent": "custom", + "X-Forwarded-Proto": "https" + }, + "queryStringParameters": { + "bonkers": "true" + }, + "pathParameters": null, + "stageVariables": null, + "requestContext": { + "identity": { + "sourceIp": "213.47.147.207", + "userArn": "42" + } + }, + "body": null, + "isBase64Encoded": false + } +""" + + +def test_user_info_with_data_collection_user_info_on(lambda_client, test_environment): + lambda_client.invoke( + FunctionName="BasicOkDataCollectionUserInfoOn", + Payload=USER_INFO_PAYLOAD, + ) + envelopes = test_environment["server"].envelopes + + (transaction_event,) = envelopes + + assert transaction_event["user"] == { + "id": "42", + "ip_address": "213.47.147.207", + } + + +def test_user_info_with_data_collection_user_info_off(lambda_client, test_environment): + lambda_client.invoke( + FunctionName="BasicOkDataCollectionUserInfoOff", + Payload=USER_INFO_PAYLOAD, + ) + envelopes = test_environment["server"].envelopes + + (transaction_event,) = envelopes + + assert "user" not in transaction_event + def test_request_data_with_data_collection_allowlist(lambda_client, test_environment): payload = b""" From d28ee71a7be5e05b79a595dc9037c2d687118af1 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Thu, 23 Jul 2026 16:57:52 -0400 Subject: [PATCH 5/5] test(aws_lambda): Place identity at top level of test event payloads The request event processor reads aws_event["identity"] at the top level, but the payloads used by the user info tests nested it under requestContext, so no user was attached and the assertions failed in CI. --- tests/integrations/aws_lambda/test_aws_lambda.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/integrations/aws_lambda/test_aws_lambda.py b/tests/integrations/aws_lambda/test_aws_lambda.py index cb56561abf..aa4fd54032 100644 --- a/tests/integrations/aws_lambda/test_aws_lambda.py +++ b/tests/integrations/aws_lambda/test_aws_lambda.py @@ -427,11 +427,9 @@ def test_request_data_with_send_default_pii_true(lambda_client, test_environment }, "pathParameters": null, "stageVariables": null, - "requestContext": { - "identity": { - "sourceIp": "213.47.147.207", - "userArn": "42" - } + "identity": { + "sourceIp": "213.47.147.207", + "userArn": "42" }, "body": null, "isBase64Encoded": false @@ -486,11 +484,9 @@ def test_request_data_with_send_default_pii_true(lambda_client, test_environment }, "pathParameters": null, "stageVariables": null, - "requestContext": { - "identity": { - "sourceIp": "213.47.147.207", - "userArn": "42" - } + "identity": { + "sourceIp": "213.47.147.207", + "userArn": "42" }, "body": null, "isBase64Encoded": false