Liberándose de los valores predefinidos

Liberándose de los valores codificados: Estrategias más inteligentes para el software moderno

Los valores codificados directamente en el código parecen atajos. Un desarrollador necesita la URL de una base de datos, la introduce directamente en la cadena de conexión y continúa. Tres meses después, la URL cambia, el cambio se refleja en un archivo pero no en otros cuatro, y la producción se interrumpe a las 2 de la madrugada. Otro desarrollador necesita una clave API, la introduce en el archivo fuente, realiza la confirmación y la envía. Seis meses después, el repositorio se clona, ​​la clave aparece en una bifurcación pública y la cuenta se ve comprometida antes de que nadie se dé cuenta. Estos no son casos excepcionales. Son la trayectoria habitual de los valores codificados directamente en el código en los sistemas de software: atajos aparentemente inofensivos que se acumulan generando deuda técnica, incidentes de seguridad y fallos en la implementación.

Encontrar y eliminar valores codificados

Eliminar y deshacerse de los valores codificados

Aprende más

La solución no es complicada. Las variables de entorno, los archivos de configuración, la inyección de dependencias y las constantes centralizadas son patrones bien conocidos y disponibles en todos los lenguajes y frameworks principales. El reto consiste en aplicarlos sistemáticamente en los códigos existentes, garantizar su uso en el código nuevo y desarrollar la disciplina necesaria para detectarlos antes de que lleguen a producción. A continuación, se describen todas las técnicas necesarias para lograrlo, con ejemplos de código funcionales para Python, Java y JavaScript.

¿Qué es un valor codificado?

Un valor fijo es una constante literal integrada directamente en el código fuente, en lugar de proporcionarse mediante configuración, variables de entorno, una base de datos o entrada en tiempo de ejecución. Su característica principal es la ausencia de indirección: si cambiar el valor requiere modificar el código fuente, recompilar o volver a desplegar la aplicación, se trata de un valor fijo.

Ejemplos comunes:

  • Una cadena de conexión a la base de datos escrita directamente en una clase de configuración.
  • Una clave API o token de acceso en un archivo fuente
  • Una URL de servicio que apunta a un entorno específico
  • Un umbral numérico (if (amount > 5000)) sin explicación ni fuente externa
  • Una ruta de archivo que asume una configuración de servidor específica.
  • Una cadena de rol de usuario ("admin") dispersos en controles de autorización

Los valores codificados directamente aparecen en todos los lenguajes y bases de código. Las aplicaciones heredadas de mainframe codifican nombres de conjuntos de datos específicos del entorno, códigos de región y umbrales de negocio directamente en programas COBOL. Los microservicios modernos acumulan valores de tiempo de espera, recuentos de reintentos y URL de descubrimiento de servicios codificados directamente en las clases de la aplicación. La forma cambia con el lenguaje; el problema es el mismo.

Valores fijos frente a constantes: ¿Cuál es la diferencia?

Los valores codificados a menudo se confunden con constantes, pero la distinción es importante. Una constante representa un hecho estable, intencional y propio de un dominio, que es correcto por definición y es poco probable que cambie: Math.PI, HTTP_STATUS_OK = 200, MAX_RETRY_ATTEMPTS = 3 (si tres es realmente correcto para todos los contextos). Las constantes son apropiadas en el código. Aportan claridad, evitan errores tipográficos y explicitan la intención.

Un valor fijo codifica una suposición sobre el contexto de implementación, la infraestructura o las reglas de negocio que se espera que varíen. Una URL de base de datos de producción, una clave API, una tasa impositiva específica de una región, el estado de un indicador de características: estos son valores fijos que se hacen pasar por constantes. La prueba es sencilla: si el valor debe ser diferente en un entorno, cliente o versión distintos, no es una constante y no debería estar en el código fuente.

¿Qué es la codificación flexible?

