Освобождение от заложенных в нас ценностей

Освобождение от жестко запрограммированных ценностей: более разумные стратегии для современного программного обеспечения

Жестко закодированные значения выглядят как ярлыки. Разработчику нужен URL базы данных, он вводит его непосредственно в строку подключения и продолжает работу. Три месяца спустя URL меняется, изменение внесено в один файл, но пропущено в четырех других, и в 2 часа ночи в продакшене ломается система. Разработчику нужен ключ API, он вводит его в исходный файл, делает коммит и отправляет изменения. Шесть месяцев спустя репозиторий клонируется, ключ появляется в публичном форке, и учетная запись скомпрометирована, прежде чем кто-либо это заметит. Это не исключения. Это стандартная траектория жестко закодированных значений в программных системах: безобидные на вид ярлыки, которые накапливаются, приводя к долгам по поддержке, инцидентам безопасности и сбоям развертывания.

Выявляйте и устраняйте жестко закодированные значения.

Устраните и избавьтесь от жестко закодированных значений

Узнать больше

Решение несложное. Переменные окружения, конфигурационные файлы, внедрение зависимостей и централизованные константы — все это хорошо известные шаблоны, доступные в каждом основном языке и фреймворке. Задача состоит в том, чтобы систематически применять их в существующих кодовых базах, обеспечивать их использование в новом коде и выработать дисциплину, позволяющую выявлять их до того, как они попадут в продакшн. Все необходимые для этого методы описаны ниже с рабочими примерами кода для Python, Java и JavaScript.

Содержание

Что такое жестко заданное значение?

Жестко закодированное значение — это литеральная константа, встроенная непосредственно в исходный код, а не передаваемая через конфигурацию, переменные среды, базу данных или входные данные во время выполнения. Отличительной характеристикой является отсутствие косвенного доступа: если изменение значения требует модификации исходного кода, перекомпиляции или повторного развертывания приложения, оно считается жестко закодированным.

Общие примеры:

  • Строка подключения к базе данных, вводимая непосредственно в класс конфигурации.
  • Ключ API или токен доступа в исходном файле
  • URL-адрес сервиса, указывающий на конкретную среду.
  • Числовой порог (if (amount > 5000)) без объяснений или внешних источников
  • Путь к файлу, предполагающий определенную конфигурацию сервера.
  • Строка роли пользователя ("admin") разбросаны по проверкам авторизации

Жестко закодированные значения присутствуют в каждом языке программирования и в каждом коде. В устаревших приложениях для мэйнфреймов имена наборов данных, коды регионов и бизнес-пороги кодируются непосредственно в программах COBOL для каждой конкретной среды. В современных микросервисах жестко закодированные значения тайм-аутов, счетчики повторных попыток и URL-адреса обнаружения сервисов накапливаются в классах приложений. Форма меняется в зависимости от языка, но проблема остается той же.

В чем разница между жестко заданными значениями и константами?

Заданные значения часто путают с константами, но это различие важно. Константа представляет собой стабильный, преднамеренный факт на уровне предметной области, который по определению является корректным и вряд ли изменится: Math.PI, HTTP_STATUS_OK = 200, MAX_RETRY_ATTEMPTS = 3 (если число три действительно подходит для всех контекстов). Константы уместны в коде. Они добавляют ясности, предотвращают опечатки и делают намерение явным.

Заданное значение подразумевает предположение о контексте развертывания, инфраструктуре или бизнес-правилах, которое, как ожидается, будет меняться. URL-адрес производственной базы данных, ключ API, региональная налоговая ставка, состояние флага функции: это жестко заданные значения, маскирующиеся под константы. Проверка проста: если значение должно отличаться в разных средах, у разных клиентов или в разных релизах, оно не является константой и не должно присутствовать в исходном коде.

Что такое мягкое кодирование?

