Environment Variables Containing Secrets Logged in Python Applications

High Risk Secrets Exposure
pythonloggingenvironment-variablessecretsdebug-loggingerror-handlingmonitoring

What it is

A critical vulnerability where Python applications inadvertently log environment variables containing sensitive information such as database passwords, API keys, and authentication tokens. This commonly occurs through debug logging, error handling, or system diagnostics that dump environment variables to log files, console output, or monitoring systems.

# settings.py - VULNERABLE Django logging configuration
import os
import logging.config

# Dangerous: Logging all environment variables during startup
DEBUG = True  # Often accidentally left as True in production

if DEBUG:
    print("=== DEBUG MODE ENVIRONMENT VARIABLES ===")
    for key, value in os.environ.items():
        print(f"{key}={value}")  # Exposes SECRET_KEY, DB passwords, etc!

# Vulnerable logging configuration
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'file': {
            'level': 'DEBUG',
            'class': 'logging.FileHandler',
            'filename': '/var/log/django/debug.log',
        },
    },
    'root': {
        'handlers': ['file'],
        'level': 'DEBUG',
    },
}

# views.py - Vulnerable view that logs sensitive data
from django.http import JsonResponse
from django.conf import settings
import logging

logger = logging.getLogger(__name__)

def debug_info(request):
    if settings.DEBUG:
        # VULNERABLE: Logging entire environment
        logger.debug(f"Environment dump: {dict(os.environ)}")
        
        debug_data = {
            'environment': dict(os.environ),  # Contains Django SECRET_KEY!
            'settings': {
                'SECRET_KEY': settings.SECRET_KEY,  # Exposed!
                'DATABASE_URL': os.environ.get('DATABASE_URL'),
                'REDIS_URL': os.environ.get('REDIS_URL')
            }
        }
        return JsonResponse(debug_data)
    
    return JsonResponse({'error': 'Debug mode disabled'})
# settings.py - SECURE Django logging configuration
import os
import re

# Pattern to identify sensitive variables
SENSITIVE_PATTERN = re.compile(r'.*(PASSWORD|SECRET|KEY|TOKEN|URL|CREDENTIAL).*', re.IGNORECASE)

def log_safe_startup_info():
    """Log only safe configuration information during startup"""
    if os.environ.get('DJANGO_DEBUG_STARTUP') == 'true':
        safe_vars = {}
        for key, value in os.environ.items():
            if SENSITIVE_PATTERN.match(key):
                safe_vars[key] = '[REDACTED]'
            else:
                safe_vars[key] = value
        
        print("=== SAFE STARTUP CONFIGURATION ===")
        for key, value in safe_vars.items():
            print(f"{key}={value}")

# Call safe startup logging
log_safe_startup_info()

# views.py - Secure debug view
from django.http import JsonResponse
from django.contrib.admin.views.decorators import staff_member_required
import logging

logger = logging.getLogger(__name__)

@staff_member_required
def system_info(request):
    """Secure system information endpoint"""
    
    # Redact sensitive environment variables
    safe_env = {}
    for key, value in os.environ.items():
        if SENSITIVE_PATTERN.match(key):
            safe_env[key] = '[REDACTED]'
        else:
            safe_env[key] = value
    
    safe_info = {
        'debug_mode': settings.DEBUG,
        'environment': safe_env
    }
    
    logger.info(f"System info accessed by: {request.user.username}")
    return JsonResponse(safe_info)

💡 Why This Fix Works

The vulnerable Django application exposes sensitive environment variables through debug logging and admin views. The secure version implements filtering, whitelisting of safe variables, and proper access controls.

# app.py - VULNERABLE Flask application with environment exposure
from flask import Flask, jsonify, request
import os
import logging
import traceback
from sqlalchemy import create_engine
from sqlalchemy.exc import SQLAlchemyError

app = Flask(__name__)

# Vulnerable: Debug mode logging all environment
if app.debug:
    app.logger.setLevel(logging.DEBUG)
    app.logger.debug("Starting application with environment:")
    for key, value in os.environ.items():
        app.logger.debug(f"{key}={value}")  # Exposes DB_URL, SECRET_KEY, etc!

# Database connection with vulnerable error handling
def get_database_connection():
    try:
        engine = create_engine(os.environ['DATABASE_URL'])
        return engine.connect()
    except Exception as e:
        # VULNERABLE: Exposing database URL in error
        error_msg = f"Database connection failed: {e}\nDatabase URL: {os.environ.get('DATABASE_URL')}"
        app.logger.error(error_msg)
        raise

