CWE-547: Use of Hard-coded, Security-relevant Constants
Overview
Hard-coded paths, ports, IP addresses, and configuration values in source code create inflexibility, environment-specific failures, security issues when deploying across environments, and prevent configuration changes without recompilation.
OWASP Classification
A02:2025 - Security Misconfiguration
Risk
Medium: Hard-coded constants cause deployment failures (wrong paths/hosts), security issues (exposed internal IPs), inflexibility (can't change config), privilege escalation (assuming fixed paths), and maintenance burden.
Remediation Steps
Core principle: Do not hard-code security-relevant constants (keys, tokens, bypass flags); externalize to managed configuration/secrets.
Locate Hard-coded Security-Relevant Constants
When reviewing security scan results:
- Identify hard-coded values: Database hosts/ports, file paths, IP addresses, URLs, port numbers
- Check for platform-specific paths: Windows-only (C:\) or Unix-only (/var/) paths
- Find environment-specific values: Production IPs, internal hostnames hardcoded
- Look for security constants: Admin usernames, timeout values, encryption algorithms
- Search codebase: grep for IP addresses (\d+.\d+.\d+.\d+), absolute paths, port numbers
Common hard-coded patterns:
- Database:
DB_HOST = "192.168.1.100",PORT = 3306 - Paths:
LOG_PATH = "/var/log/app.log",UPLOAD_DIR = "C:\\uploads" - Network:
API_URL = "http://internal-api.company.local" - Security:
ADMIN_USER = "administrator",MAX_LOGIN_ATTEMPTS = 3
Use Configuration Files (Primary Defense)
# WRONG - hard-coded values
DB_HOST = "192.168.1.100"
DB_PORT = 3306
LOG_PATH = "/var/log/app/app.log"
UPLOAD_DIR = "C:\\uploads" # Windows-specific!
API_URL = "http://internal-server.local:8080"
MAX_ATTEMPTS = 5
# RIGHT - from configuration file
import os
from configparser import ConfigParser
config = ConfigParser()
config.read('app.config') # Or /etc/app/config.ini
DB_HOST = config.get('database', 'host')
DB_PORT = config.getint('database', 'port')
LOG_PATH = config.get('logging', 'path')
UPLOAD_DIR = config.get('storage', 'upload_dir')
API_URL = config.get('api', 'base_url')
MAX_ATTEMPTS = config.getint('security', 'max_login_attempts')
Configuration file (app.config):
[database]
host = db.production.local
port = 5432
name = prod_db
[logging]
path = /var/log/myapp/app.log
level = INFO
[storage]
upload_dir = /mnt/uploads
max_file_size = 10485760
[api]
base_url = https://api.production.local
timeout = 30
[security]
max_login_attempts = 5
session_timeout = 3600
Why this works: Configuration files separate environment-specific values from code, allowing deployment to different environments without code changes. Different config files for dev/staging/production.
Use Environment Variables for Sensitive/Dynamic Values
import os
# Read from environment with defaults
DB_HOST = os.getenv('DB_HOST', 'localhost')
DB_PORT = int(os.getenv('DB_PORT', '5432'))
DB_NAME = os.getenv('DB_NAME', 'myapp')
# Required variables (no default)
API_KEY = os.getenv('API_KEY')
if not API_KEY:
raise ValueError("API_KEY environment variable is required")
# Secret values MUST be in environment, never in config files
DB_PASSWORD = os.getenv('DB_PASSWORD')
JWT_SECRET = os.getenv('JWT_SECRET_KEY')
Docker/container deployment:
services:
app:
image: myapp:latest
environment:
DB_HOST: db.production.local
DB_PORT: 5432
DB_NAME: prod_db
LOG_LEVEL: INFO
env_file:
- secrets.env # DB_PASSWORD, API_KEY, JWT_SECRET
Kubernetes deployment:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DB_HOST: "db.production.local"
DB_PORT: "5432"
LOG_LEVEL: "INFO"
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
data:
DB_PASSWORD: <base64-encoded>
API_KEY: <base64-encoded>
Use Platform-Independent Paths
import os
from pathlib import Path
# WRONG - platform-specific paths
data_dir = "C:\\Program Files\\App\\data" # Windows only!
log_file = "/var/log/app.log" # Unix only!
temp_file = "C:\\temp\\upload.tmp" # Won't work on Linux!
# RIGHT - platform-independent
# Use os.path or pathlib.Path
data_dir = os.path.join(os.getenv('APPDATA', '/var/lib'), 'app', 'data')
log_file = Path.home() / '.app' / 'logs' / 'app.log'
temp_file = Path(tempfile.gettempdir()) / 'upload.tmp'
# Better - use standard directory locations
from appdirs import user_data_dir, user_log_dir, user_cache_dir
data_dir = user_data_dir('myapp', 'company') # ~/Library/Application Support/myapp on macOS
log_dir = user_log_dir('myapp', 'company') # ~/Library/Logs/myapp on macOS
cache_dir = user_cache_dir('myapp', 'company') # ~/.cache/myapp on Linux
Java example:
// WRONG
String dataDir = "C:\\\\Users\\\\Public\\\\AppData";
// RIGHT - platform-independent
String userHome = System.getProperty("user.home");
Path dataDir = Paths.get(userHome, ".myapp", "data");
Files.createDirectories(dataDir); // Create if doesn't exist
Environment-Based Configuration Management
database:
host: localhost
port: 5432
name: dev_db
logging:
level: DEBUG
path: ./logs/app.log
storage:
upload_path: ./uploads
max_size: 10485760 # 10 MB
api:
base_url: http://localhost:8080
timeout: 5
database:
host: ${DB_HOST} # From environment
port: ${DB_PORT}
name: prod_db
logging:
level: WARN
path: /var/log/myapp/app.log
storage:
upload_path: /mnt/uploads
max_size: 104857600 # 100 MB
api:
base_url: https://api.production.company.com
timeout: 30
Load configuration based on environment:
import os
import yaml
env = os.getenv('APP_ENV', 'development')
config_file = f'config/{env}.yml'
with open(config_file) as f:
config = yaml.safe_load(f)
# Substitute environment variables in config
import re
def substitute_env_vars(obj):
if isinstance(obj, dict):
return {k: substitute_env_vars(v) for k, v in obj.items()}
elif isinstance(obj, str):
# Replace ${VAR_NAME} with environment variable
return re.sub(r'\$\{([^}]+)\}', lambda m: os.getenv(m.group(1), ''), obj)
return obj
config = substitute_env_vars(config)
Test and Verify Configuration Flexibility
Configuration validation test:
import pytest
def test_no_hardcoded_ips():
"""Ensure no hard-coded IP addresses in config classes"""
import inspect
import re
source = inspect.getsource(Config)
# Find IP address patterns
ip_pattern = r'\b(?:\d{1,3}\.){3}\d{1,3}\b'
matches = re.findall(ip_pattern, source)
assert len(matches) == 0, f"Found hard-coded IPs: {matches}"
def test_config_from_environment():
"""Test configuration loads from environment"""
os.environ['DB_HOST'] = 'test.local'
os.environ['DB_PORT'] = '9999'
config = load_config()
assert config['database']['host'] == 'test.local'
assert config['database']['port'] == 9999
def test_platform_independence():
"""Test paths work on both Windows and Unix"""
log_path = get_log_path()
# Should be valid Path object on any platform
assert isinstance(log_path, Path)
assert log_path.is_absolute() or log_path.is_relative_to('.')
Deployment verification:
- Deploy to test environment with different config
- Verify application connects to correct database/services
- Check logs written to configured location
- Test with environment variables set
Common Hard-coded Constants
Paths:
- File paths: /var/log/app.log
- Directories: C:\Program Files
- Temp directories: /tmp
- Config locations: /etc/app/config
Network:
- IP addresses: 192.168.1.100
- Ports: 8080, 3306
- Hostnames: internal-server.local
- URLs:
http://api.internal.com
System:
- User names: root, admin
- Process IDs
- Registry keys (Windows)
- File descriptors
Vulnerable Examples
// Hard-coded paths
public class Config {
public static final String LOG_DIR = "/var/log/myapp";
public static final String DATA_DIR = "C:\\\\data"; // Windows only!
public static final String DB_HOST = "10.0.1.50";
}
// Hard-coded ports
ServerSocket server = new ServerSocket(8080); // Can't change!
// Hard-coded credentials
String adminUser = "administrator";
if (user.equals(adminUser)) {
grantAdmin();
}
Secure Patterns
Configuration from Environment Variables
class Config:
def __init__(self):
self.db_host = os.getenv('DB_HOST', 'localhost')
self.db_port = int(os.getenv('DB_PORT', '5432'))
self.log_dir = Path(os.getenv('LOG_DIR', '/var/log/app'))
def validate(self):
"""Ensure configuration is valid"""
if not self.log_dir.exists():
self.log_dir.mkdir(parents=True)
config = Config()
config.validate()
Why this works:
- Separation of concerns:
os.getenv()separates config from code - same binary runs in dev/staging/prod without recompilation; DevOps changes config without touching source - Secure defaults: Fallback values (
'localhost','5432') provide fail-safe behavior for missing variables instead of crashes - Platform independence:
Pathobjects handle Windows (C:\logs), Linux (/var/log), macOS without hard-coded separators - Graceful handling: Validation logic creates missing directories (
mkdir(parents=True)) and converts types (int()for port) - Infrastructure-as-code: Enables twelve-factor app compliance where config varies between deploys but code doesn't (Kubernetes, AWS Parameter Store)
Security Checklist
- No hard-coded IP addresses in code
- No hard-coded file paths (except relative to config location)
- No platform-specific paths (no C:\ or /var/ in code)
- All environment-specific values in config files or environment
- Application works on Windows and Linux without code changes
- Can deploy to different environments (dev/staging/prod) with config change only