Módulo 4: Bedrock Guardrails And Defense In Depth

4. Por qué un guardrail gestionado no basta solo

Descripción

La lección 3 dejó un guardrail con seis mecanismos activos, declarado en HCL, validate/plan reales. Es una capa seria. Esta lección explica, con precisión y sin exagerar el riesgo en ninguna dirección, exactamente dónde esa capa termina — no porque Bedrock Guardrails esté mal diseñado, sino porque hay preguntas que, por su propia naturaleza, un guardrail de seguridad de contenido nunca fue construido para contestar. Esas preguntas son, precisamente, el trabajo de pre_invoke_checks.py y post_invoke_checks.py, las lecciones 5 y 6.

Conexión con el módulo

Esta es la lección bisagra del módulo: todo lo anterior (lecciones 2 y 3) construyó la mitad gestionada; todo lo que sigue (lecciones 5 y 6) construye la mitad propia. Esta lección es el puente que explica, con argumentos concretos —no con una advertencia genérica de "nunca confíes en una sola capa"—, por qué esa segunda mitad es trabajo real, no una formalidad de checklist.


Analogía: el filtro de spam de tu correo, y tu propio criterio al abrir un adjunto

El filtro de spam de cualquier proveedor de correo serio es bueno — de verdad bueno. Aprende de millones de mensajes, bloquea la enorme mayoría de intentos de phishing obvios, y rara vez deja pasar algo genuinamente peligroso sin, al menos, marcarlo con una advertencia. Pero ningún administrador de sistemas competente concluye, por eso, que ya no hace falta que un empleado piense antes de abrir un adjunto inesperado de un remitente que no reconoce, aunque ese correo haya pasado el filtro sin ninguna alerta. El filtro de spam evalúa patrones generales de un correo malicioso — no sabe que ese empleado específico nunca esperaría un adjunto de ese remitente específico, en este momento específico, para este trabajo específico. Esa segunda evaluación —contextual, específica del caso, imposible de generalizar en una regla que sirva para todos los correos de todo el mundo— es exactamente el trabajo que pre_invoke_checks.py y post_invoke_checks.py hacen para extract-shipment-manifest-fields: no repiten lo que el filtro de spam (Bedrock Guardrails) ya hace bien, cubren lo que ningún filtro genérico, sin importar cuán bueno sea, podría cubrir por diseño.


Lo que Bedrock Guardrails cubre de verdad, sin restarle mérito

Antes de hablar de sus límites, vale la pena decir con precisión lo que la lección 2 y 3 ya demostraron: seis mecanismos, cinco políticas, declarados como código real, validate/plan confirmados. Un guardrail gestionado bien configurado —el de Andes Cargo, específicamente— detendría, de verdad, un intento de ataque de prompt con fuerza HIGH, anonimizaría un correo o teléfono incidental en el texto de un manifiesto, bloquearía una solicitud de ayuda para evadir aduanas disfrazada de "pregunta sobre el envío", exigiría que la respuesta esté fundamentada en el texto original con un umbral de confianza del 0.75, y filtraría profanidad y dos frases específicas del riesgo de negocio de Andes Cargo. Todo eso es real, es valioso, y ninguna parte de esta lección lo cuestiona.


Lo que se le escapa, con precisión, no con vaguedad

Brecha 1 — El contrato exacto de negocio, ShipmentFields

Ningún mecanismo de la lección 2 pregunta "¿esta respuesta tiene exactamente los cinco campos que Shipments necesita, ni uno más, ni uno menos?". content_policy_config pregunta si hay contenido dañino. topic_policy_config pregunta si el tema es uno prohibido. contextual_grounding_policy_config pregunta si la respuesta está fundamentada en la fuente. Ninguna de las seis preguntas de Bedrock Guardrails es "¿esto tiene la forma correcta para el sistema que lo va a recibir?" — porque esa pregunta no es de seguridad de contenido, es del contrato específico de la aplicación de Andes Cargo, algo que un servicio genérico, compartido por miles de aplicaciones distintas con formas de salida completamente distintas entre sí, no podría conocer sin que tú se lo enseñes explícitamente (algo que, hoy, el schema de aws_bedrock_guardrail no expone como mecanismo).

Brecha 2 — Una respuesta puede pasar las seis políticas y seguir siendo inutilizable

Este es el punto más concreto de esta lección, y vale la pena verlo con un ejemplo hecho a mano, no en abstracto. Imagina una respuesta representativa del modelo —nunca invocada de verdad, ver lección 7— que dice, en JSON:

