Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion packages/cloudflare/src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function withSentry<
QueueHandlerMessage,
CfHostMetadata
>,
>(optionsCallback: (env: Env) => CloudflareOptions, handler: T): T {
>(optionsCallback: (env: Env) => CloudflareOptions | undefined, handler: T): T {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

note: I made this return undefined as it was also working before theoretically. I adapted the types to also allow undefined just to have this case also handled

setAsyncLocalStorageAsyncContextStrategy();

try {
Expand Down
21 changes: 19 additions & 2 deletions packages/cloudflare/src/options.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { envToBool } from '@sentry/core';
import type { CloudflareOptions } from './client';

/**
Expand All @@ -17,6 +18,12 @@ function isVersionMetadata(value: unknown): value is CfVersionMetadata {
return typeof value === 'object' && value !== null && 'id' in value && typeof value.id === 'string';
}

function getEnvVar<T extends Record<string, unknown>>(env: unknown, varName: keyof T): string | undefined {
return typeof env === 'object' && env !== null && varName in env && typeof (env as T)[varName] === 'string'
? ((env as T)[varName] as string)
: undefined;
}

/**
* Merges the options passed in from the user with the options we read from
* the Cloudflare `env` environment variable object.
Expand All @@ -31,7 +38,7 @@ function isVersionMetadata(value: unknown): value is CfVersionMetadata {
*
* @returns The final options.
*/
export function getFinalOptions(userOptions: CloudflareOptions, env: unknown): CloudflareOptions {
export function getFinalOptions(userOptions: CloudflareOptions = {}, env: unknown): CloudflareOptions {
if (typeof env !== 'object' || env === null) {
return userOptions;
}
Expand All @@ -44,5 +51,15 @@ export function getFinalOptions(userOptions: CloudflareOptions, env: unknown): C
? env.CF_VERSION_METADATA.id
: undefined;

return { release, ...userOptions };
const tracesSampleRate = userOptions.tracesSampleRate ?? parseFloat(getEnvVar(env, 'SENTRY_TRACE_SAMPLE_RATE') ?? '');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Env variable name typo: missing 'S' in TRACES

High Severity

The code reads SENTRY_TRACE_SAMPLE_RATE (missing the 'S' in "TRACES"), but the standard Sentry env variable name is SENTRY_TRACES_SAMPLE_RATE. The tests also use SENTRY_TRACES_SAMPLE_RATE. This mismatch means the traces sample rate env variable will never be read, making the feature non-functional. This also means the related tests will fail.

Fix in Cursor Fix in Web


return {
release,
...userOptions,
dsn: userOptions.dsn ?? getEnvVar(env, 'SENTRY_DSN'),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l: can we also add SENTRY_DEBUG? You could use envToBool for that from node-core, maybe lift that up into @sentry/core?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Great idea. Added.

I also added tunnel to it

environment: userOptions.environment ?? getEnvVar(env, 'SENTRY_ENVIRONMENT'),
tracesSampleRate: isFinite(tracesSampleRate) ? tracesSampleRate : undefined,
debug: userOptions.debug ?? envToBool(getEnvVar(env, 'SENTRY_DEBUG')),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

envToBool(undefined) forces debug: false when unset

Medium Severity

When SENTRY_DEBUG is absent from env, getEnvVar returns undefined, and envToBool(undefined) returns false (since Boolean(undefined) is false in loose mode). This means debug is always explicitly set to false in the returned options, even when neither the user nor the env specified it. This breaks multiple existing toEqual assertions (e.g., lines 29, 120, 132) that don't expect a debug property.

Fix in Cursor Fix in Web

tunnel: userOptions.tunnel ?? getEnvVar(env, 'SENTRY_TUNNEL'),
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Spread operator overrides env-derived fallback values

Medium Severity

The ...userOptions spread at the end of the return object overrides the env-derived fallback values for dsn, environment, and tracesSampleRate whenever those keys exist in userOptions — even if their values are undefined. This means if a user passes { dsn: undefined }, the SENTRY_DSN env fallback is silently ignored. The ?? fallback logic and the isFinite guard are both bypassed by the spread.

Fix in Cursor Fix in Web

}
54 changes: 54 additions & 0 deletions packages/cloudflare/test/options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,60 @@ describe('getFinalOptions', () => {
expect(result).toEqual(userOptions);
});

it('uses SENTRY_DSN from env when user dsn is undefined', () => {
const userOptions = { release: 'user-release' };
const env = { SENTRY_DSN: 'https://key@ingest.sentry.io/1' };

const result = getFinalOptions(userOptions, env);

expect(result.dsn).toBe('https://key@ingest.sentry.io/1');
expect(result.release).toBe('user-release');
});

it('uses SENTRY_ENVIRONMENT from env when user environment is undefined', () => {
const userOptions = { dsn: 'test-dsn' };
const env = { SENTRY_ENVIRONMENT: 'staging' };

const result = getFinalOptions(userOptions, env);

expect(result.environment).toBe('staging');
});

it('uses SENTRY_TRACES_SAMPLE_RATE from env when user tracesSampleRate is undefined', () => {
const userOptions = { dsn: 'test-dsn' };
const env = { SENTRY_TRACES_SAMPLE_RATE: '0.5' };

const result = getFinalOptions(userOptions, env);

expect(result.tracesSampleRate).toBe(0.5);
});

it('does not use SENTRY_TRACES_SAMPLE_RATE from env when it is gibberish', () => {
const env = { SENTRY_TRACES_SAMPLE_RATE: 'ʕっ•ᴥ•ʔっ' };

const result = getFinalOptions(undefined, env);

expect(result.tracesSampleRate).toBeUndefined();
});

it('prefers user dsn over env SENTRY_DSN', () => {
const userOptions = { dsn: 'user-dsn', release: 'user-release' };
const env = { SENTRY_DSN: 'https://env@ingest.sentry.io/1' };

const result = getFinalOptions(userOptions, env);

expect(result.dsn).toBe('user-dsn');
});

it('ignores SENTRY_DSN when not a string', () => {
const userOptions = { dsn: undefined };
const env = { SENTRY_DSN: 123 };

const result = getFinalOptions(userOptions, env);

expect(result.dsn).toBeUndefined();
});

describe('CF_VERSION_METADATA', () => {
it('uses CF_VERSION_METADATA.id as release when no other release is set', () => {
const userOptions = { dsn: 'test-dsn' };
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export {
_INTERNAL_shouldSkipAiProviderWrapping,
_INTERNAL_clearAiProviderSkips,
} from './utils/ai/providerSkip';
export { envToBool } from './utils/envToBool';
export { applyScopeDataToEvent, mergeScopeData, getCombinedScopeData } from './utils/scopeData';
export { prepareEvent } from './utils/prepareEvent';
export type { ExclusiveEventHintOrCaptureContext } from './utils/prepareEvent';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { envToBool } from '../../src/utils/envToBool';
import { envToBool } from '../../../src/utils/envToBool';

describe('envToBool', () => {
it.each([
Expand Down
2 changes: 1 addition & 1 deletion packages/node-core/src/common-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export { getRequestUrl } from './utils/getRequestUrl';
export { initializeEsmLoader } from './sdk/esmLoader';
export { isCjs } from './utils/detection';
export { createMissingInstrumentationContext } from './utils/createMissingInstrumentationContext';
export { envToBool } from './utils/envToBool';
export { makeNodeTransport, type NodeTransportOptions } from './transports';
export type { HTTPModuleRequestIncomingMessage } from './transports/http-module';
export { cron } from './cron';
Expand Down Expand Up @@ -117,6 +116,7 @@ export {
wrapMcpServerWithSentry,
featureFlagsIntegration,
metrics,
envToBool,
} from '@sentry/core';

export type {
Expand Down
2 changes: 1 addition & 1 deletion packages/node-core/src/light/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
consoleIntegration,
consoleSandbox,
debug,
envToBool,
eventFiltersIntegration,
functionToStringIntegration,
getCurrentScope,
Expand All @@ -29,7 +30,6 @@ import { defaultStackParser, getSentryRelease } from '../sdk/api';
import { makeNodeTransport } from '../transports';
import type { NodeClientOptions, NodeOptions } from '../types';
import { isCjs } from '../utils/detection';
import { envToBool } from '../utils/envToBool';
import { getSpotlightConfig } from '../utils/spotlight';
import { setAsyncLocalStorageAsyncContextStrategy } from './asyncLocalStorageStrategy';
import { LightNodeClient } from './client';
Expand Down
2 changes: 1 addition & 1 deletion packages/node-core/src/sdk/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
consoleSandbox,
conversationIdIntegration,
debug,
envToBool,
functionToStringIntegration,
getCurrentScope,
getIntegrationsToSetup,
Expand Down Expand Up @@ -38,7 +39,6 @@ import { systemErrorIntegration } from '../integrations/systemError';
import { makeNodeTransport } from '../transports';
import type { NodeClientOptions, NodeOptions } from '../types';
import { isCjs } from '../utils/detection';
import { envToBool } from '../utils/envToBool';
import { getSpotlightConfig } from '../utils/spotlight';
import { defaultStackParser, getSentryRelease } from './api';
import { NodeClient } from './client';
Expand Down
2 changes: 1 addition & 1 deletion packages/node-core/src/utils/spotlight.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { envToBool } from './envToBool';
import { envToBool } from '@sentry/core';

/**
* Parse the spotlight option with proper precedence:
Expand Down
Loading