Zum Hauptinhalt springen
Lektion 6 von 14 · KI-Sicherheit und Red Teaming

Guardrails: Eingabe- und Ausgabevalidierung

Lektion 6 von 14 Aktualisiert am 28. Juli 2026 Fortgeschritten
Auf dieser Seite
  1. Was ist ein Guardrail?
  2. Eine Pipeline-Architektur
  3. Drei typische Guardrails
  4. Zusammenfügen aller Teile
  5. Wenn die Regel nicht in Regex geschrieben werden kann
  6. Kernaussagen

Was ist ein Guardrail?

Ein Guardrail ist eine Validierungsschicht, die sich zwischen Nutzer und Modell auf der Eingabeseite oder zwischen Modell und Nutzer auf der Ausgabeseite befindet. Die Abgrenzung zur Moderation, die im vorherigen Kapitel behandelt wurde, ist scharf und muss gewahrt bleiben: Moderation beantwortet die Frage „Ist dieser Inhalt anstößig?” – eine universelle Frage, deren Antwort nicht von Ihrem Unternehmen abhängt. Guardrails hingegen beantworten Fragen, die nur Sie formulieren können: Liegt die Nachricht im erwarteten Rahmen? Enthält die Antwort verbotene Informationen? Ist die angeforderte Aktion für diese Rolle autorisiert? Ein Bankassistent und ein Buchhandelsassistent teilen sich dieselbe Moderation, haben aber keine gemeinsamen Guardrails.

Bis 2026 sind Guardrails zu einem Standardkomponente von KI-Agent-Architekturen geworden und unverzichtbar, sobald ein Modell Zugriff auf Tools oder sensible Daten hat.

Eine Pipeline-Architektur

Die strukturelle Wahl besteht darin, jeden Guardrail als ein unabhängiges Objekt mit einer einzigen Verantwortung zu behandeln und sie dann in einer gewählten Reihenfolge zu verketteten. Der Vorteil zeigt sich während der Wartung: Das Hinzufügen einer Geschäftsregel erfordert keine Änderungen am bestehenden Code, und eine Regel, die zu viel Lärm verursacht, kann in einer einzigen Zeile entfernt werden.

Ein drittes Urteil ergänzt erlauben und blockieren: ändern. Dies ändert die Natur der Übung. Viele reale Situationen rechtfertigen keine Ablehnung – eine Antwort, die einen internen Serverpfad enthält, bleibt nützlich, sobald dieser Pfad maskiert ist. Ein Blockieren würde die Information verlieren; eine Änderung bewahrt sie, indem sie das Problem darstellende Element entfernt.

from dataclasses import dataclass
from enum import Enum

class Decision(Enum):
    ALLOW = "allow"
    BLOCK = "block"
    MODIFY = "modify"

@dataclass
class ValidationResult:
    decision: Decision
    reason: str
    modified_content: str | None = None

class Guardrail:
    """Basis-Klasse für einen Guardrail."""

    def validate(self, content: str, context: dict) -> ValidationResult:
        raise NotImplementedError

class GuardrailPipeline:
    """Verkettet mehrere Guardrails nacheinander."""

    def __init__(self):
        self.input_guardrails: list[Guardrail] = []
        self.output_guardrails: list[Guardrail] = []

    def add_input(self, guardrail: Guardrail):
        self.input_guardrails.append(guardrail)

    def add_output(self, guardrail: Guardrail):
        self.output_guardrails.append(guardrail)

    def validate_input(self, message: str, context: dict) -> ValidationResult:
        for g in self.input_guardrails:
            result = g.validate(message, context)
            if result.decision == Decision.BLOCK:
                return result
            if result.decision == Decision.MODIFY:
                message = result.modified_content
        return ValidationResult(Decision.ALLOW, "Alle Validierungen bestanden")

    def validate_output(self, response: str, context: dict) -> ValidationResult:
        for g in self.output_guardrails:
            result = g.validate(response, context)
            if result.decision == Decision.BLOCK:
                return result
            if result.decision == Decision.MODIFY:
                response = result.modified_content
        return ValidationResult(Decision.ALLOW, "OK", response)

Drei typische Guardrails

