Зашитая конфигурация.
ИИ-ассистенты вшивают зависящие от окружения значения — URL, порты, пути, тайм-ауты, ключи и магические числа — прямо в логику вместо чтения их из конфигурации или окружения, потому что литерал является наиболее вероятным следующим токеном, а модель не знает о вашем существующем слое конфигурации.
##Признаки и симптомы
ИИ-ассистент склонен выдавать правдоподобный литерал ровно там, где должна быть ссылка. Код работает в демонстрации, поэтому запах переживает ревью, если его не искать специально.
Характерные признаки:
- Встроенные эндпоинты, порты, пути и тайм-ауты, разбросанные по бизнес-логике вместо модуля конфигурации:
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-значение, которое вот-вот достигнет продакшена.
##Причины проблемы
Почему модели это порождают
- Литерал — самый вероятный следующий токен. Имея
fetch(, самым дешёвым продолжением, удовлетворяющим непосредственному запросу, является конкретная строка URL, а неconfig.apiBaseUrl. Разрешение ссылки требует знания о том, что где-то существует символ; выдача значения не требует ничего. Модели оптимизируют локально правдоподобный, запускаемый фрагмент. - Обучающий корпус состоит из самодостаточных фрагментов. Учебники, примеры из README и ответы на Stack Overflow подставляют значения прямо в код, чтобы они работали автономно. Модель усвоила, что «хороший пример кода» зашивает значения — противоположность гигиене продакшена.
- Нет контекста репозитория / ограниченное окно. Модель обычно не читала ваш
config/, схему переменных окружения или файл констант, поэтому не может их переиспользовать. В пересказе DevClass исследования GitClear это связывается с ростом дублирования: ассистенты реже предлагают переиспользовать похожую функцию — отчасти из-за ограниченного размера контекста. Собственные данные GitClear показывают, что число блоков из пяти и более дублирующихся строк за 2024 год выросло примерно в восемь раз, а доля строк, связанных с рефакторингом («перемещённых»), упала с 25% изменённых строк в 2021 году до менее чем 10% в 2024-м. - Угодничество / склонность отвечать буквально на запрос. Если попросить «добавить синхронизацию заказов», модель выдаёт что-то, что работает сейчас; вынесение конфигурации — это лишний обвес, который она не предложит по своей инициативе, если не сказать.
- Устаревание из-за даты отсечения обучения. Зашитые литералы часто оказываются устаревшими литералами — старые эндпоинты, устаревшие версии API, порты по умолчанию или слабые криптографические константы. В статье OX Security о безопасности вайб-кодинга устаревшие практики вроде хеширования MD5 объясняются «шаблонами, усвоенными из legacy-кодовых баз, где такие практики когда-то были отраслевым стандартом», и отмечается, что «модель совершенно не знает, что стандарты безопасности изменились».
Чем это вредит
- Сопровождаемость — Стрельба дробью. Изменение одного тайм-аута или базового URL означает охоту за каждым дублированным литералом по всей кодовой базе; пропустите один — и поведение незаметно разойдётся в зависимости от места вызова.
- Корректность в разных окружениях. Зашитый в логику URL стейджинга или ID dev-аккаунта уезжает в продакшен; магические пороги без имени рассинхронизируются со значениями, которые должны были бы отражать.
- Безопасность. Зашитые токены и ключи — это утечка учётных данных: попав в коммит, они навсегда остаются в истории git. Эмпирическое исследование сгенерированного ИИ кода сборки (arXiv 2601.16839) нашло 364 запаха в коде сборки из пул-реквестов, написанных агентами, в том числе 25 зашитых путей/URL и 5 зашитых учётных данных с высокой степенью серьёзности; более 61% этих агентских пул-реквестов были влиты.
- Нагрузка на ревью и техдолг. Ревьюерам приходится проверять происхождение каждого литерала, а значения, которые управляют поведением через вспомогательные функции и пороги, вручную проверять труднее всего. В интервью Stack Overflow с Эно Рейесом из Factory он замечает: «чем качественнее кодовая база, тем сильнее ИИ ускорял организацию», — а разрастание зашитых значений подтачивает именно этот базовый уровень.
##Лечение
Тактики промптинга / ревью
- Направьте модель на вашу конфигурационную поверхность: «Прочитай
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-factor) и читайте их только через этот модуль; никогда не коммитьте секреты.
// 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.
##Обнаружение анализаторами
- ESLint no-magic-numbers — Запрет магических чисел
- SonarSource typescript:S109 / java:S109 — Магические числа
- SonarSource java:S1075 — Зашитые URI
- SonarSource java:S2068 — Зашитые учётные данные
- Checkstyle MagicNumber — Проверка магических чисел
- PMD AvoidUsingHardCodedIP — Избегать зашитых IP-адресов
- Bandit B105 hardcoded_password_string / B108 hardcoded_tmp_directory — Зашитые пароли / временные каталоги (Python)
- gitleaks generic-api-key — Сканер зашитых секретов
- Semgrep generic.secrets / hardcoded-credentials — Правила для зашитых секретов/конфигурации
- eslint-plugin-no-secrets no-secrets/no-secrets — Обнаружение высокоэнтропийных строк (секретов)