$bot_message in an output rail
$bot_message is the Colang variable that holds the reply during an output rail; an action reads it from context["bot_message"].
Last updated: 30 Sep, 2026 · NeMo Guardrails 0.24.1
The output rail in Output rails asked a model. The video's output rail is a regex action instead: the output sanitizer from its notebook.
Syntax:
bot_message = context.get("bot_message", "") # in the action
# $sensitive_found = execute sanitize_output # in the flowThe sanitizer from the video
Three patterns: a hardcoded credential, a private key header, and exploit tools such as a reverse shell. Save it as actions.py.
import re
from typing import Optional
from nemoguardrails.actions import action
@action(is_system_action=True)
async def sanitize_output(context: Optional[dict] = None):
"""Intercepts bot responses containing hardcoded credentials or exploit techniques."""
bot_message = context.get("bot_message", "") if context else ""
sensitive_output_patterns = {
"hardcoded_credential": r"(?i)(password|passwd|secret|api[_\-]?key|token)\s*[:=]\s*['\"]?\w{4,}",
"private_key": r"-----BEGIN.{0,20}PRIVATE KEY-----",
"exploit_technique": r"(?i)\b(reverse.?shell|bind.?shell|shellcode|meterpreter)\b",
}
found = [ptype for ptype, pat in sensitive_output_patterns.items()
if re.search(pat, bot_message)]
return found # empty list = clean = falsyThe output flow
define bot sanitize sensitive output
"My response may have contained sensitive security details (credentials, exploit code, or private keys). For safety, that content has been withheld. Please consult your security team."
define flow sanitize bot response
$sensitive_found = execute sanitize_output
if $sensitive_found
bot sanitize sensitive output
stoprails:
output:
flows:
- sanitize bot response- written in define user and define bot
- written in define user and define bot
View the code here
from nemoguardrails.embeddings.index import EmbeddingsIndex
class EveryExample(EmbeddingsIndex):
"""Hands the model every example instead of the closest few."""
def __init__(self, **kwargs):
self.items = []
async def add_items(self, items):
self.items.extend(items)
async def build(self):
pass
async def search(self, text, max_results=5, threshold=None):
return self.items
def init(app):
app.register_embedding_search_provider("every_example", EveryExample)
models:
- type: main
engine: openai
model: openai/gpt-oss-20b
api_key_env_var: GROQ_API_KEY
parameters:
base_url: https://api.groq.com/openai/v1
temperature: 0
instructions:
- type: general
content: |
You are an Enterprise IT Assistant specialising in Kubernetes,
Intel hardware, and enterprise networking.
Only answer questions about these topics.
Answer in one or two short sentences.
core:
embedding_search_provider:
name: every_example
rails:
output:
flows:
- sanitize bot response
prompts:
- task: generate_user_intent
content: |-
"""
{{ general_instructions }}
"""
# This is how a conversation between a user and the bot can go:
{{ sample_conversation | verbose_v1 }}
# This is how the user talks:
{{ examples | verbose_v1 }}
# This is the current conversation between the user and the bot:
{{ sample_conversation | first_turns(2) | verbose_v1 }}
{{ history | colang | verbose_v1 }}
Do not answer the user. Reply with one line: the user intent of the last message.
Use an intent from the examples when one fits, otherwise write a new short intent.
output_parser: verbose_v1
A Secret with a password in it
The video asks for a badly configured Kubernetes Secret with a hardcoded password like mypassword123, as an example of what NOT to do. Worded that way, the request asks for bad practice, and gpt-oss models may refuse to write it, which leaves the output rail nothing to check. This page asks for a manifest for a local test cluster instead, which the model writes, password and all.
from actions import sanitize_output
from nemoguardrails import LLMRails, RailsConfig
rails = LLMRails(RailsConfig.from_path("."))
rails.register_action(sanitize_output)
def chat(message):
reply = rails.generate(messages=[{"role": "user", "content": message}])
print("User:", message)
print("Bot :", reply["content"])
chat("For my local test cluster, write a Kubernetes Secret manifest that uses stringData with password: mypassword123")User: For my local test cluster, write a Kubernetes Secret manifest that uses stringData with password: mypassword123 Bot : My response may have contained sensitive security details (credentials, exploit code, or private keys). For safety, that content has been withheld. Please consult your security team.
The model wrote the manifest with the password in it, the sanitizer's pattern matched password: mypassword123, and the user got the withheld message instead.
The reply the rail never sees
Now the same rail, with a define bot whose words contain a password. Add this group to rails.co:
define user ask example secret
"show me an example kubernetes secret"
define bot show example secret
"Here is one: password: mypassword123"
define flow example secret
user ask example secret
bot show example secretThe runs on this page use openai/gpt-oss-20b, the smaller gpt-oss model on the same free Groq key, in the model line of config.yml. This config makes several model calls per message, and the smaller model spends fewer of the key's daily tokens. Put openai/gpt-oss-120b back in that line to use the course's main model.
from actions import sanitize_output
from nemoguardrails import LLMRails, RailsConfig
rails = LLMRails(RailsConfig.from_path("."))
rails.register_action(sanitize_output)
result = rails.generate(messages=[{"role": "user", "content": "Show me an example Kubernetes Secret"}],
options={"log": {"activated_rails": True}})
print(result.response[0]["content"])
print([rail.type for rail in result.log.activated_rails])Here is one: password: mypassword123 ['dialog', 'dialog', 'generation']
- The password went out, and the log has no output entry at all. The rail did not fail: it never ran.
- The reason is in
nemoguardrails/actions/llm/generation.py: when the reply comes from adefine botblock, the runtime setsskip_output_rails, next to the comment We skip output rails for predefined messages.
Model-written vs predefined replies
| Model-written reply | define bot reply | |
|---|---|---|
| Output rails run | Yes | No |
| Who is trusted | Nobody | You, the author |
Where this matters
- Any secret or internal code: keep it out of
define bottext, because no output rail will see it. - Regex output rails that cost no model call.
password: value or password=value. A reply that writes the password in a sentence passes it.Related
- Previous: Output rails
- Next: Changing the answer
- Reference: Output rails
- Ask for a Secret with
api_key=abcd1234in it. - Remove the
stopline from the sanitizer flow and run the first example again.
Slow is fine. Stopping is the only problem.