Der Bereichs-Guardrail hält das Gespräch innerhalb seines Domänenbereichs. Er dient sowohl der Sicherheit als auch dem Ruf: Ein Kundenservice-Assistent, der darauf trainiert wurde, Fragen zu Lieferungen zu beantworten, hat nichts davon, sich zu politischen Nachrichten zu äußern, und ein Screenshot einer solchen Antwort verbreitet sich schnell. Die Liste der verbotenen Themen muss mit der Geschäftsführung besprochen werden, niemals allein vom technischen Team erstellt.

class ScopeGuardrail(Guardrail):
    """Überprüft, dass die Nachricht im autorisierten Bereich bleibt."""

    def __init__(self, allowed_topics: list[str], forbidden_topics: list[str]):
        self.allowed_topics = allowed_topics
        self.forbidden_topics = forbidden_topics

    def validate(self, content: str, context: dict) -> ValidationResult:
        content_lower = content.lower()

        # Überprüfen verbotener Themen
        for topic in self.forbidden_topics:
            if topic.lower() in content_lower:
                return ValidationResult(
                    Decision.BLOCK,
                    f"Thema außerhalb des Bereichs erkannt: {topic}"
                )

        return ValidationResult(Decision.ALLOW, "Im Bereich")

Der Format-Guardrail scheint trivial, liefert aber den größten täglichen Nutzen. Er kostet nur wenige Mikrosekunden, hängt von keinen Netzwerkanrufen ab und unterbricht übergroße Nachrichten, die für volumetrische Injectionen und Budget-Auslaugungsversuche verwendet werden. Platzieren Sie ihn als ersten in der Kette: Es gibt keinen Sinn, für API-Moderation auf eine 200.000-Zeichen-Nachricht zu zahlen, die Sie sowieso ablehnen werden.

class FormatGuardrail(Guardrail):
    """Validiert Nachrichtenformat und Länge."""

    def __init__(self, max_tokens: int = 4000, max_lines: int = 100):
        self.max_tokens = max_tokens
        self.max_lines = max_lines

    def validate(self, content: str, context: dict) -> ValidationResult:
        # Grobe Token-Schätzung (1 Token ~ 4 Zeichen in Englisch)
        estimated_tokens = len(content) // 4
        if estimated_tokens > self.max_tokens:
            return ValidationResult(
                Decision.BLOCK,
                f"Nachricht zu lang: ~{estimated_tokens} Tokens (max {self.max_tokens})"
            )

        line_count = content.count("\n") + 1
        if line_count > self.max_lines:
            return ValidationResult(
                Decision.BLOCK,
                f"Zu viele Zeilen: {line_count} (max {self.max_lines})"
            )

        return ValidationResult(Decision.ALLOW, "Gültiges Format")

Der dritte überwacht die Ausgabe und illustriert die Verwendung des „ändern”-Urteils. Die vier Familien, nach denen er sucht, teilen eine bequeme Eigenschaft: Sie haben eine erkennbare Form, unabhängig von der Bedeutung. Eine API-Schlüssel, eine interne Adresse, eine private IP oder ein Serverpfad werden durch ihre Struktur identifiziert, ohne dass der Satz, der sie enthält, verstanden werden muss.

import re

class AntiExfiltrationGuardrail(Guardrail):
    """Verhindert die Ausfiltration sensibler Daten in Ausgaben."""

    def __init__(self):
        self.sensitive_patterns = {
            "api_key": r"(sk|pk|api[_-]?key)[_-][a-zA-Z0-9]{16,}",
            "internal_email": r"[a-zA-Z0-9.]+@(company|internal|corp)\.\w+",
            "private_ip": r"\b(10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3})\b",
            "server_path": r"(/data/|/etc/|/home/|C:\\Users\\)",
        }

    def validate(self, content: str, context: dict) -> ValidationResult:
        for data_type, pattern in self.sensitive_patterns.items():
            if re.search(pattern, content):
                # Maskieren statt Blockieren
                cleaned_content = re.sub(pattern, "[REDACTED]", content)
                return ValidationResult(
                    Decision.MODIFY,
                    f"Sensitive data redacted: {data_type}",
                    cleaned_content,
                )
        return ValidationResult(Decision.ALLOW, "Keine Ausfiltration erkannt")

Zusammenfügen aller Teile

