Зашитая конфигурация.
ИИ-ассистенты вшивают зависящие от окружения значения — 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
- ESLint no-magic-numbers — Запрет магических чисел
- SonarSource typescript:S109 / java:S109 — "Magic numbers should not be used" — Магические числа
- SonarSource java:S1075 — "URIs should not be hardcoded" — Зашитые URI
- SonarSource java:S2068 — "Hard-coded credentials are security-sensitive" — Зашитые учётные данные
- Checkstyle MagicNumber — Проверка магических чисел
- PMD AvoidUsingHardCodedIP — Избегать зашитых IP-адресов
- Bandit B105 hardcoded_password_string / B108 hardcoded_tmp_directory — Зашитые пароли / временные каталоги (Python)
- gitleaks secret detection (e.g. generic-api-key, regex/entropy rules) — Сканер зашитых секретов
- Semgrep generic.secrets / hardcoded-credentials registry rules — Правила для зашитых секретов/конфигурации
- eslint-plugin-no-secrets no-secrets/no-secrets — Обнаружение высокоэнтропийных строк (секретов)