Skip to content

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:

docker-compose.yml
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

config/development.yml
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
config/production.yml
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: Path objects 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

Additional Resources