«Мягкое кодирование» — это практика, при которой значения настраиваются во время выполнения, а не фиксируются на этапе компиляции. Значение, заданное таким образом, может быть изменено через файл конфигурации, переменную среды, запись в базе данных или административный интерфейс без изменения исходного кода или повторного развертывания приложения. «Мягкое кодирование» — это правильный подход для настроек, специфичных для конкретной среды, бизнес-параметров, флагов функций и любых значений, которые могут отличаться между средами разработки, тестирования и производства.

Разница между жёстким и мягким кодированием заключается в разнице между системой, требующей изменения кода и развертывания для обновления налоговой ставки, и системой, позволяющей члену оперативной группы обновить её в конфигурационном файле и перезапустить службу. В масштабах предприятия эта разница означает тысячи часов работы инженеров и значительный операционный риск.

Почему жесткое кодирование — плохая практика

Это разрушает ремонтопригодность.

Жестко закодированные значения умножаются. URL-адрес сервиса, жестко закодированный в одном файле, становится жестко закодированным в пяти файлах при копировании модуля для новой интеграции. Магическое число, появляющееся в одном вычислении, появляется в трех, когда та же логика воспроизводится в немного разных контекстах. Каждый такой случай — это отдельная задача по поддержке: найти его, обновить, протестировать обновление и надеяться, что ни один случай не был пропущен.

Проблема поддерживаемости заключается не только в затраченных усилиях. Она связана с риском. Поиск и замена в коде для обновления жестко закодированного значения — это операция рефакторинга, оказывающая влияние на производственную среду. Любой пропущенный экземпляр — это ошибка. Любая некорректная замена — это ошибка. Единственный способ убедиться в полноте изменений — это иметь автоматизированные тесты, которые завершатся с ошибкой, если останется хотя бы один экземпляр, но именно автоматизированные тесты и создают трудности при написании кода из-за жестко закодированных значений.

Это блокирует тестирование и CI/CD.

Автоматизированные тесты должны выполняться в контролируемых средах. Тест, который подключается к производственной базе данных из-за жестко заданной строки подключения, не является тестом; это производственная операция, которая случайно выполняется во время сборки. Конвейер CI, который ломается из-за недоступности жестко заданного URL-адреса службы с сервера сборки, — это не проблема тестовой инфраструктуры; это проблема жесткого кодирования.

Переменные среды и внедрение конфигурации позволяют запускать один и тот же набор тестов в локальной среде разработки, CI, тестовой и производственной средах с различными сервисами. Без них тесты становятся специфичными для каждой среды, нестабильными и в конечном итоге заброшенными.

Это создает серьезные уязвимости в системе безопасности.

Встроенные учетные данные — одна из самых распространенных и часто используемых категорий уязвимостей в программной безопасности. Встроенный ключ API, пароль базы данных или токен доступа, зафиксированные в системе контроля версий, сохраняются в истории репозитория даже после удаления из текущей ветки. Автоматизированные сканеры постоянно проверяют общедоступные репозитории на наличие таких шаблонов. Ключ, найденный в коммите восемнадцатимесячной давности, остается действительным, если он никогда не был обновлен.

OWASP прямо указывает на жестко закодированные пароли как на критическую уязвимость безопасности (CWE-259, CWE-798). Рекомендации NIST требуют, чтобы учетные данные никогда не хранились в исходном коде. Несмотря на это, утечки учетных данных через жестко закодированные значения остаются одной из наиболее частых причин инцидентов в области облачной безопасности.

Риск выходит за рамки учетных данных. Закодированные внутренние URL-адреса служб раскрывают топологию инфраструктуры. Закодированные строки ролей пользователей раскрывают логику авторизации. Закодированные пороговые значения проверки могут быть обойдены, как только злоумышленник узнает их точные значения. Ни один из этих рисков не является очевидным с первого взгляда, поэтому они накапливаются, оставаясь незамеченными.

Встроенные учетные данные и ключи API: категория наивысшего риска.

Зашифрованные секреты заслуживают отдельного подхода, поскольку последствия неправильного их использования принципиально отличаются от других проблем, связанных с зашифрованными данными. Зашифрованное значение таймаута приводит к ошибке. Зашифрованный ключ API приводит к утечке данных.

Как выглядят жестко закодированные учетные данные

