From fd2eef5f0351b457b60407129d857c2cd54953a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20=C5=BBydek?= Date: Wed, 18 Mar 2026 11:49:17 +0100 Subject: [PATCH 01/13] feat: introduce TTL-based caching for Secrets Manager --- .changeset/cuddly-tires-tan.md | 5 + proxy/app.ts | 3 +- proxy/test/utils/cache.test.ts | 107 ++++++++++++++++++ proxy/utils/cache.ts | 47 ++++++++ .../secrets-manager/retrieve-secret.ts | 20 ++-- .../secrets-manager-variables.ts | 7 +- proxy/utils/headers.ts | 19 ++++ 7 files changed, 192 insertions(+), 16 deletions(-) create mode 100644 .changeset/cuddly-tires-tan.md create mode 100644 proxy/test/utils/cache.test.ts create mode 100644 proxy/utils/cache.ts diff --git a/.changeset/cuddly-tires-tan.md b/.changeset/cuddly-tires-tan.md new file mode 100644 index 00000000..95c2478b --- /dev/null +++ b/.changeset/cuddly-tires-tan.md @@ -0,0 +1,5 @@ +--- +'@fingerprint/cloudfront-proxy': minor +--- + +Introduce TTL for secret caching diff --git a/proxy/app.ts b/proxy/app.ts index 3cc694d0..568a69a0 100644 --- a/proxy/app.ts +++ b/proxy/app.ts @@ -8,6 +8,7 @@ import type { CloudFrontRequest } from 'aws-lambda/common/cloudfront' import { createIngressHandler } from './handlers/handleIngress' import { handleStatus } from './handlers/handleStatus' import { V4_INGRESS_PATH } from './utils/paths' +import { getSecretCacheTtlMs } from './utils/headers' export type Route = { pathPattern: RegExp @@ -57,7 +58,7 @@ export const handler = async (event: CloudFrontRequestEvent): Promise { + beforeEach(() => { + jest.useFakeTimers() + }) + + afterEach(() => { + jest.useRealTimers() + }) + + test('should store and retrieve values', () => { + const cache = new TTLCache(1000) + cache.set('key1', 'value1') + + expect(cache.get('key1')).toBe('value1') + expect(cache.has('key1')).toBe(true) + }) + + test('should return undefined for missing keys', () => { + const cache = new TTLCache(1000) + + expect(cache.get('missingKey')).toBeUndefined() + expect(cache.has('missingKey')).toBe(false) + }) + + test('should expire items after default TTL', () => { + const cache = new TTLCache(1000) + cache.set('key1', 'value1') + + expect(cache.get('key1')).toBe('value1') + + // Advance time past TTL + jest.advanceTimersByTime(1001) + + expect(cache.get('key1')).toBeUndefined() + expect(cache.has('key1')).toBe(false) + }) + + test('should expire items after default TTL for null values', () => { + const cache = new TTLCache(1000) + cache.set('key1', null) + + expect(cache.get('key1')).toBeNull() + expect(cache.has('key1')).toBe(true) + + // Advance time past TTL + jest.advanceTimersByTime(1001) + + expect(cache.get('key1')).toBeUndefined() + expect(cache.has('key1')).toBe(false) + }) + + test('should respect custom TTL per item', () => { + const cache = new TTLCache(1000) + + // Item with shorter TTL + cache.set('key1', 'value1', 500) + // Item with longer TTL + cache.set('key2', 'value2', 2000) + + // Advance past first item's TTL but before second item's TTL + jest.advanceTimersByTime(1000) + + expect(cache.get('key1')).toBeUndefined() + expect(cache.get('key2')).toBe('value2') + + // Advance past second item's TTL + jest.advanceTimersByTime(1001) + + expect(cache.get('key2')).toBeUndefined() + }) + + test('should delete items', () => { + const cache = new TTLCache(1000) + cache.set('key1', 'value1') + + expect(cache.get('key1')).toBe('value1') + + cache.delete('key1') + + expect(cache.get('key1')).toBeUndefined() + expect(cache.has('key1')).toBe(false) + }) + + test('should clear all items', () => { + const cache = new TTLCache(1000) + cache.set('key1', 'value1') + cache.set('key2', 'value2') + + cache.clear() + + expect(cache.get('key1')).toBeUndefined() + expect(cache.get('key2')).toBeUndefined() + }) + + test('has() should trigger eviction if item is expired', () => { + const cache = new TTLCache(1000) + cache.set('key1', 'value1') + + jest.advanceTimersByTime(1001) + + expect(cache.has('key1')).toBe(false) + // Underlying map should have been cleaned up by has() which calls get() + expect(cache.get('key1')).toBeUndefined() + }) +}) diff --git a/proxy/utils/cache.ts b/proxy/utils/cache.ts new file mode 100644 index 00000000..8f0c2e1c --- /dev/null +++ b/proxy/utils/cache.ts @@ -0,0 +1,47 @@ +interface CacheItem { + value: T + expiresAt: number +} + +export class TTLCache { + private cache: Map> + private readonly ttlMs: number + + constructor(ttlMs: number) { + this.cache = new Map() + this.ttlMs = ttlMs + } + + get(key: K): V | undefined { + const item = this.cache.get(key) + if (item === undefined) { + return undefined + } + + if (Date.now() > item.expiresAt) { + this.cache.delete(key) + return undefined + } + + return item.value + } + + set(key: K, value: V, customTtlMs?: number): void { + this.cache.set(key, { + value, + expiresAt: Date.now() + (customTtlMs ?? this.ttlMs), + }) + } + + has(key: K): boolean { + return this.get(key) !== undefined + } + + delete(key: K): void { + this.cache.delete(key) + } + + clear(): void { + this.cache.clear() + } +} diff --git a/proxy/utils/customer-variables/secrets-manager/retrieve-secret.ts b/proxy/utils/customer-variables/secrets-manager/retrieve-secret.ts index f91c70d1..684d6c22 100644 --- a/proxy/utils/customer-variables/secrets-manager/retrieve-secret.ts +++ b/proxy/utils/customer-variables/secrets-manager/retrieve-secret.ts @@ -1,37 +1,31 @@ import { CustomerVariablesRecord } from '../types' import { - SecretsManagerClient, GetSecretValueCommand, GetSecretValueCommandOutput, + SecretsManagerClient, } from '@aws-sdk/client-secrets-manager' import { arrayBufferToString } from '../../buffer' import { validateSecret } from './validate-secret' import { normalizeSecret } from './normalize-secret' - -interface CacheEntry { - value: CustomerVariablesRecord | null -} +import { TTLCache } from '../../cache' /** * Global cache for customer variables fetched from Secrets Manager. + * By default, the cache is set to expire after 5 minutes. * */ -const cache = new Map() +const cache = new TTLCache(300_000) /** * Retrieves a secret from Secrets Manager and caches it or returns it from cache if it's still valid. * */ -export async function retrieveSecret(secretsManager: SecretsManagerClient, key: string) { +export async function retrieveSecret(secretsManager: SecretsManagerClient, key: string, cacheTtlMs?: number) { if (cache.has(key)) { - const entry = cache.get(key)! - - return entry.value + return cache.get(key)! } const result = await fetchSecret(secretsManager, key) - cache.set(key, { - value: result, - }) + cache.set(key, result, cacheTtlMs) return result } diff --git a/proxy/utils/customer-variables/secrets-manager/secrets-manager-variables.ts b/proxy/utils/customer-variables/secrets-manager/secrets-manager-variables.ts index d3f33f20..7a36c1a7 100644 --- a/proxy/utils/customer-variables/secrets-manager/secrets-manager-variables.ts +++ b/proxy/utils/customer-variables/secrets-manager/secrets-manager-variables.ts @@ -18,7 +18,10 @@ export class SecretsManagerVariables implements CustomerVariableProvider { private readonly secretsManager?: SecretsManagerClient - constructor(private readonly request: CloudFrontRequest) { + constructor( + private readonly request: CloudFrontRequest, + private readonly cacheTtlMs?: number + ) { this.readSecretsInfoFromHeaders() if (SecretsManagerVariables.isValidSecretInfo(this.secretsInfo)) { @@ -45,7 +48,7 @@ export class SecretsManagerVariables implements CustomerVariableProvider { } try { - return await retrieveSecret(this.secretsManager, this.secretsInfo!.secretName!) + return await retrieveSecret(this.secretsManager, this.secretsInfo!.secretName!, this.cacheTtlMs) } catch (error) { console.error('Error retrieving secret from secrets manager', { error, diff --git a/proxy/utils/headers.ts b/proxy/utils/headers.ts index a8ed008b..c080c7c1 100644 --- a/proxy/utils/headers.ts +++ b/proxy/utils/headers.ts @@ -225,3 +225,22 @@ export function getHeaderValue(request: CloudFrontRequest, name: string): string } return headers[name][0].value } + +/** + * Retrieves the secret cache time-to-live (TTL) value in milliseconds from the request headers. + * + * @param {CloudFrontRequest} request - The CloudFront request object containing headers. + * @return {number|undefined} The parsed TTL value in milliseconds if present and valid; otherwise, undefined. + */ +export function getSecretCacheTtlMs(request: CloudFrontRequest): number | undefined { + const value = getHeaderValue(request, 'fpjs_proxy_secret_cache_ttl_ms') + + if (value) { + const parsedValue = parseInt(value) + if (!isNaN(parsedValue)) { + return parsedValue + } + } + + return undefined +} From 991031cef51d2302335c148c5d682a81d6a91e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20=C5=BBydek?= Date: Wed, 18 Mar 2026 11:53:04 +0100 Subject: [PATCH 02/13] chore: replace `isNaN` with `Number.isNaN` for stricter type checking --- proxy/utils/headers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/utils/headers.ts b/proxy/utils/headers.ts index c080c7c1..e30edacd 100644 --- a/proxy/utils/headers.ts +++ b/proxy/utils/headers.ts @@ -237,7 +237,7 @@ export function getSecretCacheTtlMs(request: CloudFrontRequest): number | undefi if (value) { const parsedValue = parseInt(value) - if (!isNaN(parsedValue)) { + if (!Number.isNaN(parsedValue)) { return parsedValue } } From 5022af9a0abbc2baf4fd6d527bafbe5b744987c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20=C5=BBydek?= Date: Wed, 18 Mar 2026 11:57:47 +0100 Subject: [PATCH 03/13] chore: avoid two cache lookups in retrieveSecret Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../customer-variables/secrets-manager/retrieve-secret.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/proxy/utils/customer-variables/secrets-manager/retrieve-secret.ts b/proxy/utils/customer-variables/secrets-manager/retrieve-secret.ts index 684d6c22..f48d791c 100644 --- a/proxy/utils/customer-variables/secrets-manager/retrieve-secret.ts +++ b/proxy/utils/customer-variables/secrets-manager/retrieve-secret.ts @@ -19,8 +19,9 @@ const cache = new TTLCache(300_000) * Retrieves a secret from Secrets Manager and caches it or returns it from cache if it's still valid. * */ export async function retrieveSecret(secretsManager: SecretsManagerClient, key: string, cacheTtlMs?: number) { - if (cache.has(key)) { - return cache.get(key)! + const cached = cache.get(key) + if (cached !== undefined) { + return cached } const result = await fetchSecret(secretsManager, key) From 2f20c6598bac4a53d4ae1a07445f9999b6eccb1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20=C5=BBydek?= Date: Wed, 18 Mar 2026 12:43:27 +0100 Subject: [PATCH 04/13] chore: add validation for custom ttl ms Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- proxy/utils/cache.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/proxy/utils/cache.ts b/proxy/utils/cache.ts index 8f0c2e1c..b848b0c8 100644 --- a/proxy/utils/cache.ts +++ b/proxy/utils/cache.ts @@ -27,9 +27,14 @@ export class TTLCache { } set(key: K, value: V, customTtlMs?: number): void { + const ttlMsToUse = + typeof customTtlMs === 'number' && Number.isFinite(customTtlMs) && customTtlMs >= 0 + ? customTtlMs + : this.ttlMs + this.cache.set(key, { value, - expiresAt: Date.now() + (customTtlMs ?? this.ttlMs), + expiresAt: Date.now() + ttlMsToUse, }) } From 77149d0792ae20644b90378700dea95325e726bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20=C5=BBydek?= Date: Wed, 18 Mar 2026 12:43:48 +0100 Subject: [PATCH 05/13] chore: validate secret cache ttl ms Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- proxy/utils/headers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/utils/headers.ts b/proxy/utils/headers.ts index e30edacd..c871371e 100644 --- a/proxy/utils/headers.ts +++ b/proxy/utils/headers.ts @@ -237,7 +237,7 @@ export function getSecretCacheTtlMs(request: CloudFrontRequest): number | undefi if (value) { const parsedValue = parseInt(value) - if (!Number.isNaN(parsedValue)) { + if (!Number.isNaN(parsedValue) && parsedValue >= 0) { return parsedValue } } From 122b44af47eab60eeadf6f21100a349d73ecd76a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20=C5=BBydek?= Date: Wed, 18 Mar 2026 12:46:18 +0100 Subject: [PATCH 06/13] chore: add test for default TTL fallback when custom TTL is NaN --- proxy/test/utils/cache.test.ts | 11 +++++++++++ proxy/utils/cache.ts | 4 +--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/proxy/test/utils/cache.test.ts b/proxy/test/utils/cache.test.ts index de7c1a24..e86eae64 100644 --- a/proxy/test/utils/cache.test.ts +++ b/proxy/test/utils/cache.test.ts @@ -71,6 +71,17 @@ describe('TTLCache', () => { expect(cache.get('key2')).toBeUndefined() }) + test('should fallback to default TTL if passed TTL is NaN', () => { + const cache = new TTLCache(1000) + + cache.set('key1', 'value1', NaN) + expect(cache.get('key1')).toBe('value1') + + jest.advanceTimersByTime(1001) + + expect(cache.get('key1')).toBeUndefined() + }) + test('should delete items', () => { const cache = new TTLCache(1000) cache.set('key1', 'value1') diff --git a/proxy/utils/cache.ts b/proxy/utils/cache.ts index b848b0c8..9b38fe93 100644 --- a/proxy/utils/cache.ts +++ b/proxy/utils/cache.ts @@ -28,9 +28,7 @@ export class TTLCache { set(key: K, value: V, customTtlMs?: number): void { const ttlMsToUse = - typeof customTtlMs === 'number' && Number.isFinite(customTtlMs) && customTtlMs >= 0 - ? customTtlMs - : this.ttlMs + typeof customTtlMs === 'number' && Number.isFinite(customTtlMs) && customTtlMs >= 0 ? customTtlMs : this.ttlMs this.cache.set(key, { value, From 6982accd722b1d405010f30249ae69749d803c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20=C5=BBydek?= Date: Wed, 18 Mar 2026 12:46:46 +0100 Subject: [PATCH 07/13] chore: ensure custom TTL validation checks for finite values --- proxy/utils/headers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/utils/headers.ts b/proxy/utils/headers.ts index c871371e..7ff0d486 100644 --- a/proxy/utils/headers.ts +++ b/proxy/utils/headers.ts @@ -237,7 +237,7 @@ export function getSecretCacheTtlMs(request: CloudFrontRequest): number | undefi if (value) { const parsedValue = parseInt(value) - if (!Number.isNaN(parsedValue) && parsedValue >= 0) { + if (!Number.isNaN(parsedValue) && parsedValue >= 0 && Number.isFinite(parsedValue)) { return parsedValue } } From 9ef50ee8f3f02183ff733458af82badbc13f4967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20=C5=BBydek?= Date: Wed, 18 Mar 2026 12:48:40 +0100 Subject: [PATCH 08/13] chore: add test for cache expiration and refetch behavior in retrieveSecret --- .../secrets-manager/retrieve-secret.test.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/proxy/test/utils/customer-variables/secrets-manager/retrieve-secret.test.ts b/proxy/test/utils/customer-variables/secrets-manager/retrieve-secret.test.ts index 157908f9..09e4a960 100644 --- a/proxy/test/utils/customer-variables/secrets-manager/retrieve-secret.test.ts +++ b/proxy/test/utils/customer-variables/secrets-manager/retrieve-secret.test.ts @@ -2,6 +2,7 @@ import { mockClient } from 'aws-sdk-client-mock' import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager' import { clearSecretsCache, retrieveSecret } from '../../../../utils/customer-variables/secrets-manager/retrieve-secret' import 'aws-sdk-client-mock-jest' +import { afterEach } from 'node:test' const secretName = 'test' const mock = mockClient(SecretsManagerClient) @@ -9,11 +10,16 @@ const client = new SecretsManagerClient({}) describe('retrieve secret', () => { beforeEach(() => { + jest.useFakeTimers() clearSecretsCache() mock.reset() }) + afterEach(() => { + jest.useRealTimers() + }) + it('caches result even if it is null', async () => { mock .on(GetSecretValueCommand, { @@ -27,6 +33,26 @@ describe('retrieve secret', () => { expect(mock).toHaveReceivedCommandTimes(GetSecretValueCommand, 1) }) + it('refetches secret after cache expires', async () => { + mock + .on(GetSecretValueCommand, { + SecretId: secretName, + }) + .resolves({}) + + await retrieveSecret(client, secretName) + await retrieveSecret(client, secretName) + + expect(mock).toHaveReceivedCommandTimes(GetSecretValueCommand, 1) + + jest.advanceTimersByTime(500_001) + + await retrieveSecret(client, secretName) + await retrieveSecret(client, secretName) + + expect(mock).toHaveReceivedCommandTimes(GetSecretValueCommand, 2) + }) + it('caches result even if it secrets manager throws', async () => { mock .on(GetSecretValueCommand, { From 8a1f4cd1d432191de34a6d36a9bd13fa2b77db4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20=C5=BBydek?= Date: Wed, 18 Mar 2026 12:53:54 +0100 Subject: [PATCH 09/13] chore: add explicit radix Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- proxy/utils/headers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/utils/headers.ts b/proxy/utils/headers.ts index 7ff0d486..73169f5c 100644 --- a/proxy/utils/headers.ts +++ b/proxy/utils/headers.ts @@ -236,7 +236,7 @@ export function getSecretCacheTtlMs(request: CloudFrontRequest): number | undefi const value = getHeaderValue(request, 'fpjs_proxy_secret_cache_ttl_ms') if (value) { - const parsedValue = parseInt(value) + const parsedValue = parseInt(value, 10) if (!Number.isNaN(parsedValue) && parsedValue >= 0 && Number.isFinite(parsedValue)) { return parsedValue } From 30dfaeb87677947ba53aad5bd8f2318fdb30b89f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20=C5=BBydek?= Date: Wed, 18 Mar 2026 12:54:12 +0100 Subject: [PATCH 10/13] chore: fix afterEach import --- .../customer-variables/secrets-manager/retrieve-secret.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/proxy/test/utils/customer-variables/secrets-manager/retrieve-secret.test.ts b/proxy/test/utils/customer-variables/secrets-manager/retrieve-secret.test.ts index 09e4a960..9225dad4 100644 --- a/proxy/test/utils/customer-variables/secrets-manager/retrieve-secret.test.ts +++ b/proxy/test/utils/customer-variables/secrets-manager/retrieve-secret.test.ts @@ -2,7 +2,6 @@ import { mockClient } from 'aws-sdk-client-mock' import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager' import { clearSecretsCache, retrieveSecret } from '../../../../utils/customer-variables/secrets-manager/retrieve-secret' import 'aws-sdk-client-mock-jest' -import { afterEach } from 'node:test' const secretName = 'test' const mock = mockClient(SecretsManagerClient) From 1efef3de6c1e6619ff189a373c073bf72c357833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20=C5=BBydek?= Date: Wed, 18 Mar 2026 12:54:40 +0100 Subject: [PATCH 11/13] chore: align behavior with an exact TTL boundary Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- proxy/utils/cache.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/utils/cache.ts b/proxy/utils/cache.ts index 9b38fe93..3f2273e6 100644 --- a/proxy/utils/cache.ts +++ b/proxy/utils/cache.ts @@ -18,7 +18,7 @@ export class TTLCache { return undefined } - if (Date.now() > item.expiresAt) { + if (Date.now() >= item.expiresAt) { this.cache.delete(key) return undefined } From 8eb90bd41adc4d2399fe6927ef4f2d620dd8e23d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20=C5=BBydek?= Date: Thu, 26 Mar 2026 11:56:39 +0100 Subject: [PATCH 12/13] chore: centralize TTL validation logic in TTLCache and apply defaults --- proxy/utils/cache.ts | 12 +++++++++--- proxy/utils/headers.ts | 3 ++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/proxy/utils/cache.ts b/proxy/utils/cache.ts index 3f2273e6..629949ac 100644 --- a/proxy/utils/cache.ts +++ b/proxy/utils/cache.ts @@ -7,9 +7,12 @@ export class TTLCache { private cache: Map> private readonly ttlMs: number + // Default TTL is 5 minutes + private static readonly DEFAULT_TTL_MS = 300_000 + constructor(ttlMs: number) { this.cache = new Map() - this.ttlMs = ttlMs + this.ttlMs = TTLCache.isValidTTL(ttlMs) ? ttlMs : TTLCache.DEFAULT_TTL_MS } get(key: K): V | undefined { @@ -27,8 +30,7 @@ export class TTLCache { } set(key: K, value: V, customTtlMs?: number): void { - const ttlMsToUse = - typeof customTtlMs === 'number' && Number.isFinite(customTtlMs) && customTtlMs >= 0 ? customTtlMs : this.ttlMs + const ttlMsToUse = TTLCache.isValidTTL(customTtlMs) ? customTtlMs : this.ttlMs this.cache.set(key, { value, @@ -47,4 +49,8 @@ export class TTLCache { clear(): void { this.cache.clear() } + + static isValidTTL(value?: number): value is number { + return typeof value === 'number' && !Number.isNaN(value) && Number.isFinite(value) && value >= 0 + } } diff --git a/proxy/utils/headers.ts b/proxy/utils/headers.ts index 73169f5c..ef3f69c4 100644 --- a/proxy/utils/headers.ts +++ b/proxy/utils/headers.ts @@ -4,6 +4,7 @@ import { filterCookie } from './cookie' import { updateCacheControlHeader } from './cache-control' import { CustomerVariables } from './customer-variables/customer-variables' import { getPreSharedSecret } from './customer-variables/selectors' +import { TTLCache } from './cache' export const BLACKLISTED_HEADERS = new Set([ 'age', @@ -237,7 +238,7 @@ export function getSecretCacheTtlMs(request: CloudFrontRequest): number | undefi if (value) { const parsedValue = parseInt(value, 10) - if (!Number.isNaN(parsedValue) && parsedValue >= 0 && Number.isFinite(parsedValue)) { + if (TTLCache.isValidTTL(parsedValue)) { return parsedValue } } From da20f1dc0f3e185250d3266057a0d1abd06bac46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20=C5=BBydek?= Date: Thu, 26 Mar 2026 12:00:15 +0100 Subject: [PATCH 13/13] chore: rename package to `@fingerprint/aws-cloudfront-proxy` in changeset --- .changeset/cuddly-tires-tan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/cuddly-tires-tan.md b/.changeset/cuddly-tires-tan.md index 95c2478b..b4f26d94 100644 --- a/.changeset/cuddly-tires-tan.md +++ b/.changeset/cuddly-tires-tan.md @@ -1,5 +1,5 @@ --- -'@fingerprint/cloudfront-proxy': minor +'@fingerprint/aws-cloudfront-proxy': minor --- Introduce TTL for secret caching