Volver al Blog
AI & Development26 de abril de 202611 min de lectura

Cómo configuré AI Guardrails en BMO

DR

Doug Ribeiro

11 min de lectura

En un banco, la primera pregunta sobre un asistente de codificación con IA rara vez es '¿qué puede hacer esta herramienta?' Es '¿qué podemos asegurarnos de que nunca haga?' Ese enfoque moldeó todo cuando configuré el asistente de mi equipo en BMO, GitHub Copilot Business. Los guardrails en un entorno bancario no se tratan de limitar la herramienta, sino de hacerla lo suficientemente confiable para usarla en un codebase regulado.

Lo que sigue es el enfoque: las capas en las que me apoyo, las categorías de riesgo que vale la pena cubrir y cómo luce una configuración así en la práctica. Los archivos de este post son ejemplos ilustrativos que escribí para él, no la configuración del banco, que se queda interna. Lo que se traslada a tu propio codebase es la estructura y el razonamiento.

Qué significan realmente los Guardrails en un entorno regulado

En una startup de tecnología de consumo, los guardrails de IA podrían significar 'no dejar que Copilot sugiera generación insegura de números aleatorios.' En un banco, el alcance es considerablemente más amplio. Un banco canadiense opera bajo las directrices de OSFI, los requisitos de FINTRAC, PCI DSS para datos de tarjetas de pago, y políticas de clasificación de datos que determinan qué puede hacer el código con qué categorías de información.

El modelo de amenazas para codificación asistida por IA en banca tiene tres capas. Primero, riesgo de exfiltración de datos: código que envía inadvertidamente datos de clientes a servicios externos. Segundo, anti-patrones de seguridad: sugerencias que introducen vulnerabilidades conocidas, como credenciales hardcodeadas, validación insuficiente de inputs o referencias directas inseguras a objetos. Tercero, violaciones de compliance: código que omite el logging de auditoría, ignora flags de consentimiento, o procesa datos de maneras que violan requisitos de residencia de datos.

Nada de esto es hipotético. Sin instrucciones, un asistente de IA sugerirá sin problema localStorage para almacenar un token de sesión de usuario (un anti-patrón de seguridad en un contexto bancario), una llamada HTTP directa a una API de terceros sin enrutamiento por proxy (un problema de gobernanza de datos), o una sentencia console.log que imprime un objeto de cliente completo (una violación de logging de PII). La revisión de código detecta estos casos. Los guardrails se tratan de detectarlos antes de que lleguen a un revisor humano.

Capa de instrucciones de IA.github/copilot-instructions.md y archivos de instrucciones por ruta. P… 1 Capa de análisis estáticoReglas personalizadas de ESLint. Detectan violaciones que se escaparon … 2 Capa de revisión de códigoGates de revisión humana. Detectan desajustes de intención que ninguna … 3 Quality gate de CIESLint, el umbral de cobertura de Jest y los quality gates de SonarQube… 4

Instrucciones personalizadas de repositorio en Copilot

Copilot lee las instrucciones personalizadas para todo el repositorio desde .github/copilot-instructions.md, un simple archivo Markdown con instrucciones en lenguaje natural versionado junto al código. Piénsalo como un system prompt que viaja con el repositorio y se agrega a cada petición de chat. GitHub lo documenta para Copilot Chat, code review y el agente en la nube, y la documentación de VS Code aclara que las instrucciones personalizadas no se toman en cuenta para las sugerencias inline mientras escribes, que es una de las razones por las que la capa de linting de abajo importa. Aquí es donde siguen viviendo los guardrails de mayor impacto.

Un archivo así funciona mejor dividido en seis áreas: contexto del proyecto, reglas de seguridad, manejo de datos, patrones específicos de Angular, estándares de calidad de código y cosas que la IA nunca debe hacer. La sección de 'nunca hacer' es la más importante: es explícita e imperativa. El ejemplo de abajo sigue esa estructura. Todo lo que contiene, incluido cada nombre de servicio, está inventado para este post, así que trátalo como una forma para llenar y no como un conjunto de reglas para copiar.

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.

Instrucciones por ruta y exclusión de contenido

Las reglas que sólo importan para una parte del codebase van en archivos por ruta bajo .github/instructions/, con el nombre NOMBRE.instructions.md y un glob applyTo en el frontmatter. Cuando un archivo coincide, Copilot combina esas instrucciones con las de todo el repositorio. Las convenciones de testing son un buen candidato: importan en cada archivo spec y en ningún otro lado. Este es un .github/instructions/testing.instructions.md a modo de ejemplo:

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.