{
  "shipmentId": "4474",
  "originCountry": "Bolivia",
  "destinationCountry": "Chile",
  "carrier": "AndesExpress"
}

Sin weightKg. Recorre las seis políticas de la lección 2 contra esta respuesta: ningún ataque de prompt (PROMPT_ATTACK evalúa la entrada, no aplica aquí de todas formas). Ningún correo ni teléfono (sensitive_information_policy_config, limpio). Ningún tema prohibido (topic_policy_config, limpio — esto no menciona contrabando ni evasión aduanera). Perfectamente fundamentada en una fuente hipotética que sí mencionaba estos cuatro datos (contextual_grounding_policy_config, aprobaría sin problema). Sin profanidad ni las dos frases personalizadas (word_policy_config, limpio). Las seis políticas de Bedrock Guardrails aprobarían esta respuesta sin ninguna objeción — y, sin embargo, escribirla en Shipments tal cual rompería cualquier código que espere el campo weightKg, porque el modelo, simplemente, no lo extrajo. Este es exactamente el caso que post_invoke_checks.py (lección 6) existe para atrapar, y que ningún guardrail gestionado, con la configuración que sea, podría atrapar por sí solo — no fue diseñado para esa pregunta.

   LA BRECHA EXACTA, EN UN DIAGRAMA

   Respuesta del modelo
        │
        ▼
   Bedrock Guardrails (6 mecanismos)
        │
        │  content_policy_config .......... PASA (sin contenido dañino)
        │  sensitive_information_policy .... PASA (sin PII)
        │  topic_policy_config ............. PASA (sin tema prohibido)
        │  contextual_grounding_policy ..... PASA (fundamentada en la fuente)
        │  word_policy_config .............. PASA (sin palabras prohibidas)
        ▼
   "Contenido seguro" -- según las 6 políticas
        │
        │  Pero: falta weightKg. Bedrock Guardrails nunca evaluó esto --
        │  no es una pregunta de seguridad de contenido, es del contrato
        │  de ShipmentFields, específico de esta aplicación.
        ▼
   post_invoke_checks.py (lección 6) -- ESTA capa sí pregunta por la forma
        │
        ▼
   FAIL: missing required field(s): weightKg -- nunca llega a Shipments

Brecha 3 — Parte del propio guardrail gestionado es probabilístico, no determinista

La lección 2 ya citó, textualmente, cómo AWS describe su propio mecanismo de PII: "This filter is a probabilistic machine learning (ML) based solution that is context-dependent". Es una fortaleza real —permite detectar PII incluso cuando no sigue un patrón exacto—, pero también significa que, técnicamente, no hay garantía absoluta de que el mismo texto produzca siempre el mismo resultado de detección, de la misma forma en que un modelo de lenguaje no es 100% determinista (Módulo 1, lección 2). pre_invoke_checks.py (lección 5), en cambio, es una expresión regular pura: el mismo texto de entrada produce, siempre, exactamente el mismo resultado, en cualquier máquina, sin excepción — una garantía distinta, complementaria, no una que reemplace a la otra. Donde el guardrail gestionado aporta comprensión contextual amplia, el chequeo propio aporta reproducibilidad exacta sobre un conjunto conocido de patrones.

Brecha 4 — Independencia operativa: qué pasa si el guardrail, por cualquier razón, no se aplica

Un guardrail de Bedrock solo protege una invocación si esa invocación efectivamente lo referencia —un parámetro guardrailIdentifier en la llamada a InvokeModel/Converse, algo que vive en el código de extract-shipment-manifest-fields, no en el guardrail mismo—. Un error humano al escribir esa llamada (un ID de guardrail equivocado, un despliegue que omite el parámetro por accidente) dejaría la invocación corriendo sin ninguna de las seis políticas activas, sin que terraform plan ni terraform validate pudieran detectarlo jamás — porque el guardrail, como recurso, seguiría existiendo, correctamente configurado, simplemente no estaría siendo usado en esa llamada específica. pre_invoke_checks.py y post_invoke_checks.py, en cambio, no dependen de que nadie recuerde pasar un parámetro correctamente: si están integrados en el flujo del extractor (lección 8 de este módulo), corren siempre, sin ninguna posibilidad de "olvidarlos" en una sola invocación.


Errores comunes