La codificación flexible consiste en hacer que los valores sean configurables en tiempo de ejecución, en lugar de fijos en tiempo de compilación. Un valor codificado flexible se puede modificar mediante un archivo de configuración, una variable de entorno, una entrada en la base de datos o una interfaz administrativa, sin modificar el código fuente ni volver a implementar la aplicación. La codificación flexible es el enfoque adecuado para configuraciones específicas del entorno, parámetros de negocio, indicadores de características y cualquier valor que pueda diferir entre los entornos de desarrollo, preproducción y producción.

La diferencia entre la codificación rígida y la codificación flexible radica en que, para actualizar una tasa impositiva, un sistema requiere modificar el código y desplegarlo, mientras que un miembro del equipo de operaciones puede actualizarlo mediante un archivo de configuración y reiniciar el servicio. A gran escala, esta diferencia representa miles de horas de trabajo de ingeniería y un riesgo operativo significativo.

Por qué codificar de forma rígida es una mala práctica

Destruye la mantenibilidad

Los valores codificados se multiplican. Una URL de servicio codificada en un archivo termina codificada en cinco archivos cuando se copia el módulo para una nueva integración. Un número mágico que aparece en un cálculo aparece en tres cuando se replica la misma lógica en contextos ligeramente diferentes. Cada instancia supone una obligación de mantenimiento independiente: encontrarla, actualizarla, probar la actualización y esperar que no se haya omitido ninguna.

El problema de la mantenibilidad no se limita al esfuerzo, sino que implica un riesgo. Realizar una búsqueda y reemplazo en todo el código fuente para actualizar un valor codificado es una operación de refactorización con impacto en la producción. Cualquier instancia omitida es un error. Cualquier reemplazo incorrecto es un error. La única forma de garantizar que el cambio se haya completado es mediante pruebas automatizadas que fallen si queda alguna instancia, pero precisamente las pruebas automatizadas son lo que dificulta la escritura de valores codificados.

Bloquea las pruebas y la integración y entrega continuas (CI/CD).

Las pruebas automatizadas deben ejecutarse en entornos controlados. Una prueba que se conecta a una base de datos de producción porque la cadena de conexión está codificada no es una prueba; es una operación de producción que se ejecuta durante la compilación. Una canalización de integración continua que falla porque una URL de servicio codificada es inaccesible desde el servidor de compilación no es un problema de infraestructura de pruebas; es un problema de codificación.

Las variables de entorno y la inyección de configuración son fundamentales para que el mismo conjunto de pruebas se pueda ejecutar en entornos de desarrollo local, integración continua, preproducción y producción con diferentes servicios de soporte. Sin ellas, las pruebas se vuelven específicas del entorno, inestables y, finalmente, se abandonan.

Crea graves vulnerabilidades de seguridad.

Las credenciales codificadas son una de las categorías de vulnerabilidad más comunes y explotadas en la seguridad del software. Una clave API, contraseña de base de datos o token de acceso codificado y almacenado en el control de versiones permanece en el historial del repositorio incluso después de ser eliminado de la rama actual. Los escáneres automatizados buscan continuamente estos patrones en los repositorios públicos. Una clave encontrada en una confirmación de hace dieciocho meses sigue siendo válida si nunca se ha rotado.

OWASP identifica explícitamente las contraseñas codificadas como una vulnerabilidad de seguridad crítica (CWE-259, CWE-798). Las directrices del NIST exigen que las credenciales nunca se almacenen en el código fuente. A pesar de ello, las filtraciones de credenciales mediante valores codificados siguen siendo una de las causas más frecuentes de incidentes de seguridad en la nube.

El riesgo va más allá de las credenciales. Las URL de servicios internos codificadas revelan la topología de la infraestructura. Las cadenas de roles de usuario codificadas revelan la lógica de autorización. Los umbrales de validación codificados pueden eludirse una vez que un atacante conoce sus valores exactos. Ninguno de estos riesgos de seguridad resulta evidente de inmediato, por lo que se acumulan sin ser abordados.

Credenciales y claves API codificadas: la categoría de mayor riesgo.