@app.errorhandler(500)
def handle_server_error(error):
    # VULNERABLE: Error handler that exposes environment
    error_details = {
        'error': str(error),
        'traceback': traceback.format_exc(),
        'environment': dict(os.environ),  # Exposes all secrets!
        'request_data': {
            'method': request.method,
            'url': request.url,
            'headers': dict(request.headers)
        }
    }
    
    app.logger.error(f"Server error with context: {error_details}")
    
    if app.debug:
        return jsonify(error_details), 500
    
    return jsonify({'error': 'Internal server error'}), 500

@app.route('/health')
def health_check():
    # VULNERABLE: Health check exposing sensitive config
    return jsonify({
        'status': 'healthy',
        'config': dict(os.environ),  # All environment variables exposed!
        'database_url': os.environ.get('DATABASE_URL')
    })
# app.py - SECURE Flask application with proper secret handling
from flask import Flask, jsonify
import os
import re
from sqlalchemy import create_engine

app = Flask(__name__)

# Sensitive data patterns
SENSITIVE_PATTERN = re.compile(r'.*(PASSWORD|SECRET|KEY|TOKEN|URL|CREDENTIAL).*', re.IGNORECASE)

def log_safe_startup_config():
    """Log only safe configuration during startup"""
    safe_config = {}
    for key, value in os.environ.items():
        if SENSITIVE_PATTERN.match(key):
            safe_config[key] = '[REDACTED]'
        else:
            safe_config[key] = value
    
    app.logger.info(f"Application started with config: {safe_config}")

log_safe_startup_config()

# Secure database connection with proper error handling
def get_database_connection():
    try:
        engine = create_engine(os.environ['DATABASE_URL'])
        return engine.connect()
    except Exception as e:
        # SECURE: Don't expose database URL in errors
        app.logger.error(f"Database connection failed: {type(e).__name__}")
        raise Exception("Database connection failed - check logs for details")

@app.errorhandler(500)
def handle_server_error(error):
    # SECURE: Error handler without sensitive data exposure
    app.logger.error(f"Server error: {type(error).__name__}")
    return jsonify({'error': 'Internal server error'}), 500

@app.route('/health')
def health_check():
    # SECURE: Health check with only safe information
    return jsonify({
        'status': 'healthy',
        'version': os.environ.get('APP_VERSION', 'Unknown')
    })

💡 Why This Fix Works

The vulnerable Flask application logs and exposes environment variables containing secrets through various mechanisms. The secure version implements proper filtering, secure error handling, and admin-only access to configuration information.

Why it happens

Developers often log entire environment dictionaries during debugging or application startup to troubleshoot configuration issues. This practice exposes all environment variables, including sensitive secrets, to log files and monitoring systems. Debug logs that are accidentally left enabled in production amplify this risk.

Root causes

Debug Logging of Environment Variables

Developers often log entire environment dictionaries during debugging or application startup to troubleshoot configuration issues. This practice exposes all environment variables, including sensitive secrets, to log files and monitoring systems. Debug logs that are accidentally left enabled in production amplify this risk.

Preview example – PYTHON
import os
import logging

# VULNERABLE: Logging all environment variables
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

def startup_diagnostics():
    logger.debug("Application environment:")
    for key, value in os.environ.items():
        logger.debug(f"{key}={value}")  # Exposes DB_PASSWORD, API_KEYS, etc!
    
    # Also vulnerable - logging entire environ dict
    logger.debug(f"Full environment: {dict(os.environ)}")

Exception Handling That Exposes Environment Context

Error handling code that includes environment variables in exception messages or stack traces can inadvertently expose secrets. This is particularly common when applications log configuration errors or connection failures that include environment variable values in the error details.

Preview example – PYTHON
import os
import psycopg2

def connect_to_database():
    try:
        connection = psycopg2.connect(
            host=os.environ['DB_HOST'],
            database=os.environ['DB_NAME'],
            user=os.environ['DB_USER'],
            password=os.environ['DB_PASSWORD']
        )
        return connection
    except Exception as e:
        # VULNERABLE: Exception includes environment variables
        error_msg = f"Database connection failed with config: {dict(os.environ)}"
        logging.error(error_msg)
        raise Exception(error_msg)

System Information Collection for Monitoring

Applications that collect system information for health checks, monitoring, or diagnostics often include environment variables in their reports. These reports may be sent to external monitoring services, logged to files, or displayed in admin dashboards, potentially exposing sensitive credentials to unauthorized personnel.

Preview example – PYTHON
import os
import json
from flask import jsonify

