Skip to content

Commit ea06022

Browse files
committed
fix: wrap all random APIs with the safe runner
1 parent 0c24b54 commit ea06022

File tree

9 files changed

+29
-18
lines changed

9 files changed

+29
-18
lines changed

packages/core/src/client.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import { checkOrSetAlreadyCaught, uuid4 } from './utils/misc';
4545
import { parseSampleRate } from './utils/parseSampleRate';
4646
import { prepareEvent } from './utils/prepareEvent';
4747
import { makePromiseBuffer, type PromiseBuffer, SENTRY_BUFFER_FULL_ERROR } from './utils/promisebuffer';
48+
import { safeMathRandom } from './utils/safeRandomGeneratorRunner';
4849
import { reparentChildSpans, shouldIgnoreSpan } from './utils/should-ignore-span';
4950
import { showSpanDropWarning } from './utils/spanUtils';
5051
import { rejectedSyncPromise } from './utils/syncpromise';
@@ -1288,7 +1289,7 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
12881289
// 0.0 === 0% events are sent
12891290
// Sampling for transaction happens somewhere else
12901291
const parsedSampleRate = typeof sampleRate === 'undefined' ? undefined : parseSampleRate(sampleRate);
1291-
if (isError && typeof parsedSampleRate === 'number' && Math.random() > parsedSampleRate) {
1292+
if (isError && typeof parsedSampleRate === 'number' && safeMathRandom() > parsedSampleRate) {
12921293
this.recordDroppedEvent('sample_rate', 'error');
12931294
return rejectedSyncPromise(
12941295
_makeDoNotSendEventError(

packages/core/src/integrations/mcp-server/correlation.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import { getClient } from '../../currentScopes';
1010
import { SPAN_STATUS_ERROR } from '../../tracing';
1111
import type { Span } from '../../types-hoist/span';
12+
import { safeDateNow } from '../../utils/safeRandomGeneratorRunner';
1213
import { MCP_PROTOCOL_VERSION_ATTRIBUTE } from './attributes';
1314
import { filterMcpPiiFromSpanData } from './piiFiltering';
1415
import { extractPromptResultAttributes, extractToolResultAttributes } from './resultExtraction';
@@ -48,7 +49,7 @@ export function storeSpanForRequest(transport: MCPTransport, requestId: RequestI
4849
spanMap.set(requestId, {
4950
span,
5051
method,
51-
startTime: Date.now(),
52+
startTime: safeDateNow(),
5253
});
5354
}
5455

packages/core/src/scope.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { isPlainObject } from './utils/is';
2222
import { merge } from './utils/merge';
2323
import { uuid4 } from './utils/misc';
2424
import { generateTraceId } from './utils/propagationContext';
25+
import { safeMathRandom } from './utils/safeRandomGeneratorRunner';
2526
import { _getSpanForScope, _setSpanForScope } from './utils/spanOnScope';
2627
import { truncate } from './utils/string';
2728
import { dateTimestampInSeconds } from './utils/time';
@@ -168,7 +169,7 @@ export class Scope {
168169
this._sdkProcessingMetadata = {};
169170
this._propagationContext = {
170171
traceId: generateTraceId(),
171-
sampleRand: Math.random(),
172+
sampleRand: safeMathRandom(),
172173
};
173174
}
174175

@@ -550,7 +551,7 @@ export class Scope {
550551
this._session = undefined;
551552
_setSpanForScope(this, undefined);
552553
this._attachments = [];
553-
this.setPropagationContext({ traceId: generateTraceId(), sampleRand: Math.random() });
554+
this.setPropagationContext({ traceId: generateTraceId(), sampleRand: safeMathRandom() });
554555

555556
this._notifyScopeListeners();
556557
return this;

packages/core/src/tracing/trace.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { handleCallbackErrors } from '../utils/handleCallbackErrors';
1717
import { hasSpansEnabled } from '../utils/hasSpansEnabled';
1818
import { parseSampleRate } from '../utils/parseSampleRate';
1919
import { generateTraceId } from '../utils/propagationContext';
20+
import { safeMathRandom } from '../utils/safeRandomGeneratorRunner';
2021
import { _getSpanForScope, _setSpanForScope } from '../utils/spanOnScope';
2122
import { addChildSpanToSpan, getRootSpan, spanIsSampled, spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils';
2223
import { propagationContextFromHeaders, shouldContinueTrace } from '../utils/tracing';
@@ -293,7 +294,7 @@ export function startNewTrace<T>(callback: () => T): T {
293294
return withScope(scope => {
294295
scope.setPropagationContext({
295296
traceId: generateTraceId(),
296-
sampleRand: Math.random(),
297+
sampleRand: safeMathRandom(),
297298
});
298299
DEBUG_BUILD && debug.log(`Starting a new trace with id ${scope.getPropagationContext().traceId}`);
299300
return withActiveSpan(null, callback);

packages/core/src/utils/misc.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Exception } from '../types-hoist/exception';
33
import type { Mechanism } from '../types-hoist/mechanism';
44
import type { StackFrame } from '../types-hoist/stackframe';
55
import { addNonEnumerableProperty } from './object';
6+
import { runInRandomSafeContext, safeMathRandom } from './safeRandomGeneratorRunner';
67
import { snipLine } from './string';
78
import { GLOBAL_OBJ } from './worldwide';
89

@@ -24,7 +25,7 @@ function getCrypto(): CryptoInternal | undefined {
2425
let emptyUuid: string | undefined;
2526

2627
function getRandomByte(): number {
27-
return Math.random() * 16;
28+
return safeMathRandom() * 16;
2829
}
2930

3031
/**
@@ -35,7 +36,8 @@ function getRandomByte(): number {
3536
export function uuid4(crypto = getCrypto()): string {
3637
try {
3738
if (crypto?.randomUUID) {
38-
return crypto.randomUUID().replace(/-/g, '');
39+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
40+
return runInRandomSafeContext(() => crypto.randomUUID!().replace(/-/g, ''));
3941
}
4042
} catch {
4143
// some runtimes can crash invoking crypto

packages/core/src/utils/ratelimit.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { DataCategory } from '../types-hoist/datacategory';
22
import type { TransportMakeRequestResponse } from '../types-hoist/transport';
3+
import { runInRandomSafeContext, safeDateNow } from './safeRandomGeneratorRunner';
34

45
// Intentionally keeping the key broad, as we don't know for sure what rate limit headers get returned from backend
56
export type RateLimits = Record<string, number>;
@@ -12,7 +13,7 @@ export const DEFAULT_RETRY_AFTER = 60 * 1000; // 60 seconds
1213
* @param now current unix timestamp
1314
*
1415
*/
15-
export function parseRetryAfterHeader(header: string, now: number = Date.now()): number {
16+
export function parseRetryAfterHeader(header: string, now: number = runInRandomSafeContext(() => Date.now())): number {
1617
const headerDelay = parseInt(`${header}`, 10);
1718
if (!isNaN(headerDelay)) {
1819
return headerDelay * 1000;
@@ -40,7 +41,7 @@ export function disabledUntil(limits: RateLimits, dataCategory: DataCategory): n
4041
/**
4142
* Checks if a category is rate limited
4243
*/
43-
export function isRateLimited(limits: RateLimits, dataCategory: DataCategory, now: number = Date.now()): boolean {
44+
export function isRateLimited(limits: RateLimits, dataCategory: DataCategory, now: number = safeDateNow()): boolean {
4445
return disabledUntil(limits, dataCategory) > now;
4546
}
4647

@@ -52,7 +53,7 @@ export function isRateLimited(limits: RateLimits, dataCategory: DataCategory, no
5253
export function updateRateLimits(
5354
limits: RateLimits,
5455
{ statusCode, headers }: TransportMakeRequestResponse,
55-
now: number = Date.now(),
56+
now: number = safeDateNow(),
5657
): RateLimits {
5758
const updatedRateLimits: RateLimits = {
5859
...limits,

packages/core/src/utils/time.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { runInRandomSafeContext } from './safeRandomGeneratorRunner';
12
import { GLOBAL_OBJ } from './worldwide';
23

34
const ONE_SECOND_IN_MS = 1000;
@@ -21,7 +22,7 @@ interface Performance {
2122
* Returns a timestamp in seconds since the UNIX epoch using the Date API.
2223
*/
2324
export function dateTimestampInSeconds(): number {
24-
return Date.now() / ONE_SECOND_IN_MS;
25+
return runInRandomSafeContext(() => Date.now() / ONE_SECOND_IN_MS);
2526
}
2627

2728
/**
@@ -50,7 +51,7 @@ function createUnixTimestampInSecondsFunc(): () => number {
5051
// See: https://github.com/mdn/content/issues/4713
5152
// See: https://dev.to/noamr/when-a-millisecond-is-not-a-millisecond-3h6
5253
return () => {
53-
return (timeOrigin + performance.now()) / ONE_SECOND_IN_MS;
54+
return runInRandomSafeContext(() => (timeOrigin + performance.now()) / ONE_SECOND_IN_MS);
5455
};
5556
}
5657

@@ -90,8 +91,8 @@ function getBrowserTimeOrigin(): [number | undefined, string] {
9091
}
9192

9293
const threshold = 3600 * 1000;
93-
const performanceNow = performance.now();
94-
const dateNow = Date.now();
94+
const performanceNow = runInRandomSafeContext(() => performance.now());
95+
const dateNow = runInRandomSafeContext(() => Date.now());
9596

9697
// if timeOrigin isn't available set delta to threshold so it isn't used
9798
const timeOriginDelta = performance.timeOrigin

packages/core/src/utils/tracing.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { baggageHeaderToDynamicSamplingContext } from './baggage';
77
import { extractOrgIdFromClient } from './dsn';
88
import { parseSampleRate } from './parseSampleRate';
99
import { generateSpanId, generateTraceId } from './propagationContext';
10+
import { safeMathRandom } from './safeRandomGeneratorRunner';
1011

1112
// eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor -- RegExp is used for readability here
1213
export const TRACEPARENT_REGEXP = new RegExp(
@@ -65,7 +66,7 @@ export function propagationContextFromHeaders(
6566
if (!traceparentData?.traceId) {
6667
return {
6768
traceId: generateTraceId(),
68-
sampleRand: Math.random(),
69+
sampleRand: safeMathRandom(),
6970
};
7071
}
7172

@@ -133,12 +134,12 @@ function getSampleRandFromTraceparentAndDsc(
133134
if (parsedSampleRate && traceparentData?.parentSampled !== undefined) {
134135
return traceparentData.parentSampled
135136
? // Returns a sample rand with positive sampling decision [0, sampleRate)
136-
Math.random() * parsedSampleRate
137+
safeMathRandom() * parsedSampleRate
137138
: // Returns a sample rand with negative sampling decision [sampleRate, 1)
138-
parsedSampleRate + Math.random() * (1 - parsedSampleRate);
139+
parsedSampleRate + safeMathRandom() * (1 - parsedSampleRate);
139140
} else {
140141
// If nothing applies, return a random sample rand.
141-
return Math.random();
142+
return safeMathRandom();
142143
}
143144
}
144145

packages/nextjs/src/server/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { isBuild } from '../common/utils/isBuild';
3434
import { setUrlProcessingMetadata } from '../common/utils/setUrlProcessingMetadata';
3535
import { distDirRewriteFramesIntegration } from './distDirRewriteFramesIntegration';
3636
import { handleOnSpanStart } from './handleOnSpanStart';
37+
import { prepareSafeIdGeneratorContext } from './prepareSafeIdGeneratorContext';
3738

3839
export * from '@sentry/node';
3940

@@ -92,6 +93,7 @@ export function showReportDialog(): void {
9293

9394
/** Inits the Sentry NextJS SDK on node. */
9495
export function init(options: NodeOptions): NodeClient | undefined {
96+
prepareSafeIdGeneratorContext();
9597
if (isBuild()) {
9698
return;
9799
}

0 commit comments

Comments
 (0)