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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

### Important Changes

- **fix(aws-serverless): Take `http_proxy` into account when choosing
`useLayerExtension` default ([#17817](https://github.com/getsentry/sentry-javascript/pull/17817))**

The default setting for `useLayerExtension` now considers the `http_proxy` environment variable. When `http_proxy` is set, `useLayerExtension` will be off by default. If you use a `http_proxy` but would still like to make use of the Sentry Lambda extension, exempt `localhost` in a `no_proxy` environment variable.

## 10.16.0

- feat(logs): Add internal `replay_is_buffering` flag ([#17752](https://github.com/getsentry/sentry-javascript/pull/17752))
Expand Down
47 changes: 46 additions & 1 deletion packages/aws-serverless/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,44 @@ import { getDefaultIntegrationsWithoutPerformance, initWithoutDefaultIntegration
import { DEBUG_BUILD } from './debug-build';
import { awsIntegration } from './integration/aws';
import { awsLambdaIntegration } from './integration/awslambda';

/**
* Checks if proxy environment variables would interfere with the layer extension.
* The layer extension uses localhost:9000, so we need to check if proxy settings would prevent this.
*/
function shouldDisableLayerExtensionForProxy(): boolean {
const { http_proxy, no_proxy } = process.env;

// If no http proxy is configured, no interference (https_proxy doesn't affect HTTP requests)
if (!http_proxy) {
return false;
}

// Check if localhost is exempted by no_proxy
if (no_proxy) {
const exemptions = no_proxy.split(',').map(exemption => exemption.trim().toLowerCase());

// Handle common localhost exemption patterns explicitly
// If localhost is exempted, requests to the layer extension will not be proxied
const localhostExemptions = ['*', 'localhost', '127.0.0.1', '::1'];
if (exemptions.some(exemption => localhostExemptions.includes(exemption))) {
return false;
}
}

// If http_proxy is set and no localhost exemption, it would interfere
// The layer extension uses HTTP to localhost:9000, so only http_proxy matters
if (http_proxy) {
DEBUG_BUILD &&
debug.log(
'Disabling useLayerExtension due to http_proxy environment variable. Consider adding localhost to no_proxy to re-enable.',
);
return true;
}

return false;
}

/**
* Get the default integrations for the AWSLambda SDK.
*/
Expand All @@ -28,9 +66,11 @@ export interface AwsServerlessOptions extends NodeOptions {
*/
export function init(options: AwsServerlessOptions = {}): NodeClient | undefined {
const sdkSource = getSDKSource();
const proxyWouldInterfere = shouldDisableLayerExtensionForProxy();

const opts = {
defaultIntegrations: getDefaultIntegrations(options),
useLayerExtension: sdkSource === 'aws-lambda-layer' && !options.tunnel,
useLayerExtension: sdkSource === 'aws-lambda-layer' && !options.tunnel && !proxyWouldInterfere,
...options,
};

Expand All @@ -48,6 +88,11 @@ export function init(options: AwsServerlessOptions = {}): NodeClient | undefined
} else {
DEBUG_BUILD && debug.warn('The Sentry Lambda extension is only supported when using the AWS Lambda layer.');
}
} else if (sdkSource === 'aws-lambda-layer' && proxyWouldInterfere) {
DEBUG_BUILD &&
debug.warn(
'Sentry Lambda extension disabled due to proxy environment variables (http_proxy/https_proxy). Consider adding localhost to no_proxy to re-enable.',
);
}

applySdkMetadata(opts, 'aws-serverless', ['aws-serverless'], sdkSource);
Expand Down
260 changes: 259 additions & 1 deletion packages/aws-serverless/test/init.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getSDKSource } from '@sentry/core';
import { initWithoutDefaultIntegrations } from '@sentry/node';
import { describe, expect, test, vi } from 'vitest';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import type { AwsServerlessOptions } from '../src/init';
import { init } from '../src/init';

Expand All @@ -18,6 +18,12 @@ const mockGetSDKSource = vi.mocked(getSDKSource);
const mockInitWithoutDefaultIntegrations = vi.mocked(initWithoutDefaultIntegrations);

describe('init', () => {
beforeEach(() => {
// Clean up environment variables between tests
delete process.env.http_proxy;
delete process.env.no_proxy;
});

describe('Lambda extension setup', () => {
test('should preserve user-provided tunnel option when Lambda extension is enabled', () => {
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
Expand Down Expand Up @@ -128,4 +134,256 @@ describe('init', () => {
);
});
});

describe('proxy environment variables and layer extension', () => {
test('should enable useLayerExtension when no proxy env vars are set', () => {
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
const options: AwsServerlessOptions = {};

init(options);

expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.objectContaining({
useLayerExtension: true,
tunnel: 'http://localhost:9000/envelope',
}),
);
});

test('should disable useLayerExtension when http_proxy is set', () => {
process.env.http_proxy = 'http://proxy.example.com:8080';
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
const options: AwsServerlessOptions = {};

init(options);

expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.objectContaining({
useLayerExtension: false,
}),
);
expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.not.objectContaining({
tunnel: expect.any(String),
}),
);
});

describe('no_proxy patterns', () => {
test('should enable useLayerExtension when no_proxy=* (wildcard)', () => {
process.env.http_proxy = 'http://proxy.example.com:8080';
process.env.no_proxy = '*';
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
const options: AwsServerlessOptions = {};

init(options);

expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.objectContaining({
useLayerExtension: true,
tunnel: 'http://localhost:9000/envelope',
}),
);
});

test('should enable useLayerExtension when no_proxy contains localhost', () => {
process.env.http_proxy = 'http://proxy.example.com:8080';
process.env.no_proxy = 'localhost';
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
const options: AwsServerlessOptions = {};

init(options);

expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.objectContaining({
useLayerExtension: true,
tunnel: 'http://localhost:9000/envelope',
}),
);
});

test('should enable useLayerExtension when no_proxy contains 127.0.0.1', () => {
process.env.http_proxy = 'http://proxy.example.com:8080';
process.env.no_proxy = '127.0.0.1';
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
const options: AwsServerlessOptions = {};

init(options);

expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.objectContaining({
useLayerExtension: true,
tunnel: 'http://localhost:9000/envelope',
}),
);
});

test('should enable useLayerExtension when no_proxy contains ::1', () => {
process.env.http_proxy = 'http://proxy.example.com:8080';
process.env.no_proxy = '::1';
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
const options: AwsServerlessOptions = {};

init(options);

expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.objectContaining({
useLayerExtension: true,
tunnel: 'http://localhost:9000/envelope',
}),
);
});

test('should enable useLayerExtension when no_proxy contains localhost in a comma-separated list', () => {
process.env.http_proxy = 'http://proxy.example.com:8080';
process.env.no_proxy = 'example.com,localhost,other.com';
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
const options: AwsServerlessOptions = {};

init(options);

expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.objectContaining({
useLayerExtension: true,
tunnel: 'http://localhost:9000/envelope',
}),
);
});

test('should disable useLayerExtension when no_proxy does not contain localhost patterns', () => {
process.env.http_proxy = 'http://proxy.example.com:8080';
process.env.no_proxy = 'example.com,other.com';
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
const options: AwsServerlessOptions = {};

init(options);

expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.objectContaining({
useLayerExtension: false,
}),
);
expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.not.objectContaining({
tunnel: expect.any(String),
}),
);
});

test('should disable useLayerExtension when no_proxy contains host (no longer supported)', () => {
process.env.http_proxy = 'http://proxy.example.com:8080';
process.env.no_proxy = 'host';
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
const options: AwsServerlessOptions = {};

init(options);

expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.objectContaining({
useLayerExtension: false,
}),
);
expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.not.objectContaining({
tunnel: expect.any(String),
}),
);
});

