failure-lambda
Version:
Failure injection for AWS Lambda - chaos engineering made simple
1 lines • 50.4 kB
Source Map (JSON)
{"version":3,"sources":["../src/middy.ts","../src/config.ts","../src/types.ts","../src/log.ts","../src/failures/latency.ts","../src/failures/exception.ts","../src/failures/statuscode.ts","../src/failures/diskspace.ts","../src/failures/denylist.ts","../src/failures/timeout.ts","../src/failures/corruption.ts","../src/matching.ts","../src/orchestration.ts"],"sourcesContent":["import type { Context } from \"aws-lambda\";\nimport type { FailureLambdaOptions, ResolvedFailure } from \"./types.js\";\nimport { getConfig, resolveFailures } from \"./config.js\";\nimport { clearDenylist, clearDiskSpace } from \"./failures/index.js\";\nimport { error as logError } from \"./log.js\";\nimport { runPreHandlerInjections, runPostHandlerInjections } from \"./orchestration.js\";\n\ninterface MiddyRequest<TEvent = unknown, TResult = unknown> {\n event: TEvent;\n context: Context;\n response?: TResult;\n error?: Error;\n internal?: Record<string, unknown>;\n}\n\ninterface MiddyMiddleware<TEvent = unknown, TResult = unknown> {\n // eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- Middy before hook returns TResult | void\n before: (request: MiddyRequest<TEvent, TResult>) => Promise<TResult | void>;\n after: (request: MiddyRequest<TEvent, TResult>) => Promise<void>;\n onError: (request: MiddyRequest<TEvent, TResult>) => Promise<void>;\n}\n\nexport function failureLambdaMiddleware<TEvent = unknown, TResult = unknown>(\n options?: FailureLambdaOptions,\n): MiddyMiddleware<TEvent, TResult> {\n return {\n before: async (request) => {\n if (process.env.FAILURE_LAMBDA_DISABLED === \"true\") {\n return;\n }\n\n const configProvider = options?.configProvider ?? getConfig;\n const flagsConfig = await configProvider();\n const failures = resolveFailures(flagsConfig);\n\n // Store resolved failures for after phase\n request.internal = { ...request.internal, failureLambdaFailures: failures };\n\n const dryRun = options?.dryRun === true;\n\n const preResult = await runPreHandlerInjections<TEvent, TResult>(\n failures,\n request.event,\n request.context,\n dryRun,\n );\n if (preResult) {\n request.response = preResult.shortCircuit;\n return preResult.shortCircuit;\n }\n },\n after: async (request) => {\n const failures = (request.internal?.failureLambdaFailures ?? []) as ResolvedFailure[];\n\n const dryRun = options?.dryRun === true;\n\n request.response = runPostHandlerInjections(\n failures,\n request.event,\n request.response,\n dryRun,\n );\n },\n onError: async (request) => {\n logError({ action: \"error\", message: request.error?.message ?? \"unknown error\" });\n clearDenylist();\n clearDiskSpace();\n },\n };\n}\n","import { SSMClient, GetParameterCommand } from \"@aws-sdk/client-ssm\";\nimport type {\n FailureMode,\n FlagValue,\n FailureFlagsConfig,\n ResolvedFailure,\n CachedConfig,\n ConfigValidationError,\n} from \"./types.js\";\nimport { DEFAULT_FLAGS_CONFIG, FAILURE_MODE_ORDER } from \"./types.js\";\nimport { log, warn, error } from \"./log.js\";\n\nconst KNOWN_FLAGS: ReadonlySet<string> = new Set(FAILURE_MODE_ORDER);\n\nconst MAX_DISK_SPACE_MB = 10240;\nconst MAX_REGEX_LENGTH = 512;\n\n/**\n * Detect regex patterns vulnerable to catastrophic backtracking (ReDoS).\n * Checks for nested quantifiers like (a+)+, (a*)+, (a+)*, etc.\n * Also rejects overly long patterns as a general safety measure.\n */\nexport function isUnsafeRegex(pattern: string): boolean {\n if (pattern.length > MAX_REGEX_LENGTH) return true;\n\n let depth = 0;\n const hasQuantifierInGroup: boolean[] = [false];\n\n for (let i = 0; i < pattern.length; i++) {\n const ch = pattern[i];\n\n if (ch === \"\\\\\") {\n i++;\n continue;\n }\n\n if (ch === \"[\") {\n i++;\n while (i < pattern.length) {\n if (pattern[i] === \"\\\\\") i++;\n else if (pattern[i] === \"]\") break;\n i++;\n }\n continue;\n }\n\n if (ch === \"(\") {\n depth++;\n hasQuantifierInGroup[depth] = false;\n continue;\n }\n\n if (ch === \")\") {\n const groupHadQuantifier = hasQuantifierInGroup[depth] ?? false;\n depth = Math.max(0, depth - 1);\n\n if (groupHadQuantifier && i + 1 < pattern.length) {\n const next = pattern[i + 1];\n if (next === \"+\" || next === \"*\" || next === \"{\") {\n return true;\n }\n }\n if (depth > 0 && groupHadQuantifier) {\n hasQuantifierInGroup[depth] = true;\n }\n continue;\n }\n\n if (depth > 0 && (ch === \"+\" || ch === \"*\")) {\n hasQuantifierInGroup[depth] = true;\n }\n if (depth > 0 && ch === \"{\") {\n const rest = pattern.slice(i);\n if (/^\\{\\d+,/.test(rest)) {\n hasQuantifierInGroup[depth] = true;\n }\n }\n }\n\n return false;\n}\n\nconst DEFAULT_CACHE_TTL_SECONDS = 60;\n\n/** Module-level cache. Resets naturally on Lambda cold start. */\nlet configCache: CachedConfig | null = null;\n\n/** Lazy-initialized SSM client. Created on first use to avoid cold start penalty when using AppConfig. */\nlet ssmClient: SSMClient | null = null;\n\n/** Whether we've logged the config source yet (once per cold start) */\nlet hasLoggedSource = false;\n\nfunction getSSMClient(): SSMClient {\n if (ssmClient === null) {\n ssmClient = new SSMClient({});\n }\n return ssmClient;\n}\n\nfunction isAppConfigSource(): boolean {\n return Boolean(process.env.FAILURE_APPCONFIG_CONFIGURATION);\n}\n\nfunction getCacheTtlMs(): number {\n const envValue = process.env.FAILURE_CACHE_TTL;\n if (envValue === undefined || envValue === \"\") {\n // Auto-disable library cache for AppConfig — the extension already caches\n // at its poll interval (AWS_APPCONFIG_EXTENSION_POLL_INTERVAL_SECONDS).\n // Double-caching adds unnecessary staleness when changing config.\n if (isAppConfigSource()) {\n return 0;\n }\n return DEFAULT_CACHE_TTL_SECONDS * 1000;\n }\n const parsed = Number(envValue);\n if (Number.isNaN(parsed) || parsed < 0) {\n warn({ action: \"config\", message: `invalid FAILURE_CACHE_TTL=\"${envValue}\", using default ${DEFAULT_CACHE_TTL_SECONDS}s` });\n return DEFAULT_CACHE_TTL_SECONDS * 1000;\n }\n if (parsed > 0 && isAppConfigSource()) {\n warn({\n action: \"config\",\n message: `FAILURE_CACHE_TTL=${parsed}s with AppConfig — the AppConfig extension already caches at its poll interval; library caching adds staleness`,\n });\n }\n return parsed * 1000;\n}\n\nfunction isCacheValid(): boolean {\n if (configCache === null) {\n return false;\n }\n const ttlMs = getCacheTtlMs();\n if (ttlMs === 0) {\n return false;\n }\n return Date.now() - configCache.fetchedAt < ttlMs;\n}\n\n/** Validate a single flag value. Returns array of errors (empty = valid). */\nexport function validateFlagValue(\n mode: string,\n raw: Record<string, unknown>,\n): ConfigValidationError[] {\n const errors: ConfigValidationError[] = [];\n\n if (typeof raw.enabled !== \"boolean\") {\n errors.push({\n field: `${mode}.enabled`,\n message: \"must be a boolean\",\n value: raw.enabled,\n });\n }\n\n if (raw.percentage !== undefined) {\n if (typeof raw.percentage !== \"number\" || !Number.isInteger(raw.percentage) || raw.percentage < 0 || raw.percentage > 100) {\n errors.push({\n field: `${mode}.percentage`,\n message: \"must be an integer between 0 and 100\",\n value: raw.percentage,\n });\n }\n }\n\n if (mode === \"latency\") {\n if (raw.min_latency !== undefined) {\n if (typeof raw.min_latency !== \"number\" || raw.min_latency < 0) {\n errors.push({\n field: `${mode}.min_latency`,\n message: \"must be a non-negative number\",\n value: raw.min_latency,\n });\n }\n }\n if (raw.max_latency !== undefined) {\n if (typeof raw.max_latency !== \"number\" || raw.max_latency < 0) {\n errors.push({\n field: `${mode}.max_latency`,\n message: \"must be a non-negative number\",\n value: raw.max_latency,\n });\n }\n }\n if (\n typeof raw.min_latency === \"number\" &&\n typeof raw.max_latency === \"number\" &&\n raw.min_latency > raw.max_latency\n ) {\n errors.push({\n field: `${mode}.max_latency`,\n message: \"max_latency must be >= min_latency\",\n value: raw.max_latency,\n });\n }\n }\n\n if (mode === \"exception\") {\n if (raw.exception_msg !== undefined && typeof raw.exception_msg !== \"string\") {\n errors.push({\n field: `${mode}.exception_msg`,\n message: \"must be a string\",\n value: raw.exception_msg,\n });\n }\n }\n\n if (mode === \"statuscode\") {\n if (raw.status_code !== undefined) {\n if (typeof raw.status_code !== \"number\" || raw.status_code < 100 || raw.status_code > 599) {\n errors.push({\n field: `${mode}.status_code`,\n message: \"must be an HTTP status code (100-599)\",\n value: raw.status_code,\n });\n }\n }\n }\n\n if (mode === \"diskspace\") {\n if (raw.disk_space !== undefined) {\n if (typeof raw.disk_space !== \"number\" || raw.disk_space <= 0 || raw.disk_space > MAX_DISK_SPACE_MB) {\n errors.push({\n field: `${mode}.disk_space`,\n message: `must be between 1 and ${MAX_DISK_SPACE_MB} (MB)`,\n value: raw.disk_space,\n });\n }\n }\n }\n\n if (mode === \"denylist\") {\n if (raw.deny_list !== undefined) {\n if (\n !Array.isArray(raw.deny_list) ||\n !raw.deny_list.every((item: unknown) => typeof item === \"string\")\n ) {\n errors.push({\n field: `${mode}.deny_list`,\n message: \"must be an array of strings\",\n value: raw.deny_list,\n });\n } else {\n for (let i = 0; i < raw.deny_list.length; i++) {\n const pattern = raw.deny_list[i] as string;\n try {\n new RegExp(pattern);\n } catch {\n errors.push({\n field: `${mode}.deny_list[${i}]`,\n message: \"invalid regular expression\",\n value: pattern,\n });\n continue;\n }\n if (isUnsafeRegex(pattern)) {\n errors.push({\n field: `${mode}.deny_list[${i}]`,\n message: \"potentially unsafe pattern (nested quantifiers may cause excessive backtracking)\",\n value: pattern,\n });\n }\n }\n }\n }\n }\n\n if (mode === \"timeout\") {\n if (raw.timeout_buffer_ms !== undefined) {\n if (typeof raw.timeout_buffer_ms !== \"number\" || raw.timeout_buffer_ms < 0) {\n errors.push({\n field: `${mode}.timeout_buffer_ms`,\n message: \"must be a non-negative number\",\n value: raw.timeout_buffer_ms,\n });\n }\n }\n }\n\n if (mode === \"corruption\") {\n if (raw.body !== undefined && typeof raw.body !== \"string\") {\n errors.push({\n field: `${mode}.body`,\n message: \"must be a string\",\n value: raw.body,\n });\n }\n }\n\n if (raw.match !== undefined) {\n if (!Array.isArray(raw.match)) {\n errors.push({\n field: `${mode}.match`,\n message: \"must be an array of match condition objects\",\n value: raw.match,\n });\n } else {\n const VALID_OPERATORS = new Set([\"eq\", \"exists\", \"startsWith\", \"regex\"]);\n for (let i = 0; i < raw.match.length; i++) {\n const condition = raw.match[i] as unknown;\n if (typeof condition !== \"object\" || condition === null) {\n errors.push({\n field: `${mode}.match[${i}]`,\n message: \"must be an object with a string path field\",\n value: condition,\n });\n continue;\n }\n const cond = condition as Record<string, unknown>;\n if (typeof cond.path !== \"string\") {\n errors.push({\n field: `${mode}.match[${i}].path`,\n message: \"must be a string\",\n value: cond.path,\n });\n }\n const operator = (cond.operator as string) ?? \"eq\";\n if (cond.operator !== undefined && !VALID_OPERATORS.has(operator)) {\n errors.push({\n field: `${mode}.match[${i}].operator`,\n message: `must be one of: eq, exists, startsWith, regex`,\n value: cond.operator,\n });\n }\n if (operator !== \"exists\" && typeof cond.value !== \"string\") {\n errors.push({\n field: `${mode}.match[${i}].value`,\n message: \"must be a string (required for all operators except 'exists')\",\n value: cond.value,\n });\n }\n if (operator === \"regex\" && typeof cond.value === \"string\") {\n try {\n new RegExp(cond.value);\n } catch {\n errors.push({\n field: `${mode}.match[${i}].value`,\n message: \"invalid regular expression\",\n value: cond.value,\n });\n continue;\n }\n if (isUnsafeRegex(cond.value)) {\n errors.push({\n field: `${mode}.match[${i}].value`,\n message: \"potentially unsafe pattern (nested quantifiers may cause excessive backtracking)\",\n value: cond.value,\n });\n }\n }\n }\n }\n }\n\n return errors;\n}\n\n/** Parse raw JSON into FailureFlagsConfig. Validates each known flag key. */\nexport function parseFlags(raw: Record<string, unknown>): FailureFlagsConfig {\n if (\"isEnabled\" in raw || \"failureMode\" in raw) {\n warn({\n action: \"config\",\n message: \"detected 0.x configuration format — this version requires the v1.0 feature-flag format. See https://github.com/gunnargrosch/failure-lambda#migration-from-0x\",\n });\n }\n\n const config: FailureFlagsConfig = {};\n\n for (const key of Object.keys(raw)) {\n if (!KNOWN_FLAGS.has(key)) {\n continue;\n }\n\n const mode = key as FailureMode;\n const flagRaw = raw[mode];\n\n if (typeof flagRaw !== \"object\" || flagRaw === null || Array.isArray(flagRaw)) {\n warn({ action: \"config\", mode, message: \"must be an object, skipping\" });\n continue;\n }\n\n const flagObj = flagRaw as Record<string, unknown>;\n const validationErrors = validateFlagValue(mode, flagObj);\n\n if (validationErrors.length > 0) {\n for (const validationError of validationErrors) {\n warn({ action: \"config\", field: validationError.field, message: validationError.message, value: validationError.value });\n }\n warn({ action: \"config\", mode, message: \"skipping flag due to validation errors\" });\n continue;\n }\n\n config[mode] = flagObj as unknown as FlagValue;\n }\n\n return config;\n}\n\n/**\n * Resolve enabled flags into an ordered array of failures to inject.\n * Order: latency, diskspace, denylist (non-terminating), then statuscode, exception (terminating).\n * Defaults percentage to 100 when omitted.\n */\nexport function resolveFailures(config: FailureFlagsConfig): ResolvedFailure[] {\n const failures: ResolvedFailure[] = [];\n\n for (const mode of FAILURE_MODE_ORDER) {\n const flag = config[mode];\n if (flag === undefined || !flag.enabled) {\n continue;\n }\n\n failures.push({\n mode,\n percentage: Math.max(0, Math.min(100, flag.percentage ?? 100)),\n flag,\n });\n }\n\n return failures;\n}\n\nasync function fetchFromAppConfig(): Promise<FailureFlagsConfig> {\n const appConfigPort = process.env.AWS_APPCONFIG_EXTENSION_HTTP_PORT ?? \"2772\";\n const application = process.env.FAILURE_APPCONFIG_APPLICATION;\n const environment = process.env.FAILURE_APPCONFIG_ENVIRONMENT;\n const configuration = process.env.FAILURE_APPCONFIG_CONFIGURATION;\n\n const url =\n `http://localhost:${appConfigPort}` +\n `/applications/${application}` +\n `/environments/${environment}` +\n `/configurations/${configuration}`;\n\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(\n `AppConfig fetch failed: ${response.status} ${response.statusText}`\n );\n }\n\n const json = (await response.json()) as Record<string, unknown>;\n return parseFlags(json);\n}\n\nasync function fetchFromSSM(): Promise<FailureFlagsConfig> {\n const parameterName = process.env.FAILURE_INJECTION_PARAM as string;\n const client = getSSMClient();\n const command = new GetParameterCommand({ Name: parameterName });\n const response = await client.send(command);\n\n const rawValue = response.Parameter?.Value;\n if (rawValue === undefined) {\n throw new Error(`SSM parameter \"${parameterName}\" has no value`);\n }\n\n const json = JSON.parse(rawValue) as Record<string, unknown>;\n return parseFlags(json);\n}\n\n/** Fetch config from AppConfig or SSM, with caching. */\nexport async function getConfig(): Promise<FailureFlagsConfig> {\n if (isCacheValid() && configCache !== null) {\n return configCache.config;\n }\n\n try {\n let config: FailureFlagsConfig;\n let source: string;\n\n if (process.env.FAILURE_APPCONFIG_CONFIGURATION) {\n config = await fetchFromAppConfig();\n source = \"appconfig\";\n } else if (process.env.FAILURE_INJECTION_PARAM) {\n config = await fetchFromSSM();\n source = \"ssm\";\n } else {\n return { ...DEFAULT_FLAGS_CONFIG };\n }\n\n if (!hasLoggedSource) {\n const cacheTtlMs = getCacheTtlMs();\n log({\n action: \"config\",\n config_source: source,\n cache_ttl_seconds: cacheTtlMs / 1000,\n enabled_flags: Object.keys(config).filter(\n (k) => (config as Record<string, { enabled?: boolean }>)[k]?.enabled,\n ),\n });\n hasLoggedSource = true;\n }\n\n configCache = {\n config,\n fetchedAt: Date.now(),\n };\n\n return config;\n } catch (err) {\n error({ action: \"config\", message: \"error fetching config\", error: String(err) });\n return { ...DEFAULT_FLAGS_CONFIG };\n }\n}\n\n/** Clear the config cache. Useful for testing. @internal */\nexport function clearConfigCache(): void {\n configCache = null;\n hasLoggedSource = false;\n}\n\n/** Replace the SSM client instance. For testing with mocks. @internal */\nexport function setSSMClient(client: SSMClient): void {\n ssmClient = client;\n}\n","import type { Context, Callback } from \"aws-lambda\";\n\n/** The supported failure injection modes */\nexport type FailureMode =\n | \"latency\"\n | \"exception\"\n | \"statuscode\"\n | \"diskspace\"\n | \"denylist\"\n | \"timeout\"\n | \"corruption\";\n\n/**\n * Ordered list of all failure modes.\n * Non-terminating pre-handler first, then terminating, then post-handler (corruption) last.\n */\nexport const FAILURE_MODE_ORDER: readonly FailureMode[] = [\n \"latency\",\n \"timeout\",\n \"diskspace\",\n \"denylist\",\n \"statuscode\",\n \"exception\",\n \"corruption\",\n];\n\n/** Match operators for event-based targeting */\nexport type MatchOperator = \"eq\" | \"exists\" | \"startsWith\" | \"regex\";\n\n/** Condition for event-based targeting */\nexport interface MatchCondition {\n /** Dot-separated path into the event object (e.g. \"requestContext.http.method\") */\n path: string;\n /** Expected string value at the path. Required for all operators except \"exists\". */\n value?: string;\n /** Comparison operator. Defaults to \"eq\". */\n operator?: MatchOperator;\n}\n\n/** A single feature flag's value */\nexport interface FlagValue {\n enabled: boolean;\n /** Percentage of invocations to inject (0 to 100, integer). Defaults to 100. */\n percentage?: number;\n /** Minimum latency in ms (latency mode) */\n min_latency?: number;\n /** Maximum latency in ms (latency mode) */\n max_latency?: number;\n /** Error message to throw (exception mode) */\n exception_msg?: string;\n /** HTTP status code to return (statuscode mode) */\n status_code?: number;\n /** MB of disk to fill in /tmp (diskspace mode) */\n disk_space?: number;\n /** Array of regex patterns for hosts to block (denylist mode) */\n deny_list?: string[];\n /** Buffer in ms before Lambda timeout (timeout mode). Default: 0 */\n timeout_buffer_ms?: number;\n /** Replacement body string (corruption mode) */\n body?: string;\n /** Event-based targeting conditions. All conditions must match for the flag to fire. */\n match?: MatchCondition[];\n}\n\n/** The full config: a map of failure mode names to their flag values */\nexport type FailureFlagsConfig = Partial<Record<FailureMode, FlagValue>>;\n\n/** A failure resolved and ready to inject */\nexport interface ResolvedFailure {\n mode: FailureMode;\n percentage: number;\n flag: FlagValue;\n}\n\n/** Default: empty config, all modes disabled */\nexport const DEFAULT_FLAGS_CONFIG: FailureFlagsConfig = {};\n\n/**\n * Generic Lambda handler type. Intentionally broad to support any\n * event source (API Gateway, SQS, SNS, EventBridge, etc.).\n */\nexport type LambdaHandler<TEvent = unknown, TResult = unknown> = (\n event: TEvent,\n context: Context,\n callback: Callback<TResult>,\n // eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- Lambda handlers may return void or Promise\n) => void | Promise<TResult>;\n\n/** Options for the injectFailure wrapper */\nexport interface FailureLambdaOptions {\n /** Override the config source (useful for testing or custom config backends) */\n configProvider?: () => Promise<FailureFlagsConfig>;\n /** Log which failures would fire without actually injecting them */\n dryRun?: boolean;\n}\n\n/** Internal: cached config entry */\nexport interface CachedConfig {\n config: FailureFlagsConfig;\n fetchedAt: number;\n}\n\n/** Validation error detail */\nexport interface ConfigValidationError {\n field: string;\n message: string;\n value: unknown;\n}\n","const SOURCE = \"failure-lambda\";\n\nexport function log(data: Record<string, unknown>): void {\n console.log(JSON.stringify({ source: SOURCE, level: \"info\", ...data }));\n}\n\nexport function warn(data: Record<string, unknown>): void {\n console.warn(JSON.stringify({ source: SOURCE, level: \"warn\", ...data }));\n}\n\nexport function error(data: Record<string, unknown>): void {\n console.error(JSON.stringify({ source: SOURCE, level: \"error\", ...data }));\n}\n","import type { FlagValue } from \"../types.js\";\nimport { log } from \"../log.js\";\n\nexport async function injectLatency(flag: FlagValue): Promise<void> {\n const minLatency = flag.min_latency ?? 0;\n const maxLatency = flag.max_latency ?? 0;\n const latencyRange = Math.max(0, maxLatency - minLatency);\n const injectedLatency = Math.floor(minLatency + Math.random() * latencyRange);\n\n log({ mode: \"latency\", action: \"inject\", latency_ms: injectedLatency, min_latency: minLatency, max_latency: maxLatency });\n await new Promise<void>((resolve) => setTimeout(resolve, injectedLatency));\n}\n","import type { FlagValue } from \"../types.js\";\nimport { log } from \"../log.js\";\n\nexport function injectException(flag: FlagValue): never {\n const message = flag.exception_msg ?? \"Injected exception\";\n log({ mode: \"exception\", action: \"inject\", exception_msg: message });\n throw new Error(message);\n}\n","import type { FlagValue } from \"../types.js\";\nimport { log } from \"../log.js\";\n\nexport interface StatusCodeResponse {\n statusCode: number;\n headers: Record<string, string>;\n body: string;\n}\n\nexport function injectStatusCode(flag: FlagValue): StatusCodeResponse {\n const statusCode = flag.status_code ?? 500;\n log({ mode: \"statuscode\", action: \"inject\", status_code: statusCode });\n return {\n statusCode,\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ message: `Injected status code ${statusCode}` }),\n };\n}\n","import { spawnSync } from \"node:child_process\";\nimport { readdirSync, unlinkSync } from \"node:fs\";\nimport type { FlagValue } from \"../types.js\";\nimport { log, warn, error } from \"../log.js\";\n\nconst DISKSPACE_PREFIX = \"diskspace-failure-\";\n\n/** Fill /tmp with data using dd. Only works on Linux (Lambda runtime); not available on Windows/macOS. */\nexport function injectDiskSpace(flag: FlagValue): void {\n const diskSpaceMB = flag.disk_space ?? 100;\n log({ mode: \"diskspace\", action: \"inject\", disk_space_mb: diskSpaceMB });\n\n // bs=diskSpaceMB*1024 bytes per block, count=1024 blocks → diskSpaceMB * 1024 * 1024 = exact MB\n const result = spawnSync(\"dd\", [\n \"if=/dev/zero\",\n `of=/tmp/${DISKSPACE_PREFIX}${Date.now()}.tmp`,\n \"count=1024\",\n `bs=${diskSpaceMB * 1024}`,\n ]);\n\n if (result.error) {\n error({ mode: \"diskspace\", action: \"error\", message: result.error.message });\n } else if (result.status !== 0) {\n const stderr = result.stderr?.toString().trim();\n error({ mode: \"diskspace\", action: \"error\", message: `dd exited with status ${result.status}`, stderr });\n }\n}\n\n/** Remove diskspace failure files from /tmp. */\nexport function clearDiskSpace(): void {\n try {\n const files = readdirSync(\"/tmp\").filter((f) => f.startsWith(DISKSPACE_PREFIX));\n for (const file of files) {\n unlinkSync(`/tmp/${file}`);\n }\n if (files.length > 0) {\n log({ mode: \"diskspace\", action: \"clear\", files_removed: files.length });\n }\n } catch (e) {\n warn({ mode: \"diskspace\", action: \"clear_error\", message: (e as Error).message });\n }\n}\n","import dns from \"node:dns\";\nimport type { FlagValue } from \"../types.js\";\nimport { log, warn } from \"../log.js\";\n\n/** Capture the original dns.lookup once at module load (once per Lambda cold start) */\nconst originalLookup = dns.lookup;\n\n/** Whether our wrapper is currently installed on dns.lookup */\nlet isActive = false;\n\n/** Current compiled regex patterns (replaced on each injectDenylist call) */\nlet activePatterns: RegExp[] = [];\n\n/** Restore dns.lookup and clear denylist patterns */\nexport function clearDenylist(): void {\n if (isActive) {\n dns.lookup = originalLookup;\n isActive = false;\n activePatterns = [];\n }\n}\n\nexport function injectDenylist(flag: FlagValue): void {\n const denylistPatterns = flag.deny_list ?? [];\n log({ mode: \"denylist\", action: \"inject\", patterns: denylistPatterns });\n\n activePatterns = [];\n for (const pattern of denylistPatterns) {\n try {\n activePatterns.push(new RegExp(pattern));\n } catch (e) {\n warn({ mode: \"denylist\", action: \"error\", message: `invalid regex \"${pattern}\"`, error: (e as Error).message });\n }\n }\n\n if (!isActive) {\n // dns.lookup has multiple overloads: (hostname, cb), (hostname, options, cb).\n // The options.all flag changes the callback signature to return an array,\n // but we extract the callback generically as the last arg and pass through\n // to the original for non-blocked hosts. AWS SDK calls don't use all: true.\n dns.lookup = function blockedLookup(\n hostname: string,\n ...args: unknown[]\n ): void {\n const callback = args[args.length - 1] as (\n err: NodeJS.ErrnoException | null,\n address?: string,\n family?: number,\n ) => void;\n const rest = args.slice(0, -1);\n\n if (activePatterns.some((regex) => regex.test(hostname))) {\n log({ mode: \"denylist\", action: \"block\", hostname });\n const err = new Error(\n `getaddrinfo ENOTFOUND ${hostname}`,\n ) as NodeJS.ErrnoException & { hostname?: string };\n err.code = \"ENOTFOUND\";\n err.hostname = hostname;\n err.syscall = \"getaddrinfo\";\n process.nextTick(() => callback(err));\n return;\n }\n\n (originalLookup as (...args: unknown[]) => void).call(dns, hostname, ...rest, callback);\n } as typeof dns.lookup;\n\n isActive = true;\n }\n}\n\n/** Reset denylist state entirely. For testing. @internal */\nexport function resetDenylist(): void {\n dns.lookup = originalLookup;\n isActive = false;\n activePatterns = [];\n}\n","import type { Context } from \"aws-lambda\";\nimport type { FlagValue } from \"../types.js\";\nimport { log } from \"../log.js\";\n\nexport async function injectTimeout(flag: FlagValue, context: Context): Promise<void> {\n const bufferMs = flag.timeout_buffer_ms ?? 0;\n const remaining = context.getRemainingTimeInMillis();\n const sleepMs = Math.max(0, remaining - bufferMs);\n\n log({ mode: \"timeout\", action: \"inject\", sleep_ms: sleepMs, buffer_ms: bufferMs, remaining_ms: remaining });\n await new Promise<void>((resolve) => setTimeout(resolve, sleepMs));\n}\n","import type { FlagValue } from \"../types.js\";\nimport { log, warn } from \"../log.js\";\n\nexport function corruptResponse(flag: FlagValue, result: unknown): unknown {\n if (flag.body !== undefined) {\n log({ mode: \"corruption\", action: \"inject\", method: \"replace\" });\n if (typeof result === \"object\" && result !== null && \"body\" in result) {\n return { ...result, body: flag.body };\n }\n warn({ mode: \"corruption\", message: \"response has no body field; wrapping in { body }\" });\n return { body: flag.body };\n }\n\n log({ mode: \"corruption\", action: \"inject\", method: \"mangle\" });\n if (typeof result === \"object\" && result !== null && \"body\" in result) {\n const obj = result as Record<string, unknown>;\n if (typeof obj.body === \"string\") {\n return { ...obj, body: mangleString(obj.body) };\n }\n }\n\n warn({ mode: \"corruption\", message: \"response has no string body field to mangle; returning unchanged\" });\n return result;\n}\n\nfunction mangleString(input: string): string {\n if (input.length === 0) return input;\n const truncatePoint = Math.floor(input.length * (0.3 + Math.random() * 0.5));\n return input.slice(0, truncatePoint) + \"\\uFFFD\".repeat(3);\n}\n","import type { MatchCondition, MatchOperator } from \"./types.js\";\n\n/** Cache compiled regexes to avoid recompiling on every invocation */\nconst regexCache = new Map<string, RegExp>();\n\nfunction getCachedRegex(pattern: string): RegExp {\n let cached = regexCache.get(pattern);\n if (cached === undefined) {\n cached = new RegExp(pattern);\n regexCache.set(pattern, cached);\n }\n return cached;\n}\n\n/** Resolve a dot-separated path against a nested object */\nexport function getNestedValue(obj: unknown, path: string): unknown {\n let current = obj;\n for (const part of path.split(\".\")) {\n if (current === null || current === undefined || typeof current !== \"object\") return undefined;\n current = (current as Record<string, unknown>)[part];\n }\n return current;\n}\n\n/** Evaluate a single match operator against an actual value */\nfunction matchOperator(actual: unknown, operator: MatchOperator, value?: string): boolean {\n switch (operator) {\n case \"exists\":\n return actual !== null && actual !== undefined;\n case \"startsWith\":\n if (actual === null || actual === undefined) return false;\n return String(actual).startsWith(value ?? \"\");\n case \"regex\":\n if (actual === null || actual === undefined) return false;\n return getCachedRegex(value ?? \"\").test(String(actual));\n case \"eq\":\n default:\n if (actual === null || actual === undefined) return false;\n return String(actual) === value;\n }\n}\n\n/** Check whether all match conditions are satisfied by the event */\nexport function matchesConditions(event: unknown, conditions: MatchCondition[]): boolean {\n return conditions.every((condition) => {\n const actual = getNestedValue(event, condition.path);\n const operator = condition.operator ?? \"eq\";\n return matchOperator(actual, operator, condition.value);\n });\n}\n","import type { Context } from \"aws-lambda\";\nimport type { ResolvedFailure } from \"./types.js\";\nimport {\n injectLatency,\n injectException,\n injectStatusCode,\n injectDiskSpace,\n clearDiskSpace,\n injectDenylist,\n clearDenylist,\n injectTimeout,\n corruptResponse,\n} from \"./failures/index.js\";\nimport { matchesConditions } from \"./matching.js\";\nimport { log } from \"./log.js\";\n\nexport interface ShortCircuitResult<TResult = unknown> {\n shortCircuit: TResult;\n}\n\n/**\n * Run pre-handler failure injections.\n *\n * Returns `{ shortCircuit }` if a terminating mode (statuscode/exception) fires,\n * or `undefined` if the handler should proceed normally.\n */\nexport async function runPreHandlerInjections<TEvent = unknown, TResult = unknown>(\n failures: ResolvedFailure[],\n event: TEvent,\n context: Context,\n dryRun = false,\n): Promise<ShortCircuitResult<TResult> | undefined> {\n // Always clear previous invocation's side effects before re-evaluating.\n // Without this, a denylist/diskspace injection from a prior invocation persists\n // even when the percentage check fails on the current invocation.\n if (!dryRun) {\n clearDenylist();\n clearDiskSpace();\n }\n\n for (const failure of failures) {\n if (failure.mode === \"corruption\") continue;\n if (failure.flag.match && !matchesConditions(event, failure.flag.match)) continue;\n const roll = Math.random() * 100;\n if (roll >= failure.percentage) continue;\n\n if (dryRun) {\n log({ mode: failure.mode, action: \"dryrun\", percentage: failure.percentage, roll });\n continue;\n }\n\n switch (failure.mode) {\n case \"latency\":\n await injectLatency(failure.flag);\n break;\n case \"timeout\":\n await injectTimeout(failure.flag, context);\n break;\n case \"diskspace\":\n injectDiskSpace(failure.flag);\n break;\n case \"denylist\":\n injectDenylist(failure.flag);\n break;\n case \"statuscode\":\n return { shortCircuit: injectStatusCode(failure.flag) as unknown as TResult };\n case \"exception\":\n injectException(failure.flag);\n }\n }\n\n return undefined;\n}\n\n/**\n * Run post-handler failure injections (corruption).\n *\n * Returns the (potentially modified) result.\n */\nexport function runPostHandlerInjections<TEvent = unknown, TResult = unknown>(\n failures: ResolvedFailure[],\n event: TEvent,\n result: TResult,\n dryRun = false,\n): TResult {\n let current: unknown = result;\n\n for (const failure of failures) {\n if (failure.mode !== \"corruption\") continue;\n if (failure.flag.match && !matchesConditions(event, failure.flag.match)) continue;\n const roll = Math.random() * 100;\n if (roll >= failure.percentage) continue;\n\n if (dryRun) {\n log({ mode: failure.mode, action: \"dryrun\", percentage: failure.percentage, roll });\n continue;\n }\n\n current = corruptResponse(failure.flag, current);\n }\n\n return current as TResult;\n}\n"],"mappings":"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