ConstructiCat Logo
CodeBust.
Browse section ▾

Зашитая конфигурация.

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

##Signs and Symptoms

ИИ-ассистент склонен выдавать правдоподобный литерал ровно там, где должна быть ссылка. Код работает в демонстрации, поэтому запах переживает ревью, если его не искать специально.

Характерные признаки:

  • Встроенные эндпоинты, порты, пути и тайм-ауты, разбросанные по бизнес-логике вместо модуля конфигурации: http://localhost:3000, /tmp/cache, 5432, setTimeout(..., 30000).
  • Магические числа/строки без именованной константы — счётчики повторов, размеры страниц, ограничения частоты, пороги функций.
  • Один и тот же литерал, повторяющийся в разных файлах, потому что модель сгенерировала его заново, а не импортировала существующую константу (тот же паттерн GitClear «копипаст вверх, рефакторинг вниз» в миниатюре).
  • Зашитые учётные данные / ключи — API-токены, ключи шифрования или строки Basic-авторизации, вставленные из обучающих данных.
  • Существующий config.ts / .env / объект настроек, который новый код полностью игнорирует.
  • Значения, вшитые под окружение: URL dev/стейджинга или ID тестового аккаунта, жёстко прописанные в пути, который уезжает в продакшен.
// 🚩 Сгенерировано ИИ: каждая настройка — литерал, dev-значения вшиты
export async function syncOrders() {
  const res = await fetch("https://api.staging.acme.dev/v1/orders", {
    headers: { Authorization: "Bearer sk_test_4eC39HqLyjWDarjtT1zdp7dc" },
    signal: AbortSignal.timeout(30000),
  });
  const orders = (await res.json()).slice(0, 50); // почему 50?
  for (let i = 0; i < 3; i++) { /* повтор 3 раза... почему 3? */ }
}

Строка Authorization — это срабатывание gitleaks/Semgrep; URL, 30000, 50 и 3 — запахи магических значений; а api.staging.acme.dev — это dev-значение, которое вот-вот достигнет продакшена.

##Reasons for the Problem

