From 682729a37a3b58a1b90215583974624d5d04790b Mon Sep 17 00:00:00 2001 From: Jonathan Mukai-Heidt Date: Thu, 15 Jan 2026 21:13:36 -0500 Subject: [PATCH] Make ENV vars sensitive by default --- docs/components/adapter.md | 2 +- docs/guides/porting-a-v2-ea-to-v3.md | 2 +- src/config/index.ts | 11 ++++++++++- test/debug-endpoints.test.ts | 1 + test/logger.test.ts | 26 ++++++++++++++++++++++++++ 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/docs/components/adapter.md b/docs/components/adapter.md index 540eb970..8dfa2270 100644 --- a/docs/components/adapter.md +++ b/docs/components/adapter.md @@ -28,7 +28,7 @@ export const config = new AdapterConfig({ fn: () => {}, }, // If applicable, a Validator object to validate the env var value. Return an error message for a failed validation, or undefined if it passes. required: true, // If the env var should be required. Default = false - sensitive: true, // Set to true to censor this env var from logs. Default = false + sensitive: false, // Set to false if the env var is safe to show uncensored in logs or telemetry. Default = true }, }) ``` diff --git a/docs/guides/porting-a-v2-ea-to-v3.md b/docs/guides/porting-a-v2-ea-to-v3.md index e33bc6e2..3836a49e 100644 --- a/docs/guides/porting-a-v2-ea-to-v3.md +++ b/docs/guides/porting-a-v2-ea-to-v3.md @@ -238,7 +238,7 @@ export const customSettings = { default: 'foo', // If applicable, a default value validate: (value?: string) => {}, // If applicable, a function to validate the env var value. Return an error message for a failed validation, or undefined if it passes. required: true, // If the env var should be required. Default = false - sensitive: true, // Set to true to censor this env var from logs. Default = false + sensitive: false, // Set to false if the env var is safe to show uncensored in logs or telemetry. Default = true }, } as const ``` diff --git a/src/config/index.ts b/src/config/index.ts index bed422b0..c2141e7e 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -19,6 +19,7 @@ export const BaseSettingsDefinition = { description: 'Starting path for the EA handler endpoint', type: 'string', default: '/', + sensitive: false, }, CACHE_LOCK_DURATION: { description: 'Time (in ms) used as a baseline for the acquisition and extension of cache locks', @@ -57,6 +58,7 @@ export const BaseSettingsDefinition = { description: 'Hostname for the Redis instance to be used', type: 'string', default: '127.0.0.1', + sensitive: false, }, CACHE_REDIS_MAX_RECONNECT_COOLDOWN: { description: 'Max cooldown (in ms) before attempting redis reconnection', @@ -72,6 +74,7 @@ export const BaseSettingsDefinition = { CACHE_REDIS_PATH: { description: 'The UNIX socket string of the Redis server', type: 'string', + sensitive: false, }, CACHE_REDIS_PORT: { description: 'Port for the Redis instance to be used', @@ -102,6 +105,7 @@ export const BaseSettingsDefinition = { description: 'Specifies a prefix to use for cache keys', type: 'string', default: '', + sensitive: false, }, STREAM_HANDLER_RETRY_MAX_MS: { type: 'number', @@ -156,6 +160,7 @@ export const BaseSettingsDefinition = { description: 'Minimum level required for logs to be output', type: 'string', default: 'info', + sensitive: false, }, CENSOR_SENSITIVE_LOGS: { description: 'Controls whether the logging of sensitive information is enabled or disabled', @@ -187,6 +192,7 @@ export const BaseSettingsDefinition = { description: 'Rate limiting tier to use from the available options for the adapter. If not present, the adapter will run using the first tier on the list.', type: 'string', + sensitive: false, }, RATE_LIMIT_CAPACITY: { description: 'Used as rate limit capacity per minute and ignores tier settings if defined', @@ -274,6 +280,7 @@ export const BaseSettingsDefinition = { description: 'Default key to be used when one cannot be determined from request parameters', type: 'string', default: 'DEFAULT_CACHE_KEY', + sensitive: false, }, EA_HOST: { description: @@ -281,6 +288,7 @@ export const BaseSettingsDefinition = { type: 'string', default: '::', validate: validator.host(), + sensitive: false, }, EA_MODE: { description: @@ -316,6 +324,7 @@ export const BaseSettingsDefinition = { description: 'Base64 Public Key of TSL/SSL certificate', type: 'string', validate: validator.base64(), + sensitive: false, }, TLS_PASSPHRASE: { description: 'Password to be used to generate an encryption key', @@ -666,7 +675,7 @@ export class AdapterConfig setting && setting.type === 'string' && - (setting.sensitive || name.endsWith('RPC_URL')) && + (setting.sensitive !== false || name.endsWith('RPC_URL')) && (this.settings as Record)[name], ) .map(([name]) => ({ diff --git a/test/debug-endpoints.test.ts b/test/debug-endpoints.test.ts index 06559d07..26568e74 100644 --- a/test/debug-endpoints.test.ts +++ b/test/debug-endpoints.test.ts @@ -141,6 +141,7 @@ test.serial('/debug/settings/raw endpoint returns expected values', async (t) => 'Rate limiting tier to use from the available options for the adapter. If not present, the adapter will run using the first tier on the list.', name: 'RATE_LIMIT_API_TIER', required: false, + sensitive: false, customSetting: false, }, ) diff --git a/test/logger.test.ts b/test/logger.test.ts index f497d6c1..55d3e004 100644 --- a/test/logger.test.ts +++ b/test/logger.test.ts @@ -13,8 +13,19 @@ test.before(async () => { type: 'string', sensitive: true, }, + AB_LAMBO_MODEL: { + description: 'Test harmless env var that is safe to log', + type: 'string', + sensitive: false, + }, + API_PRIVATE_KEY: { + description: 'Test env var that a developer forgot to explicitly flag as sensitive', + type: 'string', + }, } satisfies SettingsDefinitionMap process.env['API_KEY'] = 'mock-api-key' + process.env['AB_LAMBO_MODEL'] = 'revuelto' + process.env['API_PRIVATE_KEY'] = 'mock-private-key' const config = new AdapterConfig(customSettings) const adapter = new Adapter({ name: 'TEST', @@ -33,6 +44,11 @@ test('properly builds censor list', async (t) => { const censorList = CensorList.getAll() // eslint-disable-next-line prefer-regex-literals t.deepEqual(censorList[0], { key: 'API_KEY', value: RegExp('mock\\-api\\-key', 'gi') }) + t.deepEqual(censorList[1], { + key: 'API_PRIVATE_KEY', + // eslint-disable-next-line prefer-regex-literals + value: RegExp('mock\\-private\\-key', 'gi'), + }) }) test('properly redacts API_KEY (string)', async (t) => { @@ -65,6 +81,16 @@ test('properly handles undefined', async (t) => { t.deepEqual(redacted, undefined) }) +test('does not censor vars flagged as sensitive = false', async (t) => { + const redacted = censor({ abLamboModel: 'revuelto' }, CensorList.getAll()) + t.deepEqual(redacted, { abLamboModel: 'revuelto' }) +}) + +test('censor vars not explicitly flagged as sensitive', async (t) => { + const redacted = censor({ publicApiKey: 'mock-private-key' }, CensorList.getAll()) + t.deepEqual(redacted, { publicApiKey: '[API_PRIVATE_KEY REDACTED]' }) +}) + test('properly redacts API_KEY (multiple nested values)', async (t) => { const redacted = censor( { apiKey: 'mock-api-key', config: { headers: { auth: 'mock-api-key' } } },