From 53e333f1b6dd90a0b678ec05061ce1f508b65d11 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Mon, 29 Sep 2025 17:39:04 +0200 Subject: [PATCH] fix(aws-serverless): Take `http_proxy` into account when choosing `useLayerExtension` default 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. Fixes: #17804 --- CHANGELOG.md | 7 + packages/aws-serverless/src/init.ts | 47 +++- packages/aws-serverless/test/init.test.ts | 260 +++++++++++++++++++++- 3 files changed, 312 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe9ab44858c1..358078c1fc7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) diff --git a/packages/aws-serverless/src/init.ts b/packages/aws-serverless/src/init.ts index 6640db8ec5fa..e19cc41baf46 100644 --- a/packages/aws-serverless/src/init.ts +++ b/packages/aws-serverless/src/init.ts @@ -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. */ @@ -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, }; @@ -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); diff --git a/packages/aws-serverless/test/init.test.ts b/packages/aws-serverless/test/init.test.ts index 576257e3f3e4..e6a675ecc43f 100644 --- a/packages/aws-serverless/test/init.test.ts +++ b/packages/aws-serverless/test/init.test.ts @@ -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'; @@ -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'); @@ -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), + }), + ); + }); + }); });