Concluir, de esta lección, que Bedrock Guardrails "no sirve" o es "solo para mostrar" (de sobre-corrección hacia el otro extremo). Qué pasa: alguien, después de ver las cuatro brechas de esta lección, decide que el guardrail gestionado es prescindible y que bastaría con los dos chequeos propios. Cómo detectarlo: si tu plan es quitar manifest_extractor_guardrail de bedrock.tf porque "de todas formas hay chequeos propios". Cómo corregirlo: relee la sección "Lo que Bedrock Guardrails cubre de verdad" de esta misma lección — la detección de ataques de prompt, el catálogo completo de 31 tipos de PII, y el grounding contextual son capacidades reales que ni pre_invoke_checks.py ni post_invoke_checks.py intentan replicar (ninguno de los dos detecta un ataque de prompt ni evalúa fundamentación factual). Quitar el guardrail gestionado dejaría exactamente las brechas inversas a las de esta lección — la respuesta correcta es defensa en profundidad, dos capas activas a la vez, nunca una sustituyendo a la otra.

Pensar que la Brecha 2 (una respuesta puede pasar las 6 políticas y seguir siendo inválida) es un defecto de la configuración de Andes Cargo, corregible agregando más políticas (de creer que el problema es de configuración, no de alcance). Qué pasa: alguien intenta "arreglar" la Brecha 2 buscando una séptima política de Bedrock Guardrails que valide esquemas de JSON. Cómo detectarlo: si tu reacción a la Brecha 2 es buscar en la documentación de AWS un mecanismo de validación de esquema dentro de Guardrails. Cómo corregirlo: no existe, y no es un descuido de AWS —Bedrock Guardrails es, por diseño, un servicio de seguridad de contenido, compartido por aplicaciones con formas de salida completamente distintas entre sí (un chatbot de atención al cliente, un generador de resúmenes, el extractor de Andes Cargo); no podría, estructuralmente, conocer el contrato específico de ShipmentFields sin que la aplicación misma se lo enseñara. Esa enseñanza específica es, precisamente, lo que post_invoke_checks.py provee — no una política que falta, una responsabilidad que nunca le correspondió al guardrail gestionado.

Asumir que la Brecha 3 (parte del guardrail gestionado es probabilístico) significa que Bedrock Guardrails es poco confiable (de malinterpretar "probabilístico" como "impredecible al punto de no servir"). Qué pasa: alguien lee la cita oficial de AWS sobre el mecanismo de PII y concluye que el guardrail gestionado podría fallar en detectar PII obvia con frecuencia. Cómo detectarlo: si tu conclusión de la Brecha 3 es "entonces el filtro de PII de Bedrock no es confiable". Cómo corregirlo: "probabilístico y dependiente de contexto" es, en este caso, una fortaleza frente a una expresión regular simple —permite detectar variaciones de formato, contexto ambiguo, y patrones que una regex fija no anticiparía—; la propia documentación de AWS lo presenta así, no como una advertencia. El punto de esta lección no es que sea poco confiable, es que no ofrece la misma garantía de reproducibilidad exacta que una regex determinista —dos propiedades distintas, ninguna estrictamente superior a la otra, razón exacta por la que valen la pena las dos, juntas.


Ejercicios

Ejercicio 1 — Vuelve al ejemplo de JSON sin weightKg de la Brecha 2. Explica, política por política, por qué cada una de las cinco (excluyendo el ataque de prompt, que no aplica a una salida) aprobaría esa respuesta sin objeción.

Ver solución

sensitive_information_policy_config aprobaría porque ninguno de los cuatro valores presentes (shipmentId, originCountry, destinationCountry, carrier) coincide con ninguno de los 31 tipos de entidad de PII del catálogo. topic_policy_config aprobaría porque el contenido —datos de un envío legítimo— no coincide con la definición de ProhibitedShipmentGuidance. contextual_grounding_policy_config aprobaría porque, en este ejemplo hipotético, los cuatro campos presentes sí estarían fundamentados en la fuente (el problema no es que algo esté inventado, es que algo falta). word_policy_config aprobaría porque ninguna palabra prohibida ni las dos frases personalizadas aparecen en el texto. Ninguna de las cuatro, ni las otras dos categorías de content_policy_config (que de todas formas no están activas para este caso, según la lección 3), tiene ningún mecanismo para preguntar "¿faltan campos?" — esa pregunta, simplemente, no existe en su diseño.