Die vollständige Zusammenstellung zeigt, wo die Teile zusammenpassen. Die Asymmetrie fällt auf: zwei Guardrails auf der Eingabe, nur einer auf der Ausgabe. Dies ist häufig und gesund – Sie filtern stark vor dem Aufruf des Modells, da dies dort ist, wo Kosten vermieden werden können, und Sie halten das Minimum auf der Ausgabe, um korrekte Antworten nicht zu beschädigen. Die zurückgegebene Nachricht bei einem Blockieren auf der Ausgabe folgt derselben Vorsicht: absichtlich vage, sie sagt nicht, was erkannt wurde, Informationen, die in erster Linie jemandem dienen würden, der Ihre Verteidigungskräfte kartieren möchte.

from openai import OpenAI

def create_secure_assistant():
    """Erstellt einen Assistenten mit einer vollständigen Guardrail-Pipeline."""
    client = OpenAI()
    pipeline = GuardrailPipeline()

    # Eingabe-Guardrails
    pipeline.add_input(FormatGuardrail(max_tokens=2000))
    pipeline.add_input(ScopeGuardrail(
        allowed_topics=["products", "orders", "delivery", "returns"],
        forbidden_topics=["politics", "religion", "competitors"],
    ))

    # Ausgabe-Guardrails
    pipeline.add_output(AntiExfiltrationGuardrail())

    def process(message: str) -> str:
        context = {"user_role": "customer"}

        # Eingabevalidierung
        input_check = pipeline.validate_input(message, context)
        if input_check.decision == Decision.BLOCK:
            return f"Ich kann diese Anfrage nicht bearbeiten: {input_check.reason}"

        valid_message = input_check.modified_content or message

        # Modellaufruf
        response = client.chat.completions.create(
            model="gpt-5.6-sol",
            messages=[
                {"role": "system", "content": "Acme Corp Kundenservice-Assistent."},
                {"role": "user", "content": valid_message},
            ],
        )
        reply = response.choices[0].message.content

        # Ausgabevalidierung
        output_check = pipeline.validate_output(reply, context)
        if output_check.decision == Decision.BLOCK:
            return "Ich kann diese Frage nicht beantworten."

        return output_check.modified_content or reply

    return process

Wenn die Regel nicht in Regex geschrieben werden kann

Einige Geschäftsregeln lassen sich durch Muster nicht formalisieren. „Die Antwort darf keine Anlageberatung darstellen”, „die Nachricht darf keine Gesundheitsdaten enthalten”: Kein regulärer Ausdruck erfasst dies. Ein zweites Modell, schnell und kostengünstig, kann dann als Richter fungieren, wobei die Antwort auf JSON-Format beschränkt ist, um vom aufrufenden Code nutzbar zu bleiben. Der Kompromiss ist explizit – Sie gewinnen Präzision auf Kosten von Latenz und Determinismus, was diesen Ansatz nur für Regeln reserviert, die dies wirklich rechtfertigen.

def llm_guardrail(message: str, rule: str) -> ValidationResult:
    """Verwendet ein schnelles Modell zur Validierung der Compliance."""
    client = OpenAI()
    response = client.chat.completions.create(
        model="gpt-5.6-terra",
        messages=[
            {"role": "system", "content": f"""Sie sind ein Compliance-Validierer.
Zu überprüfende Regel: {rule}
Antworten SIE NUR mit JSON: {{"compliant": true/false, "reason": "..."}}"""},
            {"role": "user", "content": f"Zu validierende Nachricht:\n{message}"},
        ],
    )
    import json
    result = json.loads(response.choices[0].message.content)
    if result["compliant"]:
        return ValidationResult(Decision.ALLOW, result["reason"])
    return ValidationResult(Decision.BLOCK, result["reason"])

Kernaussagen

  • Guardrails validieren die Geschäftskonformität, nicht nur anstößigen Inhalt
  • Die Pipeline-Architektur ermöglicht die Kombination mehrerer Validierungsschichten
  • Jeder Guardrail hat eine einzige Verantwortung: Bereich, Format, Ausfiltration usw.
  • Ausgabe-Guardrails sind genauso wichtig wie Eingabe-Guardrails
  • Für komplexe Fälle kann ein schnelles LLM als Validierer dienen