Retour au Blog
AI & Development26 avril 202611 min de lecture

Comment j'ai configuré les garde-fous IA chez BMO

DR

Doug Ribeiro

11 min de lecture

Dans une banque, la première question au sujet d'un assistant de codage IA est rarement 'que peut faire cet outil ?' C'est 'que pouvons-nous nous assurer qu'il ne fasse jamais ?' Ce cadrage a tout façonné quand j'ai configuré l'assistant de mon équipe chez BMO, GitHub Copilot Business. Les garde-fous dans un environnement bancaire ne visent pas à limiter l'outil. Ils visent à rendre l'outil suffisamment digne de confiance pour être réellement utilisé sur un codebase réglementé.

Ce qui suit est l'approche : les couches sur lesquelles je m'appuie, les catégories de préoccupations qui méritent d'être couvertes, et à quoi ressemble une telle configuration en pratique. Les fichiers présentés dans ce billet sont des exemples illustratifs que j'ai écrits pour l'occasion, pas la configuration de la banque, qui reste interne. Ce qui se transpose à votre propre codebase, c'est la structure et le raisonnement.

Ce que les garde-fous signifient réellement dans un environnement réglementé

Dans une startup grand public, les garde-fous IA pourraient signifier 'ne pas laisser Copilot suggérer une génération de nombres aléatoires non sécurisée.' Dans une banque, la portée est considérablement plus large. Une banque canadienne opère sous les directives du BSIF, les exigences de CANAFE, PCI DSS pour les données de cartes de paiement, et des politiques de classification des données qui déterminent ce que le code peut faire avec quelles catégories d'information.

Le modèle de menace pour le codage assisté par IA dans le secteur bancaire comporte trois couches. Premièrement, le risque d'exfiltration de données : du code qui envoie par inadvertance des données clients vers des services externes. Deuxièmement, les anti-patterns de sécurité : des suggestions qui introduisent des vulnérabilités connues, comme des identifiants codés en dur, une validation d'entrée insuffisante ou des références directes à des objets non sécurisées. Troisièmement, les violations de conformité : du code qui contourne la journalisation d'audit, ignore les indicateurs de consentement, ou traite les données de manières qui violent les exigences de résidence des données.

Rien de tout cela n'est hypothétique. Sans instructions, un assistant IA suggérera volontiers localStorage pour stocker un token de session utilisateur (un anti-pattern de sécurité dans un contexte bancaire), un appel HTTP direct à une API tierce sans routage par proxy (un problème de gouvernance des données), ou un console.log qui imprime un objet client complet (une violation de journalisation de données personnelles). La revue de code détecte ces cas. Les garde-fous visent à les détecter avant qu'ils n'atteignent un reviewer humain.

Couche d'instructions IA.github/copilot-instructions.md et fichiers d'instructions par chemin. … 1 Couche d'analyse statiqueRègles ESLint personnalisées. Attrapent les violations qui ont échappé … 2 Couche de revue de codeGates de revue humaine. Attrapent les écarts d'intention qu'aucun outil… 3 Quality gate CIESLint, le seuil de couverture Jest et les quality gates SonarQube sur … 4

Les instructions personnalisées de dépôt dans Copilot

Copilot lit les instructions personnalisées valables pour tout le dépôt dans .github/copilot-instructions.md, un simple fichier Markdown d'instructions en langage naturel versionné avec le code. Considérez-le comme un system prompt qui voyage avec le dépôt et qui est ajouté à chaque requête de chat. GitHub le documente pour Copilot Chat, la revue de code et l'agent cloud, et la documentation de VS Code précise que les instructions personnalisées ne sont pas prises en compte pour les suggestions en ligne pendant que vous tapez, ce qui est une des raisons pour lesquelles la couche de linting ci-dessous compte. C'est tout de même là que vivent les garde-fous les plus impactants.

Un fichier comme celui-ci fonctionne mieux réparti sur six préoccupations : contexte du projet, règles de sécurité, traitement des données, patterns spécifiques à Angular, standards de qualité du code, et choses que l'IA ne doit jamais faire. La section 'ne jamais faire' est la plus importante. Elle est explicite et impérative. L'exemple ci-dessous suit cette structure. Tout ce qu'il contient, y compris chaque nom de service, est inventé pour ce billet : traitez-le comme une forme à remplir plutôt que comme un ensemble de règles à copier.

markdown
<!-- .github/copilot-instructions.md -->
<!-- ILLUSTRATIVE. Written for this blog post, invented end to end. -->
<!-- Every service name below is made up. This is not BMO's file, and the -->
<!-- rules are the kind a bank would write, not the ones any bank did write. -->

# Project context
This is an enterprise Angular application for a regulated financial institution.
- Angular version: <your exact major version>
- State management: NgRx with strict action hygiene.
- Styling: SCSS with design tokens via CSS custom properties.
- Testing: Jest for unit tests.
- All API calls go through ApiClientService, never directly via HttpClient.