test('should handle case-insensitive no_proxy values', () => {
process.env.http_proxy = 'http://proxy.example.com:8080';
process.env.no_proxy = 'LOCALHOST';
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
const options: AwsServerlessOptions = {};

init(options);

expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.objectContaining({
useLayerExtension: true,
tunnel: 'http://localhost:9000/envelope',
}),
);
});

test('should handle whitespace in no_proxy values', () => {
process.env.http_proxy = 'http://proxy.example.com:8080';
process.env.no_proxy = ' localhost , example.com ';
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
const options: AwsServerlessOptions = {};

init(options);

expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.objectContaining({
useLayerExtension: true,
tunnel: 'http://localhost:9000/envelope',
}),
);
});
});

test('should respect explicit useLayerExtension=false even with no proxy interference', () => {
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
const options: AwsServerlessOptions = {
useLayerExtension: false,
};

init(options);

expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.objectContaining({
useLayerExtension: false,
}),
);
expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.not.objectContaining({
tunnel: expect.any(String),
}),
);
});

test('should respect explicit useLayerExtension=false even with proxy that would interfere', () => {
process.env.http_proxy = 'http://proxy.example.com:8080';
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
const options: AwsServerlessOptions = {
useLayerExtension: false,
};

init(options);

expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.objectContaining({
useLayerExtension: false,
}),
);
expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.not.objectContaining({
tunnel: expect.any(String),
}),
);
});

test('should respect explicit useLayerExtension=false even when no_proxy would enable it', () => {
process.env.http_proxy = 'http://proxy.example.com:8080';
process.env.no_proxy = 'localhost';
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
const options: AwsServerlessOptions = {
useLayerExtension: false,
};

init(options);

expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.objectContaining({
useLayerExtension: false,
}),
);
expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.not.objectContaining({
tunnel: expect.any(String),
}),
);
});
});
});
Loading