Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b437853
Add session replay capturing
tustanivsky Jun 18, 2026
205b288
Handle replay envelope upload internally without public interface
tustanivsky Jun 29, 2026
f9a6b08
Fix replay dir path
tustanivsky Jun 29, 2026
0e6246a
Fix json property names
tustanivsky Jun 29, 2026
da1371b
Limit replay envelope to native daemon
tustanivsky Jun 29, 2026
8895c83
Clean up
tustanivsky Jun 29, 2026
cbab847
Fix lint
tustanivsky Jun 29, 2026
8e27cb0
Fix tests
tustanivsky Jun 29, 2026
f461222
Inline helper functions
tustanivsky Jun 29, 2026
cb3fc58
Clean up
tustanivsky Jun 29, 2026
772dcbb
Fix lint
tustanivsky Jun 29, 2026
148c0a1
Add replay files cleanup upon successfull envelope creation
tustanivsky Jun 30, 2026
be68d1b
Classify `replay_video` under the replay data/rate-limit category
tustanivsky Jun 30, 2026
998e92d
Remove redundant videoFilename check
tustanivsky Jul 1, 2026
0106e1f
Fix comment
tustanivsky Jul 1, 2026
52b905b
Fix issue with uploading orphan replays if crash wasn't captured
tustanivsky Jul 1, 2026
85a3cec
Clean up
tustanivsky Jul 1, 2026
50526b0
Guard replay_recording assembly against string-builder failure
tustanivsky Jul 1, 2026
acecbb8
Add helper allowing to check if there's staged replay
tustanivsky Jul 2, 2026
3299782
Reuse crash envelope path var and check if replay is staged during da…
tustanivsky Jul 2, 2026
d4dce27
Check replay dir content
tustanivsky Jul 2, 2026
706d491
Check if replay dir hold mp4 clip
tustanivsky Jul 2, 2026
b1ab19c
Add integration tests
tustanivsky Jul 2, 2026
6cabda1
Guard buffer add
tustanivsky Jul 2, 2026
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
24 changes: 24 additions & 0 deletions src/backends/native/sentry_crash_daemon.c
Original file line number Diff line number Diff line change
Expand Up @@ -3571,6 +3571,30 @@ sentry__process_crash(const sentry_options_t *options, sentry_crash_ipc_t *ipc)
#endif

cleanup:
// Send the staged session-replay envelope same-session, enriched from the
// crash event (`<run>/__sentry-event`) so it shares the crash's
// tags/contexts/trace.
if (options && options->transport) {
sentry_value_t crash_event = sentry_value_new_null();
if (run_folder) {
sentry_path_t *sentry_event_path
= sentry__path_join_str(run_folder, "__sentry-event");
if (sentry_event_path) {
size_t ev_len = 0;
char *ev_json
= sentry__path_read_to_buffer(sentry_event_path, &ev_len);
if (ev_json) {
crash_event = sentry__value_from_json(ev_json, ev_len);
sentry_free(ev_json);
}
sentry__path_free(sentry_event_path);
}
Comment thread
jpnurmi marked this conversation as resolved.
Outdated
}
sentry__session_replay_flush_pending(
options, options->transport, crash_event);
sentry_value_decref(crash_event);
}
Comment thread
tustanivsky marked this conversation as resolved.

// Send all other envelopes from run folder (logs, etc.) before cleanup
if (run_folder && options && options->transport && options->run) {
SENTRY_DEBUG("Checking for additional envelopes in run folder");
Expand Down
1 change: 1 addition & 0 deletions src/sentry_client_report.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ typedef enum {
SENTRY_DATA_CATEGORY_LOG_ITEM,
SENTRY_DATA_CATEGORY_FEEDBACK,
SENTRY_DATA_CATEGORY_TRACE_METRIC,
SENTRY_DATA_CATEGORY_REPLAY,
SENTRY_DATA_CATEGORY_MAX
} sentry_data_category_t;

Expand Down
6 changes: 6 additions & 0 deletions src/sentry_envelope.c
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ envelope_item_get_ratelimiter_category(const sentry_envelope_item_t *item)
return SENTRY_RL_CATEGORY_SESSION;
} else if (sentry__string_eq(ty, "transaction")) {
return SENTRY_RL_CATEGORY_TRANSACTION;
} else if (sentry__string_eq(ty, "replay_video")) {
return SENTRY_RL_CATEGORY_REPLAY;
} else if (sentry__string_eq(ty, "client_report")) {
// internal telemetry, bypass rate limiting
return -1;
Expand All @@ -160,6 +162,8 @@ item_type_to_data_category(const char *ty)
return SENTRY_DATA_CATEGORY_FEEDBACK;
} else if (sentry__string_eq(ty, "trace_metric")) {
return SENTRY_DATA_CATEGORY_TRACE_METRIC;
} else if (sentry__string_eq(ty, "replay_video")) {
return SENTRY_DATA_CATEGORY_REPLAY;
}
return SENTRY_DATA_CATEGORY_ERROR;
}
Expand Down Expand Up @@ -1347,6 +1351,8 @@ data_category_to_string(sentry_data_category_t category)
return "feedback";
case SENTRY_DATA_CATEGORY_TRACE_METRIC:
return "trace_metric";
case SENTRY_DATA_CATEGORY_REPLAY:
return "replay";
case SENTRY_DATA_CATEGORY_MAX:
default:
return "unknown";
Expand Down
5 changes: 4 additions & 1 deletion src/sentry_ratelimiter.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include "sentry_slice.h"
#include "sentry_utils.h"