Los secretos codificados directamente en el código merecen un tratamiento aparte, ya que las consecuencias de un error en su codificación son fundamentalmente distintas a las de otros problemas de codificación. Un valor de tiempo de espera codificado directamente en el código provoca un error. Una clave API codificada directamente en el código provoca una brecha de seguridad.

Aspecto de las credenciales codificadas

pitón

# Python -- hardcoded database credentials (never do this)
connection = psycopg2.connect(
    host="prod-db.internal.example.com",
    database="accounts",
    user="app_service",
    password="s3cr3tP@ssword!"   # hardcoded secret in source code
)

# Python -- correct approach: load from environment
import os
connection = psycopg2.connect(
    host=os.environ["DB_HOST"],
    database=os.environ["DB_NAME"],
    user=os.environ["DB_USER"],
    password=os.environ["DB_PASSWORD"]
)

Java

// Java -- hardcoded API key (never do this)
private static final String API_KEY = "sk-prod-a1b2c3d4e5f6g7h8i9j0";

// Java -- correct approach: load from environment
private static final String API_KEY = System.getenv("OPENAI_API_KEY");

Cómo solucionar el problema de las credenciales codificadas

Las variables de entorno son la solución estándar para los secretos en las aplicaciones desplegadas. El secreto se configura en el entorno de despliegue (servidor, contenedor, secreto de Kubernetes o gestor de secretos en la nube) y se accede a él en tiempo de ejecución. El código fuente no contiene el valor del secreto, solo el nombre de la clave que se utiliza para recuperarlo.

Los servicios de gestión de secretos , como AWS Secrets Manager, HashiCorp Vault, Azure Key Vault y Google Secret Manager, constituyen la solución de nivel de producción para la rotación, auditoría y distribución de secretos entre servicios. La aplicación recupera el secreto al iniciarse (o bajo demanda) desde el almacén, en lugar de desde una variable de entorno, lo que permite la rotación sin necesidad de redistribución y un registro de auditoría completo de cada acceso.

.env archivos (usando bibliotecas como python-dotenv or dotenv en Node.js) son apropiadas para el desarrollo local. Proporcionan la misma interfaz que las variables de entorno, pero se cargan desde un archivo local. La regla fundamental: .env Los archivos deben estar en .gitignore y nunca debe cometerse.

Detectar secretos ya cometidosSi un secreto se ha codificado y confirmado, debe considerarse comprometido y rotarse inmediatamente. Eliminar el valor de la rama actual no lo elimina del historial de Git. Herramientas como git-filter-branch o bien, BFG Repo Cleaner puede borrar el historial, pero la suposición más segura después de una confirmación es que el secreto está expuesto.

Cómo evitar claves API codificadas

En el caso de las claves API de terceros, las alternativas a la codificación fija dependen del contexto:

  • Aplicaciones del lado del servidor: variables de entorno o servicios de gestión de secretos
  • pipelines de CI / CD: variables secretas de la canalización (secretos de GitHub Actions, variables de GitLab CI)
  • Aplicaciones móvilesLas claves API nunca deben estar en el código del lado del cliente; utilice un proxy de backend que llame a la API con la clave almacenada en el servidor.
  • Agentes de IA e integraciones de LLM: cargar las claves API desde el entorno al inicializar el agente, nunca pasarlas como cadenas codificadas en las solicitudes o llamadas a funciones.

La codificación segura de RPG en IBM i sigue el mismo principio: las áreas de datos externas, las colas de datos o los valores del sistema deben contener parámetros de conexión específicos del entorno en lugar de codificarlos en el código fuente del programa.

Cómo evitar valores codificados directamente en el código: patrones completos con código

Variables de entorno

Las variables de entorno son la solución más universal. Todos los principales sistemas operativos, plataformas en la nube, sistemas de contenedores y marcos de implementación las admiten.

pitón

# Python: load all configuration from environment
import os
from dotenv import load_dotenv

load_dotenv()  # loads .env file in development; ignored in production