# Security rules (mandatory)
- Never suggest storing sensitive data (tokens, credentials, PII, account numbers) in localStorage or sessionStorage.
- Never generate code that logs objects containing user data, account information, or transaction details.
- Never suggest direct third-party API calls. All external communication goes through ApiClientService.
- Never generate hardcoded credentials, API keys, environment-specific URLs, or secrets of any kind.
- Never suggest eval(), Function(), or other dynamic code execution.
- Validate and sanitize input on the client, and never assume the client check replaces the server one.
- Tokens live in httpOnly cookies managed by AuthService. Do not suggest alternatives.

# Data handling
- Display PII fields (SIN, date of birth, account numbers, name plus address combinations) through PiiMaskingService.
- Audit-sensitive operations call AuditService.record() before the operation runs.
- Check consent through ConsentService before processing marketing-related data.
- Never suggest patterns that send customer data outside approved service boundaries.

# Angular patterns
- Match the component style already used in the surrounding code. Do not mix styles in one feature.
- OnPush change detection is the default. Justify any use of the Default strategy.
- Every subscription needs a teardown. Prefer AsyncPipe, or the teardown pattern the codebase already uses.
- Route errors through the global ErrorHandler, not ad-hoc try/catch with console.error.
- Feature flags go through FeatureFlagService.isEnabled(). Never hardcode feature conditionals.
- Avoid any. If you cannot type something precisely, use unknown and narrow it.

# Code quality
- Public methods have JSDoc comments.
- Flag functions longer than 30 lines for decomposition.
- Magic numbers and strings become named constants.
- New services come with a *.spec.ts that meets the coverage threshold enforced in CI.

# Never do
- Do not suggest code that bypasses the established service layer.
- Do not generate commented-out code blocks.
- Do not add TODO comments without a ticket reference.
- Do not propose architectural changes without flagging them as architectural suggestions that need review.

Instructions spécifiques à un chemin et exclusion de contenu

Les règles qui ne concernent qu'une partie du codebase vont dans des fichiers spécifiques à un chemin, sous .github/instructions/, nommés NOM.instructions.md, avec un glob applyTo dans le frontmatter. Quand un fichier correspond, Copilot combine ces instructions avec celles valables pour tout le dépôt. Les conventions de test sont un bon candidat : elles comptent dans chaque fichier spec et nulle part ailleurs. Voici un .github/instructions/testing.instructions.md donné à titre d'exemple :

markdown
---
applyTo: "**/*.spec.ts"
---

<!-- ILLUSTRATIVE. Written for this blog post, not a real bank configuration. -->

- Unit tests use Jest. Follow the describe/it structure of the neighboring spec files.
- Cover the happy path, boundary values, null and undefined inputs, and the error branch.
- Never put realistic customer data in fixtures. Use values that are obviously fake.
- Mock HTTP at the service boundary. A unit test never calls a real endpoint.
- Every assertion checks behavior. A test that only proves the component was created does not count as coverage.

Les instructions façonnent ce que Copilot écrit. L'exclusion de contenu contrôle ce qu'il peut voir. Sur les plans Business et Enterprise, les administrateurs de dépôt et les propriétaires d'organisation peuvent exclure des fichiers et des répertoires pour que Copilot ne les utilise pas comme contexte. Les exclusions se saisissent dans l'UI GitHub (Settings du dépôt, puis Copilot, puis Content exclusion, ou l'équivalent au niveau de l'organisation), pas dans un fichier versionné. C'est ce qui en fait le bon outil pour les fichiers d'implémentation les plus sensibles. Une liste illustrative au niveau du dépôt :

yaml
# Illustrative repository-level content exclusion, not a real configuration.
# Entered in the GitHub UI under Settings > Copilot > Content exclusion.
# Mirrored here for documentation only. Copilot does not read this file.
- "/src/environments/**"   # environment configuration
- "**/secrets/**"          # secret management utilities
- "*.pem"
- "*.key"
- "*.env*"

Les couches qui appliquent vraiment : ESLint et le gate CI

Les garde-fous ne peuvent pas reposer sur le respect des instructions par l'IA, parce qu'une instruction est une demande et qu'un modèle est libre d'ignorer une demande. La couche de linting détecte les anti-patterns au niveau du code, peu importe qui ou quoi les a tapés. Un détail qui compte plus qu'il n'y paraît : écrivez la règle contre l'AST, pas contre une chaîne de caractères. Une version naïve qui n'inspecte qu'un identifiant nu passe à côté de window.localStorage et des accès calculés comme window['localStorage'], qui est exactement la forme vers laquelle un modèle se tourne quand on lui a interdit la forme simple.

