Sécuriser son code généré par IA : checklist développeur

Sécuriser son code généré par IA : checklist développeur

Les assistants IA génèrent du code rapidement. Mais ce code est-il sécurisé ? Pas toujours. Les modèles sont entraînés sur du code existant, incluant des patterns vulnérables. Voici comment valider et sécuriser le code avant de le merger.

Pourquoi le code IA peut être vulnérable

Les modèles apprennent de tout

Les LLMs sont entraînés sur des milliards de lignes de code, incluant :

  • Du code legacy avec des pratiques obsolètes
  • Des exemples de tutoriels simplifiés (sans sécurité)
  • Du code avec des vulnérabilités connues
  • Des snippets Stack Overflow non validés

Patterns dangereux observés

Une étude de Stanford (2023) a montré que les développeurs utilisant Copilot produisaient 40% plus de code vulnérable que ceux qui n'utilisaient pas d'IA.

VulnérabilitéFréquenceExemple
Injection SQLTrès fréquentConcaténation de requêtes
XSSFréquentPas d'échappement HTML
Path TraversalFréquentPas de validation de chemins
Hardcoded secretsTrès fréquentClés API en clair
Insecure deserializationModéréDeserialize sans validation

Les vulnérabilités les plus courantes

1. Injection SQL

Code généré dangereux :

// ❌ L'IA génère souvent ce pattern
public User findByEmail(String email) {
    String query = "SELECT * FROM users WHERE email = '" + email + "'";
    return jdbcTemplate.queryForObject(query, userRowMapper);
}

Code sécurisé :

// ✅ Utiliser des prepared statements
public User findByEmail(String email) {
    String query = "SELECT * FROM users WHERE email = ?";
    return jdbcTemplate.queryForObject(query, userRowMapper, email);
}

Détection automatique :

# Règle Semgrep
rules:
  - id: sql-injection-java
    patterns:
      - pattern: |
          $QUERY = "..." + $VAR + "...";
          $JDBC.query($QUERY, ...)
    message: "Potential SQL injection"
    severity: ERROR

2. Cross-Site Scripting (XSS)

Code généré dangereux :

// ❌ Injection HTML directe
function displayComment(comment) {
    document.getElementById('comments').innerHTML +=
        `<div class="comment">${comment.text}</div>`;
}

Code sécurisé :

// ✅ Utiliser textContent ou une librairie d'échappement
function displayComment(comment) {
    const div = document.createElement('div');
    div.className = 'comment';
    div.textContent = comment.text;
    document.getElementById('comments').appendChild(div);
}

// Ou avec DOMPurify
function displayCommentSafe(comment) {
    const sanitized = DOMPurify.sanitize(comment.text);
    document.getElementById('comments').innerHTML +=
        `<div class="comment">${sanitized}</div>`;
}

3. Path Traversal

Code généré dangereux :

# ❌ Pas de validation du chemin
@app.route('/download/<filename>')
def download(filename):
    return send_file(f'/uploads/{filename}')

Code sécurisé :

# ✅ Valider et normaliser le chemin
import os
from werkzeug.utils import secure_filename

UPLOAD_DIR = '/uploads'

@app.route('/download/<filename>')
def download(filename):
    # Sécuriser le nom de fichier
    safe_filename = secure_filename(filename)

    # Construire le chemin absolu
    file_path = os.path.join(UPLOAD_DIR, safe_filename)

    # Vérifier que le chemin reste dans le dossier autorisé
    if not os.path.realpath(file_path).startswith(os.path.realpath(UPLOAD_DIR)):
        abort(403)

    if not os.path.exists(file_path):
        abort(404)

    return send_file(file_path)

4. Secrets hardcodés

Code généré dangereux :

# ❌ L'IA copie souvent les exemples avec de fausses clés
import openai

openai.api_key = "sk-proj-xxxxxxxxxxxxxxxxxxxxx"

def generate_text(prompt):
    response = openai.ChatCompletion.create(
        model="gpt-4",
        messages=[{"role": "user", "content": prompt}]
    )
    return response.choices[0].message.content

Code sécurisé :

# ✅ Utiliser des variables d'environnement
import os
import openai

openai.api_key = os.environ.get("OPENAI_API_KEY")

if not openai.api_key:
    raise ValueError("OPENAI_API_KEY environment variable not set")

def generate_text(prompt):
    response = openai.ChatCompletion.create(
        model="gpt-4",
        messages=[{"role": "user", "content": prompt}]
    )
    return response.choices[0].message.content

5. Authentification faible

Code généré dangereux :