DATABASE_URL = os.environ["DATABASE_URL"]
API_BASE_URL = os.environ.get("API_BASE_URL", "https://api.example.com")
MAX_RETRIES  = int(os.environ.get("MAX_RETRIES", "3"))
DEBUG_MODE   = os.environ.get("DEBUG", "false").lower() == "true"

javascript

// Node.js: access environment variables
require('dotenv').config();  // load .env in development

const config = {
  dbUrl:      process.env.DATABASE_URL,
  apiKey:     process.env.API_KEY,
  port:       parseInt(process.env.PORT, 10) || 3000,
  debug:      process.env.NODE_ENV !== 'production',
};

Java

// Java: environment variables via System.getenv()
public class AppConfig {
    public static final String DB_URL  = System.getenv("DATABASE_URL");
    public static final String API_KEY = System.getenv("THIRD_PARTY_API_KEY");
    public static final int    TIMEOUT = Integer.parseInt(
        Optional.ofNullable(System.getenv("REQUEST_TIMEOUT_MS")).orElse("5000")
    );
}

Archivos de configuración

Los archivos de configuración externalizan valores que son específicos del entorno pero no secretos, nombres de servicios, indicadores de funciones, tamaños de paginación, TTL de caché.

yaml

# config/production.yaml
database:
  host: prod-db.internal
  port: 5432
  pool_size: 20

api:
  base_url: https://api.partner.com/v2
  timeout_ms: 3000
  max_retries: 3

features:
  new_checkout: true
  beta_dashboard: false

pitón

# Load at startup; never hardcode the values it contains
import yaml

with open(f"config/{os.environ['APP_ENV']}.yaml") as f:
    config = yaml.safe_load(f)

timeout = config["api"]["timeout_ms"]

Inyección de dependencia

La inyección de dependencias transfiere valores configurados a los componentes en lugar de que estos creen o recuperen su propia configuración. Esto permite probar los componentes de forma aislada y configurarlos según el entorno.

Java

// Spring Boot: inject configuration via @Value
@Service
public class PaymentService {
    @Value("${payment.api.url}")
    private String apiUrl;

    @Value("${payment.api.timeout-ms}")
    private int timeoutMs;

    // No hardcoded URL or timeout anywhere in this class
    public PaymentResult process(Payment payment) {
        // uses apiUrl and timeoutMs injected from application.properties
    }
}

pitón

# Python: simple constructor injection
class EmailService:
    def __init__(self, smtp_host: str, smtp_port: int, api_key: str):
        self.smtp_host = smtp_host
        self.smtp_port = smtp_port
        self.api_key   = api_key

# Wire at application startup, reading from environment
email_service = EmailService(
    smtp_host=os.environ["SMTP_HOST"],
    smtp_port=int(os.environ["SMTP_PORT"]),
    api_key=os.environ["EMAIL_API_KEY"],
)

Constantes y enumeraciones centralizadas

Los valores que están realmente fijos por diseño, como los códigos de estado HTTP, los estados específicos del dominio y los identificadores de protocolo, deben estar en un único módulo de constantes con nombre, en lugar de estar dispersos como literales de cadena.

pitón

# Python: centralised constants
from enum import Enum

class OrderStatus(Enum):
    PENDING   = "pending"
    CONFIRMED = "confirmed"
    SHIPPED   = "shipped"
    DELIVERED = "delivered"
    CANCELLED = "cancelled"

# Usage: no magic strings scattered across modules
if order.status == OrderStatus.CONFIRMED:
    notify_warehouse(order)

Java

// Java: enum with business meaning
public enum UserRole {
    ADMIN("admin"),
    EDITOR("editor"),
    VIEWER("viewer");

    private final String value;
    UserRole(String value) { this.value = value; }
    public String getValue() { return value; }
}

// Replaces scattered "admin", "editor", "viewer" strings
if (user.getRole() == UserRole.ADMIN) {
    grantFullAccess(user);
}

Codificación fija en lenguajes específicos

¿Qué es la codificación fija en Python?

