Skip to content
Draft
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
2 changes: 1 addition & 1 deletion docs/components/adapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
})
```
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/porting-a-v2-ea-to-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down
11 changes: 10 additions & 1 deletion src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand All @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -274,13 +280,15 @@ 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:
'Host this EA will listen for REST requests on (if mode is set to "reader" or "reader-writer")',
type: 'string',
default: '::',
validate: validator.host(),
sensitive: false,
},
EA_MODE: {
description:
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -666,7 +675,7 @@ export class AdapterConfig<T extends SettingsDefinitionMap = SettingsDefinitionM
([name, setting]) =>
setting &&
setting.type === 'string' &&
(setting.sensitive || name.endsWith('RPC_URL')) &&
(setting.sensitive !== false || name.endsWith('RPC_URL')) &&
(this.settings as Record<string, ValidSettingValue>)[name],
)
.map(([name]) => ({
Expand Down
1 change: 1 addition & 0 deletions test/debug-endpoints.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
)
Expand Down
26 changes: 26 additions & 0 deletions test/logger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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) => {
Expand Down Expand Up @@ -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' } } },
Expand Down
Loading