// ❌ Vérification JWT côté client uniquement
function isAuthenticated() {
    const token = localStorage.getItem('token');
    if (token) {
        const payload = JSON.parse(atob(token.split('.')[1]));
        return payload.exp > Date.now() / 1000;
    }
    return false;
}

Code sécurisé :

// ✅ Toujours vérifier côté serveur
// Client : envoyer le token
async function fetchProtectedResource() {
    const token = localStorage.getItem('token');
    const response = await fetch('/api/protected', {
        headers: {
            'Authorization': `Bearer ${token}`
        }
    });

    if (response.status === 401) {
        // Token invalide ou expiré
        logout();
        return null;
    }

    return response.json();
}

// Serveur : valider le token
const jwt = require('jsonwebtoken');

function authMiddleware(req, res, next) {
    const authHeader = req.headers.authorization;
    if (!authHeader?.startsWith('Bearer ')) {
        return res.status(401).json({ error: 'No token provided' });
    }

    const token = authHeader.slice(7);

    try {
        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        req.user = decoded;
        next();
    } catch (error) {
        return res.status(401).json({ error: 'Invalid token' });
    }
}

Outils de détection automatique

SAST (Static Application Security Testing)

Semgrep - Open source, règles personnalisables :

# Installation
pip install semgrep

# Scan du projet
semgrep --config=auto .

# Avec règles OWASP
semgrep --config=p/owasp-top-ten .

# Rapport CI/CD
semgrep --config=auto --json -o results.json .

Configuration CI :

# .github/workflows/security.yaml
name: Security Scan

on: [push, pull_request]

jobs:
  semgrep:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run Semgrep
        uses: returntocorp/semgrep-action@v1
        with:
          config: >-
            p/security-audit
            p/owasp-top-ten
            p/secrets

CodeQL - Analyse sémantique GitHub :

# .github/workflows/codeql.yaml
name: CodeQL

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 6 * * 1'

jobs:
  analyze:
    runs-on: ubuntu-latest
    permissions:
      security-events: write

    steps:
      - uses: actions/checkout@v4

      - name: Initialize CodeQL
        uses: github/codeql-action/init@v3
        with:
          languages: javascript, python, java

      - name: Build
        run: npm run build

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v3

Détection de secrets

GitLeaks :

# Installation
brew install gitleaks

# Scan du repo
gitleaks detect --source . --verbose

# Pre-commit hook
gitleaks protect --staged

Configuration pre-commit :

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.0
    hooks:
      - id: gitleaks

  - repo: https://github.com/returntocorp/semgrep
    rev: v1.50.0
    hooks:
      - id: semgrep
        args: ['--config', 'auto', '--error']

Scan des dépendances

Dependabot (GitHub natif) :

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10

  - package-ecosystem: "pip"
    directory: "/"
    schedule:
      interval: "weekly"

Snyk :

# Installation
npm install -g snyk

# Authentification
snyk auth

# Scan des vulnérabilités
snyk test

# Monitoring continu
snyk monitor

Checklist de validation

Avant de merger du code généré par IA

Injection :

  • ☐ Pas de concaténation SQL, utiliser des prepared statements
  • ☐ Pas d'évaluation dynamique de code (eval(), exec())
  • ☐ Validation des entrées utilisateur
  • ☐ Échappement des sorties HTML

Authentification / Autorisation :

  • ☐ Vérification côté serveur (jamais côté client seul)
  • ☐ Tokens avec expiration
  • ☐ Validation des permissions par ressource
  • ☐ Pas de credentials hardcodés

Gestion des fichiers :

  • ☐ Validation des chemins (pas de traversal)
  • ☐ Limitation des types de fichiers uploadés
  • ☐ Scan antivirus sur les uploads
  • ☐ Stockage hors du webroot

Cryptographie :

  • ☐ Pas d'algorithmes obsolètes (MD5, SHA1 pour passwords)
  • ☐ Utilisation de bcrypt/argon2 pour les mots de passe
  • ☐ Clés de chiffrement en variables d'environnement
  • ☐ TLS pour toutes les communications

Données sensibles :

  • ☐ Pas de secrets dans le code ou les logs
  • ☐ Masquage des données sensibles dans les erreurs
  • ☐ RGPD : minimisation des données collectées

Prompts sécurisés pour l'IA

Demander explicitement la sécurité

Génère une fonction d'authentification en Node.js.