En Python, la codificación dura aparece más comúnmente como literales de cadena para URL y credenciales, constantes numéricas en la lógica de negocio y rutas de archivo que asumen una estructura de directorio específica. os.environ y python-dotenv son los mecanismos estándar para la configuración basada en el entorno. configparser Este módulo gestiona archivos de configuración en formato INI. pydantic-settings Proporciona una configuración con validación de tipos a partir de variables de entorno con valores predeterminados, que es el enfoque recomendado para los servicios Python en producción.

Detección de valores codificados en Python: las herramientas de análisis estático, como Bandit (para valores codificados relacionados con la seguridad, como contraseñas y claves), Pylint y Semgrep, pueden identificar automáticamente credenciales y números mágicos codificados.

¿Qué es la codificación fija en Java?

En Java, los valores codificados a menudo aparecen como private static final String campos, @Value anotaciones con valores predeterminados en línea que deberían externalizarse y literales en la lógica de negocio. Spring Boot application.properties y application.yml Los archivos son el mecanismo de externalización estándar. @ConfigurationPropertiesLas clases anotadas proporcionan objetos de configuración validados y con tipos seguros a partir de archivos YAML o de propiedades.

Detección de valores codificados en Java: SpotBugs, con el complemento Find Security Bugs, detecta credenciales codificadas. SonarQube marca números mágicos, URL codificadas y contraseñas codificadas. SMART TS XLEl análisis empresarial abarca bases de código Java junto con COBOL y otros lenguajes heredados.

Codificación fija en código heredado y de mainframe

Los programas COBOL, RPG y PL/I en mainframes y sistemas de gama media de IBM presentan patrones de codificación específicos: nombres de conjuntos de datos integrados en sentencias DD, identificadores de entorno en la lógica del programa y umbrales de negocio compilados directamente en los programas. Externalizar estos valores requiere herramientas compatibles con mainframes: parámetros del sistema (SYSPARM), áreas de datos externas, tablas de configuración en DB2 y JCL parametrizado. Se necesitan herramientas de análisis estático que comprendan los lenguajes de mainframe para detectar estos patrones a gran escala.

Refactorización en el mundo real: De código fijo a configurable

El enfoque de refactorización en tres fases

Fase 1: Descubrimiento. Realizar un análisis estático de todo el código fuente para generar un inventario de valores codificados, agrupados por tipo (credenciales, URL, lógica de negocio, números mágicos) y por nivel de riesgo. Priorizar las credenciales y los valores específicos de producción.

Fase 2: Externalización por categoría. Comience con las credenciales (mayor riesgo): traslade todos los secretos a variables de entorno o a un gestor de secretos. A continuación, modifique las URL y los nombres de servicio específicos del entorno. Luego, modifique los umbrales de la lógica de negocio. Finalmente, modifique los números mágicos reemplazándolos por constantes con nombre o valores de configuración.

Fase 3: Implementación. Agregue comprobaciones de análisis estático al pipeline de CI que fallen ante credenciales codificadas. Agregue reglas de análisis estático que detecten números mágicos. Agregue elementos a la lista de verificación de revisión de código. El objetivo es dificultar la adición de un valor codificado en comparación con el uso del patrón correcto.

Identificación de valores codificados en proyectos heredados

En proyectos heredados donde se han acumulado valores codificados a lo largo de los años:

  • Usar grep o búsqueda en el repositorio de patrones comunes: cadenas de conexión, password, apikeypatrones de direcciones IP y nombres de servicios conocidos
  • Ejecutar herramientas de análisis estático (Bandit, SonarQube, Semgrep, SMART TS XL) para obtener un inventario completo sin revisión manual archivo por archivo
  • Comprueba el historial de Git para ver los secretos que se confirmaron y eliminaron previamente; aún son accesibles en el historial del repositorio.
  • Busque valores que aparezcan de forma idéntica en varios archivos; estos son candidatos para la centralización.