Ejercicio 2 — Explica, con tus propias palabras, la diferencia entre "un guardrail gestionado no cubre esto" y "un guardrail gestionado está mal configurado". ¿A cuál de las dos categorías pertenece cada una de las cuatro brechas de esta lección?

Ver solución

"Mal configurado" significa que existe un mecanismo capaz de resolver el problema, pero nadie lo activó o lo activó con parámetros incorrectos —por ejemplo, si Andes Cargo hubiera olvidado declarar sensitive_information_policy_config y una respuesta filtrara un correo real, eso sería un guardrail mal configurado, corregible dentro del propio bedrock.tf—. "No cubre esto" significa que ningún mecanismo del servicio, sin importar cómo se configure, podría resolver el problema por diseño. Las cuatro brechas de esta lección son todas del segundo tipo: ninguna cantidad de configuración adicional de aws_bedrock_guardrail agregaría una validación de esquema de negocio (Brecha 1 y 2), convertiría un chequeo probabilístico en uno determinista (Brecha 3), o protegería contra el olvido de referenciar el guardrail en una llamada específica (Brecha 4) — son límites estructurales del servicio, no errores de configuración de Andes Cargo.

Ejercicio 3 — La Brecha 4 (independencia operativa) es la única de las cuatro que no depende de qué políticas tenga activas el guardrail. Explica por qué, y qué tipo de error humano específico ilustra mejor este riesgo.

Ver solución

Las Brechas 1, 2 y 3 son sobre qué evalúa el guardrail una vez que corre — no importa cuántas políticas tenga activas, hay preguntas que nunca contestaría. La Brecha 4 es distinta: es sobre si el guardrail corre en absoluto para una invocación específica. Un guardrail con las seis políticas perfectamente configuradas no protege nada si la llamada a InvokeModel/Converse no incluye el parámetro que lo referencia — el error más ilustrativo es, precisamente, ese: alguien despliega una nueva versión de extract-shipment-manifest-fields y, por un error de copiar-pegar o una variable de entorno mal configurada, la llamada sale sin guardrailIdentifier. Ningún terraform plan detectaría esto —el guardrail, como recurso de infraestructura, sigue existiendo perfectamente bien—; sería un error puramente de código de aplicación, invisible para cualquier herramienta de este módulo salvo, precisamente, los chequeos propios que corren siempre, sin depender de que nadie los invoque correctamente desde otro lugar.


Resumen y siguiente paso

Esta lección trazó, con un ejemplo concreto de JSON —no una advertencia genérica—, las cuatro brechas exactas que un guardrail gestionado, por bien configurado que esté, deja abiertas: el contrato específico de ShipmentFields, respuestas que pasan las seis políticas y siguen siendo inutilizables, la naturaleza probabilística de parte de su propio mecanismo, y la dependencia operativa de que el código de aplicación lo referencie correctamente en cada llamada. Ninguna de las cuatro es un defecto de Bedrock Guardrails ni de la configuración de Andes Cargo — son límites estructurales de lo que un servicio de seguridad de contenido, compartido por miles de aplicaciones distintas, puede saber sobre el contrato específico de una sola de ellas.

Antes de avanzar deberías poder: explicar la Brecha 2 con tu propio ejemplo de JSON, distinto al de esta lección; distinguir "no cubre esto" de "está mal configurado"; y anticipar, sin que la lección 5 te lo confirme todavía, que el chequeo de PII propio va a ser una expresión regular, no un modelo de lenguaje.

Las lecciones 5 y 6 construyen las dos piezas que cierran estas brechas: pre_invoke_checks.py, determinista, corriendo antes de la llamada al modelo; post_invoke_checks.py, determinista, corriendo después, validando exactamente el contrato de ShipmentFields que esta lección demostró que Bedrock Guardrails nunca evalúa.

Recursos

  1. AWS Docs — Sensitive information filters — fuente de la cita textual sobre el mecanismo probabilístico de PII (Brecha 3).
  2. AWS Docs — How Amazon Bedrock Guardrails works — confirma que un guardrail solo protege una invocación que lo referencia explícitamente (Brecha 4).
  3. Este módulo, lección 2 (02-the-six-bedrock-guardrails-policies-explained.md) — la base de las seis políticas cuya cobertura exacta esta lección delimita.
  4. Módulo 1, lección 2 de esta guía (02-a-notebook-and-a-production-system-are-not-the-same-problem.md) — la fuente del argumento sobre no-determinismo, retomado aquí para la Brecha 3.