Contraintes de sécurité :
- Utiliser bcrypt pour les mots de passe
- Implémenter rate limiting
- Valider toutes les entrées
- Logger les tentatives échouées
- Retourner des erreurs génériques (pas de leak d'info)

Demander une review sécurité

Review ce code pour les vulnérabilités de sécurité.
Focus sur :
- OWASP Top 10
- Injection (SQL, XSS, Command)
- Authentification/Autorisation
- Gestion des secrets
- Validation des entrées

Code à analyser :
[coller le code]

Template de validation

Ce code a été généré par IA. Vérifie :

1. Y a-t-il des injections possibles ?
2. Les entrées utilisateur sont-elles validées ?
3. Y a-t-il des secrets hardcodés ?
4. L'authentification est-elle correcte ?
5. Les erreurs révèlent-elles des infos sensibles ?

Si des problèmes sont trouvés, propose une version corrigée.

Code :
[coller le code]

Workflow sécurisé

Pipeline CI/CD recommandé

# .github/workflows/secure-pipeline.yaml
name: Secure Pipeline

on:
  push:
    branches: [main]
  pull_request:

jobs:
  # Étape 1 : Secrets detection
  secrets-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Gitleaks
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  # Étape 2 : SAST
  sast:
    runs-on: ubuntu-latest
    needs: secrets-scan
    steps:
      - uses: actions/checkout@v4

      - name: Semgrep
        uses: returntocorp/semgrep-action@v1
        with:
          config: p/owasp-top-ten

  # Étape 3 : Dependency check
  deps:
    runs-on: ubuntu-latest
    needs: secrets-scan
    steps:
      - uses: actions/checkout@v4

      - name: Snyk
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

  # Étape 4 : Tests (incluant tests de sécurité)
  test:
    runs-on: ubuntu-latest
    needs: [sast, deps]
    steps:
      - uses: actions/checkout@v4

      - name: Run tests
        run: npm test

      - name: Run security tests
        run: npm run test:security

  # Étape 5 : Container scan
  container-scan:
    runs-on: ubuntu-latest
    needs: test
    steps:
      - uses: actions/checkout@v4

      - name: Build image
        run: docker build -t app:${{ github.sha }} .

      - name: Trivy scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: app:${{ github.sha }}
          severity: CRITICAL,HIGH
          exit-code: 1

IDE : activer les checks en temps réel

VS Code :

// settings.json
{
    "sonarlint.rules": {
        "javascript:S2068": { "level": "on" },  // Hardcoded credentials
        "javascript:S5131": { "level": "on" },  // XSS
        "java:S3649": { "level": "on" }         // SQL injection
    },
    "semgrep.scan.onSave": true
}

Formation continue

Ressources recommandées

RessourceTypeFocus
OWASP Top 10GuideVulnérabilités web
PortSwigger Web Security AcademyLabsPratique offensive
SANS Secure CodingCoursBest practices
HackTheBoxCTFPentest pratique

Tests de sécurité à ajouter

// tests/security.test.js
describe('Security Tests', () => {
    describe('Input Validation', () => {
        it('should reject SQL injection attempts', async () => {
            const maliciousInput = "'; DROP TABLE users; --";
            const response = await request(app)
                .get(`/users?search=${encodeURIComponent(maliciousInput)}`);

            expect(response.status).toBe(400);
        });

        it('should sanitize XSS in user input', async () => {
            const xssPayload = '<script>alert("xss")</script>';
            const response = await request(app)
                .post('/comments')
                .send({ text: xssPayload });

            expect(response.body.text).not.toContain('<script>');
        });
    });

    describe('Authentication', () => {
        it('should not expose user existence on login failure', async () => {
            const response1 = await request(app)
                .post('/login')
                .send({ email: 'exists@test.com', password: 'wrong' });

            const response2 = await request(app)
                .post('/login')
                .send({ email: 'notexists@test.com', password: 'wrong' });

            // Same error message for both cases
            expect(response1.body.error).toBe(response2.body.error);
        });
    });
});

Conclusion

Le code généré par IA n'est pas intrinsèquement dangereux, mais il nécessite la même vigilance que n'importe quel code externe. Les outils automatisés (Semgrep, CodeQL, Snyk) détectent la majorité des vulnérabilités connues. Les pre-commit hooks empêchent les erreurs d'atteindre le repository.

Le plus important : ne jamais faire confiance aveuglément au code généré. Relisez, testez, scannez. L'IA accélère le développement, elle ne remplace pas la vigilance humaine.


Pour choisir votre assistant IA : Claude Code vs Cursor vs GitHub Copilot : comparatif 2026

Pour les failles spécifiques aux IDE IA : IDEsaster : les failles de sécurité des IA de coding