typescript
// eslint-rules/no-web-storage.js (illustrative example)
// Disallow localStorage/sessionStorage. Session tokens live in httpOnly
// cookies managed by AuthService, which is what the instructions file says too.
module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'Disallow direct localStorage/sessionStorage access',
      category: 'Security'
    },
    messages: {
      noWebStorage:
        'Do not use {{ storage }} directly. Tokens live in httpOnly cookies managed by AuthService. See: <link to your storage guidelines>'
    }
  },
  create(context) {
    const STORAGES = ['localStorage', 'sessionStorage'];

    // Matches localStorage.x, window.localStorage.x and window['localStorage'].x
    const storageName = (object) => {
      if (object.type === 'Identifier' && STORAGES.includes(object.name)) {
        return object.name;
      }
      if (object.type === 'MemberExpression') {
        const key = object.computed
          ? (object.property.type === 'Literal' ? object.property.value : null)
          : object.property.name;
        return STORAGES.includes(key) ? key : null;
      }
      return null;
    };

    return {
      MemberExpression(node) {
        const storage = storageName(node.object);
        if (!storage) return;
        context.report({ node, messageId: 'noWebStorage', data: { storage } });
      }
    };
  }
};

ESLint n'est que la moitié de l'application des règles, et la plus petite moitié. L'autre moitié, c'est le gate CI, et c'est la couche qui décide de ce qui est mergé. Le lint et le seuil de couverture Jest s'exécutent sur chaque pull request, et les quality gates SonarQube s'exécutent en parallèle. Un gate en échec bloque le merge, et il se moque de savoir si la ligne a été écrite par une personne ou par un modèle. Toute la distinction est là : le fichier d'instructions est une orientation, et une orientation peut être ignorée, alors que le pipeline n'est pas négociable. Si votre équipe n'a l'appétit que pour une seule couche ce trimestre, construisez celle-là. Des instructions sans gate derrière elles, c'est un guide de style.

Ce que des règles de ce type préviennent

Les catégories sont prévisibles. Dans le code front-end écrit avec l'aide de l'IA, les quatre mêmes reviennent sans cesse : utilisation directe de localStorage pour l'état d'authentification, appels console.log qui sérialisent des payloads de réponse complets, utilisation directe de HttpClient qui contourne la couche de services, et annotations typées any, qui affaiblissent la sécurité de typage.

Ce ne sont pas des bugs de sécurité hypothétiques. Ce sont les patterns que les outils IA adoptent par défaut, parce que les tutoriels Angular génériques en sont remplis. Les garde-fous ne rendent pas les outils moins utiles. Ils rendent le résultat suffisamment fiable pour être inclus en revue de code plutôt que de traiter chaque suggestion IA comme suspecte par défaut.

Les limites des garde-fous

Je veux être clair sur ce que les garde-fous ne peuvent pas faire. Ils ne peuvent pas prévenir les erreurs d'architecture. Ils ne peuvent pas détecter les erreurs de logique métier dans des règles de domaine complexes. Ils ne peuvent pas vérifier qu'un algorithme généré par IA produit des résultats corrects pour les cas limites. Ils ne peuvent pas remplacer la revue de code. Ce qu'ils font, c'est imposer un plancher : un ensemble de patterns non négociables qui ne devraient jamais apparaître quelle que soit la tâche.

  • Les garde-fous sont un plancher, pas un plafond. Ils préviennent les pires résultats, pas tous les résultats sous-optimaux
  • Les instructions personnalisées sont des suggestions pour l'IA, pas des contraintes dures, et elles n'atteignent pas du tout les suggestions en ligne. Le pipeline, lint plus tests plus quality gates, est l'application réelle
  • Les garde-fous doivent être mis à jour à mesure que le codebase évolue. Des règles obsolètes créent de la confusion, puis finissent ignorées
  • Les ingénieurs doivent savoir pourquoi chaque règle existe. Des règles sans justification sont contournées
  • L'exclusion de contenu est le contrôle le plus fiable sur ce que Copilot peut voir, ce qui est une question différente de ce qui part en production. Le contenu exclu n'est pas utilisé comme contexte. GitHub documente que le mode Agent de Copilot Chat ne prend pas en charge l'exclusion de contenu, alors vérifiez quels modes votre équipe utilise réellement

L'objectif n'est pas de mettre l'IA en cage. L'objectif est de rendre les suggestions de l'IA suffisamment dignes de confiance pour que les développeurs puissent concentrer leur énergie de revue sur la logique et la justesse métier, plutôt que sur la détection d'anti-patterns de sécurité que le linting devrait détecter automatiquement.

Si vous configurez des outils IA dans un environnement réglementé, commencez par la liste des 'ne jamais faire'. Quelles sont les trois catégories de code qui, si elles étaient générées et mergées sans revue, causeraient un incident ? Écrivez ces règles en premier, appliquez-les avec du linting, et construisez les directives positives à partir de là. Mettre cela en place n'est pas un projet d'un trimestre, et ce n'est jamais fini non plus. J'ai continué de l'affiner à mesure que le codebase et les outils changeaient. C'est cadré sur une seule équipe, pas un template à l'échelle de l'organisation. C'est suffisant pour changer la façon dont une équipe de cinq développeurs travaille, et c'est un meilleur point de départ que d'essayer de standardiser toute l'organisation avant que quiconque ait utilisé les règles en conditions réelles.

Références

AIEnterpriseBankingDeveloper ToolsAngular