питон

# 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 -- 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");

Как исправить жестко закодированные учетные данные

Переменные среды — это стандартное решение для хранения секретов в развернутых приложениях. Секрет устанавливается в среде развертывания (сервер, контейнер, секрет Kubernetes или менеджер секретов облака) и доступен во время выполнения. Исходный код не содержит значения секрета, только имя ключа, используемого для его получения.

Сервисы управления секретами , такие как AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, Google Secret Manager, представляют собой готовый к использованию подход для ротации, аудита и распределения секретов между сервисами. Приложение получает секрет при запуске (или по запросу) из хранилища, а не из переменной среды, обеспечивая ротацию без повторного развертывания и полный журнал аудита каждого доступа.

.env файлов (используя такие библиотеки, как) python-dotenv or dotenv (в Node.js) подходят для локальной разработки. Они предоставляют тот же интерфейс, что и переменные окружения, но загружаются из локального файла. Ключевое правило: .env файлы должны находиться в .gitignore и ни в коем случае не должны быть совершены.

Выявление уже совершенных преступленийЕсли секретный ключ был жестко закодирован и зафиксирован, его следует считать скомпрометированным и немедленно удалить. Удаление значения из текущей ветки не удаляет его из истории Git. Такие инструменты, как git-filter-branch Или же BFG Repo Cleaner может очистить историю, но наиболее безопасное предположение после коммита — это то, что секрет раскрыт.

Как предотвратить использование жестко закодированных ключей API

В случае использования ключей API сторонних разработчиков альтернативы жесткому кодированию зависят от контекста:

  • Серверные приложения: переменные среды или службы управления секретами
  • Конвейеры CI / CD: переменные секретов конвейера (секреты GitHub Actions, переменные GitLab CI)
  • Мобильные приложенияКлючи API никогда не должны находиться в коде на стороне клиента; используйте бэкэнд-прокси, который вызывает API с ключом, хранящимся на стороне сервера.
  • Интеграция агентов ИИ и LLMЗагружайте ключи API из окружения при инициализации агента, никогда не передавайте их в виде жестко закодированных строк в запросах или вызовах функций.

Безопасное программирование на языке RPG в IBM i основано на том же принципе: внешние области данных, очереди данных или системные значения должны содержать параметры подключения, специфичные для конкретной среды, а не кодироваться в исходном коде программы.

Как предотвратить использование жестко закодированных значений: завершение шаблонов с помощью кода

Переменные среды

Переменные среды — наиболее универсальное решение. Их поддерживают все основные операционные системы, облачные платформы, системы контейнеризации и среды развертывания.

питон

# 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: 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")
    );
}

Файлы конфигурации

В конфигурационных файлах можно вынести во внешний мир значения, специфичные для среды, но не являющиеся секретными, такие как имена служб, флаги функций, размеры пагинации и время жизни кэша (TTL).

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

питон

# 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"]

Внедрение зависимости

Внедрение зависимостей передает настроенные значения в компоненты, вместо того чтобы компоненты создавали или получали свою собственную конфигурацию. Это позволяет тестировать компоненты изолированно и настраивать их для каждой среды.

Ява

// 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
    }
}

питон

# 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"],
)

Централизованные константы и перечисления

Значения, которые действительно фиксированы по замыслу разработчика, коды состояния HTTP, состояния, специфичные для предметной области, идентификаторы протокола, должны находиться в одном именованном модуле констант, а не быть разбросаны в виде строковых литералов.

питон

# 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: 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);
}

Встраивание кода непосредственно в определенные языки программирования

Что такое хардкодирование в Python?

В Python жесткое кодирование чаще всего встречается в виде строковых литералов для URL-адресов и учетных данных, числовых констант в бизнес-логике и путей к файлам, предполагающих определенную структуру каталогов. os.environ и python-dotenv являются стандартными механизмами для конфигурации на основе среды. configparser Модуль обрабатывает конфигурационные файлы в формате INI. pydantic-settings Предоставляет конфигурацию с проверкой типов на основе переменных окружения со значениями по умолчанию, что является рекомендуемым подходом для производственных сервисов Python.