@app.route('/admin/system-info')
def system_info():
    # VULNERABLE: Exposing environment in system info endpoint
    system_data = {
        'timestamp': datetime.now().isoformat(),
        'python_version': sys.version,
        'environment': dict(os.environ),  # Contains secrets!
        'working_directory': os.getcwd(),
        'process_id': os.getpid()
    }
    
    # This data might be logged or sent to monitoring services
    logging.info(f"System info requested: {json.dumps(system_data)}")
    return jsonify(system_data)

Fixes

1

Implement Selective Environment Variable Logging

Create a whitelist of safe environment variables that can be logged and explicitly filter out sensitive ones. Use structured logging with specific fields rather than dumping entire environment dictionaries. Never log variables with names containing 'PASSWORD', 'SECRET', 'KEY', 'TOKEN', or similar sensitive indicators.

View implementation – PYTHON
import os
import logging
import re

# Safe environment variables that can be logged
SAFE_ENV_VARS = {
    'NODE_ENV', 'PORT', 'HOST', 'LOG_LEVEL', 'APP_NAME', 'VERSION',
    'REGION', 'ENVIRONMENT', 'DEPLOYMENT_ID'
}

# Pattern to identify sensitive variables
SENSITIVE_PATTERNS = re.compile(r'.*(PASSWORD|SECRET|KEY|TOKEN|CREDENTIAL|AUTH).*', re.IGNORECASE)

def log_safe_environment():
    logger = logging.getLogger(__name__)
    
    safe_config = {}
    for key, value in os.environ.items():
        if key in SAFE_ENV_VARS and not SENSITIVE_PATTERNS.match(key):
            safe_config[key] = value
        elif SENSITIVE_PATTERNS.match(key):
            safe_config[key] = '[REDACTED]'
    
    logger.info(f"Application configuration: {safe_config}")
2

Secure Exception Handling and Error Logging

Implement exception handling that excludes sensitive environment variables from error messages and logs. Create sanitized error messages that provide useful debugging information without exposing secrets. Use structured exception handling with proper error context that doesn't include raw environment data.

View implementation – PYTHON
import os
import psycopg2
import logging
from typing import Dict, Any

def get_safe_db_config() -> Dict[str, Any]:
    """Return database configuration with sensitive values redacted for logging"""
    return {
        'host': os.environ.get('DB_HOST', 'not_set'),
        'database': os.environ.get('DB_NAME', 'not_set'),
        'user': os.environ.get('DB_USER', 'not_set'),
        'password': '[REDACTED]' if os.environ.get('DB_PASSWORD') else 'not_set',
        'port': os.environ.get('DB_PORT', '5432')
    }

def connect_to_database():
    try:
        connection = psycopg2.connect(
            host=os.environ['DB_HOST'],
            database=os.environ['DB_NAME'],
            user=os.environ['DB_USER'],
            password=os.environ['DB_PASSWORD']
        )
        return connection
    except psycopg2.Error as e:
        # SECURE: Log config without sensitive values
        safe_config = get_safe_db_config()
        error_msg = f"Database connection failed with config: {safe_config}"
        logging.error(error_msg)
        logging.error(f"Database error: {str(e)}")
        raise Exception("Database connection failed - check logs for details")
3

Secure System Information and Health Check Endpoints

Create system information endpoints that exclude sensitive environment variables and provide only necessary operational data. Implement proper access controls for diagnostic endpoints and ensure they never expose credentials even to authorized users. Use dedicated health check configurations that don't rely on environment dumping.

View implementation – PYTHON
import os
import json
from flask import jsonify
from functools import wraps

# Define what system info is safe to expose
SAFE_SYSTEM_INFO = {
    'APP_NAME', 'VERSION', 'ENVIRONMENT', 'PORT', 'LOG_LEVEL',
    'REGION', 'DEPLOYMENT_ID', 'BUILD_TIMESTAMP'
}

def require_admin_auth(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        # Implement proper authentication
        if not is_admin_authenticated():
            return jsonify({'error': 'Unauthorized'}), 401
        return f(*args, **kwargs)
    return decorated_function

@app.route('/admin/health')
@require_admin_auth
def health_check():
    # SECURE: Only expose safe system information
    health_data = {
        'status': 'healthy',
        'timestamp': datetime.now().isoformat(),
        'python_version': sys.version_info[:2],
        'process_id': os.getpid(),
        'uptime': get_uptime_seconds()
    }
    
    # Add safe environment variables only
    config = {}
    for key in SAFE_SYSTEM_INFO:
        if key in os.environ:
            config[key] = os.environ[key]
    
    health_data['configuration'] = config
    
    # Log health check access (without sensitive data)
    logging.info(f"Health check accessed by admin")
    return jsonify(health_data)

Detect This Vulnerability in Your Code

Sourcery automatically identifies environment variables containing secrets logged in python applications and many other security issues in your codebase.