Почему модели это порождают

  • Литерал — самый вероятный следующий токен. Имея fetch(, самым дешёвым продолжением, удовлетворяющим непосредственному запросу, является конкретная строка URL, а не config.apiBaseUrl. Разрешение ссылки требует знания о том, что где-то существует символ; выдача значения не требует ничего. Модели оптимизируют локально правдоподобный, запускаемый фрагмент.
  • Обучающий корпус состоит из самодостаточных фрагментов. Учебники, примеры из README и ответы на Stack Overflow подставляют значения прямо в код, чтобы они работали автономно. Модель усвоила, что «хороший пример кода» зашивает значения — противоположность гигиене продакшена.
  • Нет контекста репозитория / ограниченное окно. Модель обычно не читала ваш config/, схему окружения или файл констант, поэтому не может их переиспользовать. GitClear связывает именно это с ростом дублирования: ассистенты «реже предлагают переиспользовать похожую функцию… отчасти из-за ограниченного размера контекста», при этом дублированных блоков по 5+ строк в 2024 году стало примерно в 8× больше, тогда как доля отрефакторенных («перемещённых») строк упала с ~24 % до ~9,5 %.
  • Угодничество / склонность отвечать буквально на запрос. Если попросить «добавить синхронизацию заказов», модель выдаёт что-то, что работает сейчас; вынесение конфигурации — это лишний обвес, который она не предложит по своей инициативе, если не сказать.
  • Устаревание из-за обучающего среза. Вшитые литералы — это зачастую устаревшие литералы: старые эндпоинты, устаревшие версии API, порты по умолчанию или слабые криптоконстанты (MD5, зашитые ключи). OX Security обнаружила, что 62 % сгенерированного ИИ кода поставляется с проблемами, и связывает зашитые ключи/секреты/пути с паттернами, «усвоенными из легаси-кодовых баз… [при] нулевом понимании того, что стандарты безопасности изменились».

Чем это вредит

  • Сопровождаемость — «дробовик» (Shotgun Surgery). Изменение одного тайм-аута или базового URL означает охоту за каждым дублированным литералом по всей кодовой базе; пропустите один — и поведение незаметно разойдётся в зависимости от места вызова.
  • Корректность в разных окружениях. Зашитый в логику URL стейджинга или ID dev-аккаунта уезжает в продакшен; магические пороги без имени рассинхронизируются со значениями, которые должны были бы отражать.
  • Безопасность. Зашитые токены/ключи — это утечки учётных данных: будучи однажды закоммиченными, они навсегда остаются в истории git. Исследование сборочного кода с arXiv (2601.16839) обнаружило повторяющиеся в сгенерированных ИИ сборочных файлах зашитые пути/URL и зашитые учётные данные высокой степени критичности.
  • Нагрузка на ревью и техдолг. Ревьюеры вынуждены проверять происхождение каждого литерала. Работа о специфичных для ИИ запахах (arXiv 2509.20491) отмечает, что модели нормально справляются с «литералами в области видимости», но пасуют в чувствительных к значению случаях, где корректность зависит от порогов, распространяющихся через вспомогательные функции, — именно те зашитые значения, которые людям теперь приходится проверять вручную. По словам Эно Рейеса из Factory в интервью Stack Overflow, базовое качество кода — «единственный сигнал» того, ускоряют ли агенты команду или замедляют, а разрастание зашитых значений подтачивает именно этот базовый уровень.

##Treatment

Тактики промптинга / ревью

  • Направьте модель на вашу конфигурационную поверхность: «Прочитай src/config.ts и .env.example; используй config.* / process.env для каждого URL, порта, тайм-аута и учётных данных. Не вводи литералы.» Модели подставляют значения, потому что не знают о существовании символа, — назовите его.
  • Явно запретите магические значения: «Никаких магических чисел или строк — выноси именованные константы.» Затем заставьте прогнать eslint --rule no-magic-numbers и сканер секретов (gitleaks/Semgrep) и исправить то, что они отметят. Согласно рекомендациям Factory, встройте линтеры/сканеры в цикл, чтобы агент исправлялся сам, а не опирался на ручное ревью.
  • Дайте схему окружения (zod/envalid/.env.example), чтобы модели было куда класть значения, а не угадывать их.
  • Прогрепайте новые диффы на http, localhost, IP-литералы, Bearer и голые цифры в аргументах вызовов перед слиянием.

Рефакторинг — назовите классические приёмы:

  • Замена магического числа символьной константой для порогов, счётчиков, размеров.
  • Извлечение функции / Извлечение модуля конфигурации, чтобы собрать настройки в одно типизированное, валидируемое место.
  • Вынесение в окружение (12-факторное приложение) для всего, что различается между окружениями или является секретом; никогда не коммитьте секреты.
// config.ts — единый, валидируемый источник истины
import { z } from "zod";
const env = z.object({
  ORDERS_API_BASE_URL: z.string().url(),
  ORDERS_API_TOKEN: z.string().min(1),
  ORDERS_TIMEOUT_MS: z.coerce.number().default(30_000),
  ORDERS_PAGE_SIZE: z.coerce.number().default(50),
  ORDERS_MAX_RETRIES: z.coerce.number().default(3),
}).parse(process.env);
export const config = env;
// ✅ после: ссылки, а не литералы — одно место для изменений, секрет вне исходников
import { config } from "./config";
export async function syncOrders() {
  const res = await fetch(`${config.ORDERS_API_BASE_URL}/v1/orders`, {
    headers: { Authorization: `Bearer ${config.ORDERS_API_TOKEN}` },
    signal: AbortSignal.timeout(config.ORDERS_TIMEOUT_MS),
  });
  const orders = (await res.json()).slice(0, config.ORDERS_PAGE_SIZE);
  for (let i = 0; i < config.ORDERS_MAX_RETRIES; i++) { /* ... */ }
}

Если секрет уже попал в коммит, смените его — удаление строки не очищает историю git.

##Detected by