Выявление жестко закодированных значений в Python: инструменты статического анализа, включая Bandit (для выявления жестко закодированных данных, связанных с безопасностью, таких как пароли и ключи), Pylint и Semgrep, могут автоматически идентифицировать жестко закодированные учетные данные и «магические числа».

Что такое жёсткое кодирование в Java?

В Java жестко закодированные значения часто выглядят следующим образом: private static final String поля, @Value Аннотации со встроенными значениями по умолчанию, которые должны быть вынесены во внешний интерфейс, и литералы в бизнес-логике. Spring Boot application.properties и application.yml Файлы являются стандартным механизмом внешнего отображения. @ConfigurationPropertiesАннотированные классы предоставляют типобезопасные, проверенные объекты конфигурации из файлов YAML или файлов свойств.

Обнаружение жестко закодированных значений в Java: SpotBugs с плагином Find Security Bugs обнаруживает жестко закодированные учетные данные. SonarQube помечает «магические числа», жестко закодированные URL-адреса и жестко закодированные пароли. SMART TS XLАнализ предприятия, проводимый компанией, охватывает кодовые базы Java наряду с COBOL и другими устаревшими языками.

В коде устаревших систем и мэйнфреймов жестко заданы необходимые параметры.

Программы на COBOL, RPG и PL/I на мэйнфреймах и системах среднего уровня IBM имеют специфические шаблоны жесткого кодирования: имена наборов данных, встроенные в операторы DD, идентификаторы среды в логике программы и бизнес-пороги, компилируемые непосредственно в программы. Для вынесения этих значений во внешний код требуются инструменты, поддерживающие мэйнфреймы: системные параметры (SYSPARM), внешние области данных, таблицы конфигурации в DB2 и параметризованный JCL. Для обнаружения этих шаблонов в больших масштабах необходимы инструменты статического анализа, понимающие языки мэйнфреймов.

Рефакторинг в реальных условиях: от жестко закодированных данных к настраиваемым.

Трехэтапный подход к рефакторингу

Этап 1: Обнаружение. Проведите статический анализ всей кодовой базы, чтобы составить перечень жестко закодированных значений, сгруппированных по типу (учетные данные, URL-адреса, бизнес-логика, «магические числа») и уровню риска. Определите приоритеты для учетных данных и значений, специфичных для производственной среды.

Этап 2: Вынесение информации за пределы отдельных категорий. Начните с учетных данных (наибольший риск): переместите все секреты в переменные среды или менеджер секретов. Затем займитесь URL-адресами и именами служб, специфичными для каждой среды. После этого — пороговыми значениями бизнес-логики. Наконец, замените «магические числа» именованными константами или значениями конфигурации.

Этап 3: Внедрение. Добавьте в конвейер CI проверки статического анализа, которые будут завершаться ошибкой при использовании новых жестко закодированных учетных данных. Добавьте правила линтинга, которые отмечают «магические числа». Добавьте пункты в контрольный список проверки кода. Цель состоит в том, чтобы сделать добавление жестко закодированного значения сложнее, чем использование правильного шаблона.

Выявление заложенных в устаревших проектах ценностей

В устаревших проектах, где жестко закодированные значения накапливались годами:

  • Используйте grep или поиск в репозитории по общим шаблонам: строки подключения, password, apikeyшаблоны IP-адресов и известные названия служб.
  • Запустите инструменты статического анализа (Bandit, SonarQube, Semgrep, SMART TS XL) для получения полного списка документов без ручного просмотра каждого файла по отдельности.
  • Проверьте историю Git на наличие ранее зафиксированных и удаленных секретных записей; они по-прежнему доступны в истории репозитория.
  • Найдите значения, которые встречаются одинаково в нескольких файлах; это потенциальные кандидаты на централизацию.