Cómo evitar la introducción de valores codificados directamente en el código.

  • Ganchos de pre-confirmación: ejecutar escaneo de credenciales (por ejemplo, detect-secrets, git-secrets, truffleHog) en cada commit antes de que llegue al repositorio
  • Compuertas del pipeline de CI: fallan las compilaciones que contienen hallazgos de análisis estático recientemente introducidos para credenciales o números mágicos
  • Listas de verificación para la revisión de código: elementos explícitos para la externalización de la configuración en el proceso de revisión del equipo
  • Incorporación de desarrolladores: establecer los patrones correctos como predeterminados, proporcionar una plantilla de proyecto que ya utilice variables de entorno y una .env.example listo para importar

Cómo SMART TS XL Elimina los valores codificados de forma fija a gran escala.

La búsqueda manual y el uso de grep son suficientes para encontrar casos obvios en bases de código pequeñas. En sistemas empresariales que abarcan millones de líneas en COBOL, Java, Python, .NET, RPG y SQL, la detección exhaustiva de valores codificados requiere un análisis estructural automatizado. SMART TS XL Realiza un análisis estático en todos estos idiomas simultáneamente, construyendo un modelo de referencias cruzadas unificado que identifica:

  • Valores de cadena literales en parámetros de conexión y llamadas de autenticación, marcados como posible exposición de credenciales.
  • Los números mágicos en la lógica empresarial se comparan con una línea base configurable, identificando valores que son inconsistentes entre módulos o que aparecen con valores diferentes en diferentes lugares.
  • Valores literales duplicados que aparecen en varios archivos pero que cumplen la misma función lógica, lo que indica oportunidades de centralización.
  • Valores en programas COBOL que codifican identificadores específicos del entorno, generalmente gestionados mediante parámetros JCL.
  • Rutas de flujo de datos desde valores codificados a través del sistema, mostrando qué operaciones posteriores dependen de ellos antes de tomar una decisión de refactorización.

La capacidad de análisis de código estático proporciona el inventario. La capacidad de análisis de impacto muestra qué se verá afectado al reemplazar un valor codificado. La capacidad de modernización de sistemas heredados extiende esto a escenarios multiplataforma y multilenguaje donde el mismo valor lógico está codificado de forma independiente en un programa de mainframe y en un servicio Java, lo que requiere una remediación coordinada en lugar de correcciones aisladas.

Para los equipos que gestionan específicamente la deuda de seguridad, SMART TS XLLa detección de credenciales codificadas se integra en las canalizaciones de CI/CD como una puerta de compilación: las nuevas confirmaciones que introducen secretos codificados hacen que la compilación falle automáticamente, antes de que el código llegue a un repositorio donde podría quedar expuesto.

La codificación rígida es un problema de disciplina, no de conocimiento.

La mayoría de los desarrolladores que incluyen valores directamente en el código saben que no deberían hacerlo. El conocimiento de que existen variables de entorno, que los archivos de configuración son el método correcto y que las credenciales nunca deben estar en el código fuente es ampliamente conocido y comprendido. El problema no radica en el conocimiento, sino en la disciplina bajo presión.

Los plazos de entrega hacen que la configuración de variables de entorno se perciba como una tarea tediosa. Las bases de código heredadas hacen que la externalización parezca más arriesgada que mantener los valores originales. Los equipos grandes con procesos de incorporación inconsistentes facilitan que un nuevo desarrollador copie un patrón existente que, por casualidad, es incorrecto. Por lo tanto, abordar la codificación rígida a escala organizacional requiere más que documentación: requiere una aplicación automatizada que haga que el patrón incorrecto sea más difícil que el correcto.

Los ganchos de pre-commit que rechazan las confirmaciones que contienen secretos, las canalizaciones de CI que fallan ante nuevos números mágicos, las listas de verificación de revisión de código que preguntan explícitamente sobre la externalización de la configuración y las herramientas de análisis estático que revelan el alcance completo de los valores codificados existentes en un código base: estos son los mecanismos que cierran la brecha entre conocer el enfoque correcto y aplicarlo de forma consistente. Los ejemplos de código y los patrones de este artículo proporcionan la base técnica. Los mecanismos de aplicación son los que garantizan su efectividad.