Configuration codée en dur.
Les assistants IA intègrent des valeurs propres à l’environnement — URL, ports, chemins, délais d’expiration, clés et nombres magiques — directement dans la logique au lieu de les lire depuis la configuration ou l’environnement, parce qu’un littéral est le jeton suivant le plus probable et que le modèle n’a pas conscience de votre couche de configuration existante.
##Signes et symptômes
Un assistant IA a tendance à émettre un littéral plausible exactement là où une référence devrait se trouver. Le code s’exécute dans la démo, si bien que l’odeur survit à la revue à moins que vous ne la cherchiez.
Signes révélateurs :
- Points de terminaison, ports, chemins et délais d’expiration en ligne parsemés dans la logique métier au lieu d’un module de configuration :
http://localhost:3000,/tmp/cache,5432,setTimeout(..., 30000). - Nombres/chaînes magiques sans constante nommée — nombres de tentatives, tailles de page, limites de débit, seuils de fonctionnalités.
- Le même littéral répété dans plusieurs fichiers parce que le modèle l’a régénéré au lieu d’importer une constante existante (le schéma "copier-coller en hausse, refactorisation en baisse" de GitClear en miniature).
- Identifiants / clés codés en dur — jetons d’API, clés de chiffrement ou chaînes d’authentification
Basiccollées depuis les données d’entraînement. - Un objet
config.ts/.env/ de paramètres existant que le nouveau code ignore totalement. - Valeurs intégrées propres à l’environnement : une URL de dev/préproduction ou un identifiant de compte de test câblé en dur dans un chemin qui part en production.
// 🚩 généré par IA : chaque réglage est un littéral, valeurs de dev intégrées
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); // pourquoi 50 ?
for (let i = 0; i < 3; i++) { /* nouvelle tentative 3 fois... pourquoi 3 ? */ }
}
La ligne Authorization est détectée par gitleaks/Semgrep ; l’URL, 30000, 50 et 3 sont des odeurs de valeurs magiques ; et api.staging.acme.dev est une valeur de dev sur le point d’atteindre la production.
##Causes du problème
Pourquoi les modèles le produisent
- Un littéral est le jeton suivant le plus probable. Étant donné
fetch(, la complétion la moins coûteuse qui satisfait l’invite immédiate est une chaîne d’URL concrète, et nonconfig.apiBaseUrl. Résoudre une référence exige de savoir qu’un symbole existe ailleurs ; émettre une valeur n’exige rien. Les modèles optimisent pour un extrait localement plausible et exécutable. - Le corpus d’entraînement est fait d’extraits autonomes. Les tutoriels, les exemples de README et les réponses de Stack Overflow insèrent leurs valeurs en ligne pour qu’ils s’exécutent de manière autonome. Le modèle a appris qu’un "bon exemple de code" code les valeurs en dur — l’opposé de l’hygiène en production.
- Pas de contexte du dépôt / fenêtre limitée. Le modèle n'a généralement pas lu votre
config/, votre schéma de variables d'environnement ni votre fichier de constantes, il ne peut donc pas les réutiliser. Le résumé de DevClass des travaux de GitClear relie cela à la hausse de la duplication : les assistants proposent moins souvent de réutiliser une fonction similaire, en partie à cause de la taille limitée du contexte. Les données de GitClear montrent que les blocs de cinq lignes dupliquées ou plus ont été multipliés par huit environ en 2024, tandis que les lignes associées au refactoring ("déplacées") passaient de 25 % des lignes modifiées en 2021 à moins de 10 % en 2024. - Flagornerie / biais consistant à répondre à l’invite. Quand on lui demande d’"ajouter une synchronisation des commandes", le modèle livre quelque chose qui fonctionne maintenant ; externaliser la configuration est un échafaudage supplémentaire qu’il ne proposera pas spontanément, sauf si on le lui demande.
- Obsolescence liée à la date de coupure. Les littéraux codés en dur sont souvent des littéraux périmés : anciens endpoints, versions d'API dépréciées, ports par défaut ou constantes cryptographiques faibles. L'article d'OX Security sur la sécurité du vibe coding rattache des pratiques dépassées comme le hachage MD5 à des "motifs appris de bases de code anciennes où ces pratiques étaient autrefois la norme du secteur" et note que "le modèle n'a aucune conscience que les normes de sécurité ont évolué".
Pourquoi c’est nuisible
- Maintenabilité — Chirurgie au fusil à pompe. Modifier un seul délai d’expiration ou une seule URL de base implique de traquer chaque littéral dupliqué dans toute la base de code ; oubliez-en un et le comportement diverge silencieusement selon le site d’appel.
- Exactitude entre environnements. Une URL de préproduction ou un identifiant de compte de développement câblé en dur dans la logique part en production ; des seuils magiques sans nom se désynchronisent des valeurs qu’ils devraient refléter.
- Sécurité. Les jetons et clés codés en dur sont des fuites d'identifiants : une fois commités, ils restent pour toujours dans l'historique git. Une étude empirique du code de build généré par IA (arXiv 2601.16839) a relevé 364 odeurs de code de build dans des pull requests rédigées par des agents, dont 25 chemins/URL codés en dur et 5 identifiants codés en dur classés de gravité élevée ; plus de 61 % de ces pull requests d'agents ont été fusionnées.
- Charge de revue et dette technique. Les relecteurs doivent vérifier la provenance de chaque littéral, et les valeurs qui pilotent le comportement à travers des fonctions utilitaires et des seuils sont justement les plus difficiles à auditer à la main. Dans les questions-réponses de Stack Overflow avec Eno Reyes de Factory, Reyes observe que "plus la base de code était de qualité, plus l'IA accélérait l'organisation" — et la prolifération du codage en dur érode précisément cette base.
##Traitement
Tactiques de prompt / de revue
- Orientez le modèle vers votre surface de configuration : "Lis
src/config.tset.env.example; utiliseconfig.*/process.envpour chaque URL, port, délai d’expiration et identifiant. N’introduis pas de littéraux." Les modèles intègrent les valeurs en ligne parce qu’ils ignorent que le symbole existe — nommez-le. - Interdisez explicitement les valeurs magiques : "Pas de nombres ni de chaînes magiques — extrais des constantes nommées." Faites-lui ensuite exécuter
eslint --rule no-magic-numberset un scanner de secrets (gitleaks/Semgrep), puis corrigez ce qu’ils signalent. Suivant les recommandations de Factory, intégrez les linters/scanners dans la boucle pour que l’agent s’auto-corrige au lieu de s’appuyer sur la revue humaine. - Fournissez le schéma d’environnement (zod/envalid/
.env.example) afin que le modèle ait un endroit où placer les valeurs plutôt que de les deviner. - Passez les nouveaux diffs au grep à la recherche de
http,localhost, de littéraux d’IP, deBeareret de chiffres bruts dans les arguments d’appel avant de fusionner.
Le refactoring — les techniques du catalogue, plus une pratique de configuration :
- Remplacer un nombre magique par une constante symbolique pour les seuils, compteurs et tailles.
- Extraire une classe pour regrouper les réglages dans un seul module de configuration typé et validé.
- Déplacez les valeurs propres à chaque environnement et les secrets dans des variables d'environnement (configuration 12-factor) et ne les lisez qu'à travers ce module ; ne commitez jamais de secrets.
// config.ts — source de vérité unique et validée
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;
// ✅ après : des références, pas des littéraux — un seul endroit à modifier, secret hors du code source
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++) { /* ... */ }
}
Si un secret a déjà atterri dans un commit, changez-le (rotation) — supprimer la ligne ne purge pas l’historique git.
##Détecté par
- ESLint no-magic-numbers — Interdire les nombres magiques
- SonarSource typescript:S109 / java:S109 — Nombres magiques
- SonarSource java:S1075 — URI codés en dur
- SonarSource java:S2068 — Identifiants codés en dur
- Checkstyle MagicNumber — Vérification des nombres magiques
- PMD AvoidUsingHardCodedIP — Éviter les adresses IP codées en dur
- Bandit B105 hardcoded_password_string / B108 hardcoded_tmp_directory — Mots de passe / répertoires temporaires codés en dur (Python)
- gitleaks generic-api-key — Scanner de secrets codés en dur
- Semgrep generic.secrets / hardcoded-credentials — Règles pour secrets/configuration codés en dur
- eslint-plugin-no-secrets no-secrets/no-secrets — Détection de chaînes à forte entropie (secrets)