Предотвращение ввода жестко заданных значений

  • Предварительные хуки: выполнить сканирование учетных данных (например, detect-secrets, git-secrets, truffleHog) при каждом коммите до того, как он достигнет репозитория
  • Контрольные точки конвейера CI: сбои в сборках, содержащие новые результаты статического анализа учетных данных или «магических чисел»
  • Контрольные списки для проверки кода: явные элементы для вынесения конфигурации во внешнюю среду в процессе проверки командой.
  • Привлечение разработчиков: установить правильные шаблоны в качестве шаблонов по умолчанию, предоставить шаблон проекта, который уже использует переменные среды и .env.example файл

Как SMART TS XL Исключает использование жестко закодированных значений в масштабе предприятия.

Ручной поиск и grep достаточны для обнаружения очевидных случаев в небольших кодовых базах. В корпоративных системах, содержащих миллионы строк кода на COBOL, Java, Python, .NET, RPG и SQL, для всестороннего обнаружения жестко закодированных значений требуется автоматизированный структурный анализ. SMART TS XL Выполняет статический анализ всех этих языков одновременно, создавая единую перекрестную справочную модель, которая идентифицирует:

  • Строковые значения в параметрах подключения и вызовах аутентификации, помеченные как потенциальная утечка учетных данных.
  • «Магические числа» в бизнес-логике сравниваются с настраиваемым базовым значением, выявляя значения, которые не согласуются между модулями или которые имеют разные значения в разных местах.
  • Дублирующиеся литеральные значения, встречающиеся в нескольких файлах, но выполняющие одну и ту же логическую функцию, указывают на возможности централизации.
  • Значения в программах COBOL, кодирующие идентификаторы, специфичные для среды, обычно управляются с помощью параметров JCL.
  • Пути потока данных от жестко закодированных значений через систему, показывающие, какие операции нижестоящего уровня зависят от них, прежде чем будет принято решение о рефакторинге.

Функция статического анализа кода предоставляет перечень изменений. Функция анализа влияния показывает, что будет затронуто при замене жестко закодированного значения. Функция модернизации устаревших систем расширяет это до сценариев, включающих разные языки программирования и платформы, когда одно и то же логическое значение жестко закодировано независимо в программе для мэйнфрейма и в Java-сервисе, что требует скоординированного исправления, а не отдельных исправлений.

Для команд, занимающихся управлением проблемными активами в сфере безопасности, SMART TS XLФункция обнаружения жестко закодированных учетных данных интегрируется в конвейеры CI/CD в качестве контрольного пункта сборки: новые коммиты, содержащие жестко закодированные секреты, автоматически приводят к сбою сборки до того, как код достигнет репозитория, где он может быть раскрыт.

Жесткое программирование — это проблема дисциплины, а не проблема знаний.

Большинство разработчиков, которые жёстко прописывают значения, знают, что этого делать не следует. Знание о существовании переменных окружения, о том, что файлы конфигурации — это правильный подход, и о том, что учётные данные никогда не должны находиться в исходном коде, широко распространено и общеизвестно. Проблема не в знаниях: проблема в дисциплине под давлением.

Сжатые сроки сдачи проекта делают настройку переменных окружения излишней. В устаревших кодовых базах вынесение кода во внешнюю среду кажется более рискованным, чем сохранение значений на месте. Большие команды с непоследовательной процедурой адаптации позволяют новому разработчику легко скопировать существующий шаблон, который оказывается неправильным. Поэтому решение проблемы жесткого кодирования в масштабах организации требует большего, чем просто документация: оно требует автоматизированного контроля, который делает неправильный шаблон сложнее, чем правильный.

Предварительные хуки, отклоняющие коммиты, содержащие секреты, конвейеры CI, завершающиеся с ошибкой при появлении новых «магических чисел», контрольные списки проверки кода, явно запрашивающие информацию о вынесении конфигурации во внешний код, и инструменты статического анализа, отображающие полный объем существующих жестко закодированных значений в кодовой базе: это механизмы, которые устраняют разрыв между знанием правильного подхода и его последовательным применением. Примеры кода и шаблоны, приведенные в этой статье, обеспечивают техническую основу. Механизмы обеспечения их эффективности — вот что позволяет их применять на практике.