#define MAX_RATE_LIMITS 4
#define MAX_RATE_LIMITS 5
#define MAX_RETRY_AFTER (24 * 60 * 60) // 24h

struct sentry_rate_limiter_s {
Expand All @@ -28,6 +28,7 @@ sentry__rate_limiter_new(void)
rl->disabled_until[SENTRY_RL_CATEGORY_ERROR] = 0;
rl->disabled_until[SENTRY_RL_CATEGORY_SESSION] = 0;
rl->disabled_until[SENTRY_RL_CATEGORY_TRANSACTION] = 0;
rl->disabled_until[SENTRY_RL_CATEGORY_REPLAY] = 0;
}
return rl;
}
Expand Down Expand Up @@ -64,6 +65,8 @@ sentry__rate_limiter_update_from_header(
} else if (sentry__slice_eqs(category, "transaction")) {
rl->disabled_until[SENTRY_RL_CATEGORY_TRANSACTION]
= retry_after;
} else if (sentry__slice_eqs(category, "replay")) {
rl->disabled_until[SENTRY_RL_CATEGORY_REPLAY] = retry_after;
}

categories = sentry__slice_advance(categories, category.len);
Expand Down
1 change: 1 addition & 0 deletions src/sentry_ratelimiter.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#define SENTRY_RL_CATEGORY_ERROR 1
#define SENTRY_RL_CATEGORY_SESSION 2
#define SENTRY_RL_CATEGORY_TRANSACTION 3
#define SENTRY_RL_CATEGORY_REPLAY 4

typedef struct sentry_rate_limiter_s sentry_rate_limiter_t;

Expand Down
16 changes: 16 additions & 0 deletions src/sentry_session_replay.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,20 @@
*/
sentry_path_t *sentry__session_replay_get_path(const sentry_options_t *options);

/**
* Build and send the session-replay envelope(s) the embedder staged in
* `<database>/replays/` (a `replay-<id>.json` sidecar next to its mp4). Native-
* daemon-only: called out-of-process by the crash daemon, so it runs only on a
* crash and delivers same-session. Each consumed sidecar and its mp4 are
* removed once the envelope has been captured (malformed sidecars are removed
* too), so the flush is idempotent. The SDK owns this cleanup -- the embedder
* doesn't have to clear the `replays/` folder itself.
*
* `scope_source` is the crash event (`<run>/__sentry-event`); its scope fields
* and trace id are copied onto the replay, and its timestamp ends the replay
* window. Null skips enrichment.
*/

Check warning on line 39 in src/sentry_session_replay.h

View check run for this annotation

@sentry/warden / warden: security-review

[VZ7-3P5] Unsanitized `videoFilename` from sidecar JSON enables path traversal file read (additional location)

In `sentry__session_replay_flush_pending`, the `videoFilename` value read from the embedder-written JSON sidecar is passed directly to `sentry__path_join_str(dir, video_filename)` without validation; on Unix an absolute path is returned verbatim, so a sidecar with `"videoFilename": "/etc/shadow"` makes the crash daemon read an arbitrary file and exfiltrate it as the `replay_video` blob to Sentry. Validate that the resolved path stays within the `replays/` directory before reading.
void sentry__session_replay_flush_pending(const sentry_options_t *options,
sentry_transport_t *transport, sentry_value_t scope_source);

#endif
Loading
Loading