Las instrucciones moldean lo que Copilot escribe. La exclusión de contenido controla lo que puede ver. En los planes Business y Enterprise, los administradores de repositorio y los owners de la organización pueden excluir archivos y directorios para que Copilot no los use como contexto. Las exclusiones se cargan en la UI de GitHub (Settings del repositorio, luego Copilot, luego Content exclusion, o el equivalente a nivel de organización), no en un archivo versionado. Eso las convierte en la herramienta adecuada para los archivos de implementación más sensibles. Una lista ilustrativa a nivel de repositorio:

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*"

Las capas que realmente hacen enforcement: ESLint y el gate de CI

Los guardrails no pueden depender de que la IA respete las instrucciones, porque una instrucción es un pedido y un modelo es libre de ignorar un pedido. La capa de linting detecta los anti-patrones a nivel de código, sin importar quién o qué los escribió. Un detalle que importa más de lo que parece: escribe la regla contra el AST, no contra un string. Una versión ingenua que sólo inspecciona un identificador suelto se pierde window.localStorage y los accesos computados como window['localStorage'], que es justo la forma a la que recurre un modelo cuando le dijeron que no use la forma 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 es sólo la mitad del enforcement, y la mitad más chica. La otra mitad es el gate de CI, y esa es la capa que decide qué se mergea. El lint y el umbral de cobertura de Jest corren en cada pull request, y los quality gates de SonarQube corren junto a ellos. Un gate que falla bloquea el merge, y no le importa si la línea la escribió una persona o un modelo. Esa es toda la distinción: el archivo de instrucciones es una guía, y una guía se puede ignorar, mientras que el pipeline no es negociable. Si tu equipo sólo tiene apetito para construir una capa este trimestre, construye esa. Instrucciones sin un gate detrás son una guía de estilo.

Lo que previenen reglas como estas

Las categorías son predecibles. En código front-end escrito con ayuda de IA, las mismas cuatro aparecen una y otra vez: uso directo de localStorage para estado de autenticación, llamadas console.log que serializan payloads de respuesta completos, uso directo de HttpClient que omite la capa de servicios, y anotaciones tipadas como any, que debilitan la seguridad de tipos.

Estos no son bugs de seguridad hipotéticos. Son los patrones que las herramientas de IA usan por defecto, porque los tutoriales genéricos de Angular están llenos de ellos. Los guardrails no hacen las herramientas menos útiles. Hacen que el output sea lo suficientemente confiable para incluirlo en la revisión de código en lugar de tratar cada sugerencia de IA como sospechosa por defecto.

Los límites de los Guardrails

Quiero ser claro sobre lo que los guardrails no pueden hacer. No pueden prevenir errores arquitectónicos. No pueden detectar errores de lógica de negocio en reglas de dominio complejas. No pueden verificar que un algoritmo generado por IA produzca outputs correctos para casos borde. No pueden reemplazar la revisión de código. Lo que hacen es establecer un piso: un conjunto de patrones no negociables que nunca deberían aparecer independientemente de la tarea.

  • Los guardrails son un piso, no un techo: previenen los peores resultados, no todos los subóptimos
  • Las instrucciones personalizadas son sugerencias para la IA, no restricciones duras, y no llegan a las sugerencias inline en absoluto. El pipeline, lint más tests más quality gates, es el enforcement real
  • Los guardrails necesitan actualizarse a medida que el codebase evoluciona: reglas desactualizadas crean confusión y después se ignoran
  • Los ingenieros necesitan saber por qué existe cada regla: las reglas sin justificación se eluden
  • La exclusión de contenido es el control más confiable sobre lo que Copilot puede ver, que es una pregunta distinta de lo que llega a producción. El contenido excluido no se usa como contexto. GitHub documenta que el modo Agent de Copilot Chat no soporta exclusión de contenido, así que revisa qué modos usa realmente tu equipo

El objetivo no es enjaular a la IA. El objetivo es hacer que las sugerencias de la IA sean lo suficientemente confiables para que los developers puedan enfocar su energía de revisión en la lógica y la correctitud del negocio, no en detectar anti-patrones de seguridad que el linting debería detectar automáticamente.

Si estás configurando herramientas de IA en un entorno regulado, comienza con la lista de 'nunca hacer'. ¿Cuáles son las tres categorías de código que, si se generan y se fusionan sin revisión, causarían un incidente? Escribe esas reglas primero, aplícalas con linting, y construye la guía positiva a partir de ahí. Montar esto no es un proyecto de un trimestre, y tampoco se termina nunca. Lo seguí refinando a medida que cambiaban el codebase y las herramientas. Está acotado a un solo equipo, no es una plantilla para toda la organización. Eso es suficiente para cambiar cómo trabaja un equipo de cinco desarrolladores, y es un mejor punto de partida que intentar estandarizar toda la organización antes de que alguien haya usado las reglas en serio.

Referencias

AIEnterpriseBankingDeveloper ToolsAngular