Blog/Biztonság és megfelelés

LLM output guardrailek: moderáció, biztonságos megjelenítés és ellenőrzés, mielőtt bármi történne

Mit kell ellenőrizni a modell válasza után: biztonságos Markdown, sémaellenőrzés műveletek előtt, moderációs osztályozók, streaming és megalapozottság.

··13 perc olvasás

  • LLM security
  • Guardrails
  • Content moderation
  • OWASP
  • Output validation
A modell válasza megjelenítési, séma-, moderációs és megalapozottsági ellenőrzéseken megy át, mielőtt a felhasználóhoz vagy egy másik rendszerhez érne.

A lényeg röviden

  • Az OWASP LLM05:2025 a modell kimenetét a következő rendszer megbízhatatlan bemenetének tekinti, legyen az böngésző, adatbázis, shell vagy e-mail sablon.
  • A valódi kimeneti incidensek többsége megjelenítési és műveleti hiba, nem sértő szöveg, ezért először a Markdownt tisztítsd, és korlátozd a képeket és linkeket.
  • A strukturált kimenetet séma és allowlist alapján ellenőrizd, mielőtt bármilyen műveletet indítana; soha ne add eval-nak, shellnek vagy összefűzött SQL-nek.
  • Az olyan moderációs osztályozók, mint az omni-moderation-latest vagy a Llama Guard 4, jelzést adnak, nem ítéletet; a küszöböket és a teendőket kategóriánként te döntöd el.
  • A streaming átrendezi a kompromisszumot: vagy láthat a felhasználó egy chunkot a tiltás előtt, vagy minden chunk megfizeti az ellenőrzés késleltetését.

Cikk meghallgatása

0:000:00

A guardrailekről szóló viták többsége a bemenetnél kezdődik: prompt injection, jailbreak, felhasználók, akik olyasmit kérnek, amit nem kellene megkapniuk. Ez a fele is fontos, írtam is róla a prompt injection elleni védekezés cikkben. Ez a bejegyzés a másik feléről szól. Amikor a modell válaszolt, a szövege valahová megy: Markdownként megjelenített chatbuborékba, egy JSON-parserbe, amely visszatérítést indít, egy e-mail sablonba, egy SQL-lekérdezésbe. Mindegyik célnak megvannak a saját szabályai arról, mi biztonságos, és a modell egyiket sem ismeri.

Az LLM-alkalmazásokra vonatkozó OWASP Top 10 ezt a kockázatot LLM05:2025 Improper Output Handling néven tartja számon. Az alábbiakban leírom, hogyan építem fel a kimeneti ellenőrzéseket éles funkciókban: mit és hogyan jelenítünk meg, mit validálunk egy művelet előtt, hol a helye a moderációs osztályozóknak, mit változtat mindezen a streaming, és hogyan döntsünk a blokkolás, az átírás és az eszkalálás között.

A modell kimenete megbízhatatlan bemenet a következő rendszer számára

Az OWASP szerint az improper output handling a modell kimenetének elégtelen ellenőrzése, tisztítása és kezelése, mielőtt továbbkerül. A bejegyzés példái hétköznapi webes sérülékenységek, csak új forrásból: exec-nek vagy eval-nak átadott kimenet, a böngésző által XSS-ként értelmezett JavaScript vagy Markdown, paraméterezés nélkül futtatott generált SQL, a kimenetből összerakott fájlútvonalak és escape nélkül e-mail sablonba tett tartalom.

Azért érdemel külön kategóriát, mert a támadónak nem kell hozzáférnie a kimenetedhez. Közvetett prompt injectionnel egy dokumentum, termékértékelés vagy weboldal, amelyet a modell elolvas, befolyásolhatja, mit ír. Az OWASP pontosan ezt említi a hatást növelő tényezők között, a végfelhasználóénál szélesebb modelljogosultságok és a kontextusfüggő kódolás hiánya mellett. Az első javasolt védekezés egyértelmű: kezeld a modellt úgy, mint bármely más felhasználót, és alkalmazz zero trustot arra, amit visszaad.

Négy különböző feladat, amit mind guardrailnek hívnak

Az output guardrailek körüli zavar részben abból fakad, hogy a szó négy, eltérő eszközt és eltérő felelőst igénylő feladatot takar. Tervezési átnézéseken külön kezelem őket, mert az a csapat, amelyik vett egy moderációs API-t, gyakran azt hiszi, hogy mind a négyet lefedte.

FeladatMegválaszolt kérdésTipikus eszköz
Biztonságos megjelenítésFuttathat-e kódot vagy szivárogtathat-e adatot ez a szöveg megjelenítéskor?Markdown renderer beállítás, sanitizer, CSP
Műveleti ellenőrzésEngedélyezett és helyes formájú-e ez az eszközhívás vagy JSON?JSON Schema, allowlist, jogosultság-ellenőrzés
TartalombiztonságÁrtalmas, gyűlöletkeltő vagy szabályzatba ütköző-e?omni-moderation-latest, Llama Guard 4, Azure AI Content Safety
Helyesség és szivárgásMegalapozott-e, és kiad-e olyan adatot, amit nem kellene?Megalapozottság-ellenőrzés, PII- és titokszkenner

A táblázat sorrendje egyben az a sorrend, amelyben a munkát priorizálnám. A megjelenítési és műveleti hibák egyetlen rossz válaszból incidenst csinálnak; egy B2B támogatási widgetben megjelenő sértő szöveg kínos, de ritkán töröl adatot.

Úgy jelenítsd meg a Markdownt, mintha támadó írta volna

A chatfelületek azért jelenítenek meg Markdownt, mert jól néz ki. A Markdown viszont arra is mód, hogy a böngésző URL-eket kérjen le. A klasszikus szivárogtatási minta: egy becsempészett utasítás ráveszi a modellt, hogy privát adatot írjon egy kép URL-jének query stringjébe, a böngésző pedig abban a pillanatban elküldi a támadónak, amikor a kép megjelenik, kattintás nélkül. Simon Willison számos terméknél dokumentálta; a 2025 májusában jelentett GitLab Duo esetben egy nyilvános projektben elrejtett utasítás arra vette rá az asszisztenst, hogy lekérjen egy privát merge requestet, és egy base64 összefoglalót egy képtagen keresztül kiszivárogtasson. A GitLab úgy javította, hogy linkek és képek csak megbízható domainekre mutathatnak.

Vue vagy Nuxt frontendben ugyanazok a szabályok érvényesek, amelyeket a Vue biztonsági útmutatója a felhasználói tartalomra már megad: soha ne használj megbízhatatlan tartalmat templateként, kerüld a v-html-t tisztítatlan HTML-lel, és az URL-eket a szerveren tisztítsd. Modellkimenetnél négy dolgot csinálok: kikapcsolom a nyers HTML-t a Markdown rendererben, átírom vagy eldobom azokat a képeket és linkeket, amelyek hostja nincs az allowlisten, a megmaradt külső linkekre rel="noopener nofollow" kerül, és olyan Content Security Policyt küldök, amelynek img-src része csak a saját hostjaimat engedi. A CSP az a háló, amely akkor is tart, ha a rendererben hiba van.

// server/utils/render-answer.ts — Markdown from the model, rendered on the server
import MarkdownIt from 'markdown-it'

const ALLOWED_HOSTS = new Set(['balazscsorba.com', 'cdn.example-shop.at'])
const md = new MarkdownIt({ html: false, linkify: false })

function allowed(url: string) {
  try {
    const u = new URL(url, 'https://balazscsorba.com')
    return (u.protocol === 'https:' && ALLOWED_HOSTS.has(u.hostname))
  } catch { return false }
}

// images to unknown hosts become plain alt text, so nothing is fetched
md.renderer.rules.image = (tokens, i) => {
  const src = tokens[i].attrGet('src') ?? ''
  return allowed(src) ? `<img src="${md.utils.escapeHtml(src)}" alt="${md.utils.escapeHtml(tokens[i].content)}">`
                      : md.utils.escapeHtml(tokens[i].content)
}
const defaultLink = md.renderer.rules.link_open ?? ((t, i, o, e, s) => s.renderToken(t, i, o))
md.renderer.rules.link_open = (tokens, i, opts, env, self) => {
  if (!allowed(tokens[i].attrGet('href') ?? '')) tokens[i].attrSet('href', '#blocked-link')
  tokens[i].attrSet('rel', 'noopener nofollow')
  return defaultLink(tokens, i, opts, env, self)
}

export const renderAnswer = (text: string) => md.render(text)

A szerveren renderelj, és csak akkor küldj HTML-t a kliensnek, ha ezek a szabályok lefutottak. Ha a böngészőben renderelsz, ott is ugyanezeket a szabályokat alkalmazd, és a CSP-t akkor is tartsd meg, mert egyetlen komponens, amely nyers kimenetnél v-html-re vált, a többit semmissé teszi.

Ellenőrizd a strukturált kimenetet, mielőtt bármit kiváltana

Amikor a kimenet műveletet vezérel, eszközhívást, visszatérítést, adatbázis-lekérdezést, az ellenőrzések determinisztikusak és olcsók, így nincs mentség a kihagyásukra. A szigorú JSON Schema mód a formát garantálja, nem a jelentést, ahogy a strukturált kimenet JSON Schemával cikkben kifejtettem. Parse-olás után vesd össze az értékeket az üzleti szabályokkal: láthatja-e az aktuális felhasználó ezt a rendelésszámot, a visszatérítés a saját hatáskörben adható határ alatt van-e, létezik-e ez a cikkszám a katalógusban.

  • Soha ne add át a kimenetet eval-nak, shellnek vagy kifejezéseket futtató template engine-nek.
  • Minden modellből származó értékhez paraméterezett lekérdezést használj; az OWASP ezt kifejezetten említi.
  • Fájlneveket és azonosítókat keresőtáblából oldj fel, ne szövegből rakj össze útvonalat.
  • A kimenetet a célkörnyezethez illően escape-eld: HTML, e-mail, CSV, shell argumentum.
  • Visszafordíthatatlan műveletek elé tegyél embert, ahogy a human-in-the-loop jóváhagyási pontok cikkben.

Generált SQL esetén különösen a csak olvasási jogú szerepkör és a lekérdezés-validátor számít többet bármely osztályozónál; a részletek a text-to-SQL guardrailek cikkben vannak.

Moderációs osztályozók: mit vizsgálnak a fő lehetőségek

Tartalombiztonsághoz ritkán írsz saját modellt. Az általam látott projektek többségét három család lefedi: az OpenAI moderációs végpontja, a Meta Llama Guard 4 mint saját üzemeltetésű vagy bérelt nyílt súlyú modell, valamint az Azure AI Content Safety több specializált detektorral.

LehetőségMit osztályozMegjegyzések a dokumentációból
OpenAI omni-moderation-latestSzöveg és kép; kategóriák pl. zaklatás, gyűlölet, illegális, önsértés, szexuális, erőszakIngyenes. Sok kategória csak szövegre működik. Futhat inline egy generálási kérésben.
Meta Llama Guard 4 (12B)Promptok és válaszok az S1–S14 veszélykategóriák szerintNyílt súlyok, szöveg és kép. Kimenete safe vagy unsafe, plusz a megsértett kategóriák. A kategóriák a promptban testre szabhatók.
Azure AI Content SafetyÁrtalomkategóriák, Prompt Shields, megalapozottság, védett tartalomA védett tartalom felismerése csak angolul működik; a kódindex csak 2023 áprilisáig naprakész.

A Llama Guard 4 az MLCommons veszélytaxonómiáját használja egy kiegészítéssel: S1 erőszakos bűncselekmények, S2 erőszakmentes bűncselekmények, S3 szexuális bűncselekmények, S4 gyermekek kizsákmányolása, S5 rágalmazás, S6 szakmai tanácsadás, S7 magánszféra, S8 szellemi tulajdon, S9 tömegpusztító fegyverek, S10 gyűlölet, S11 önsértés, S12 szexuális tartalom, S13 választások és S14 kódértelmezővel való visszaélés. Egy válasz ellenőrzéséhez a Meta szerint a promptnak a felhasználói üzenetet és az ágens válaszát is tartalmaznia kell, mert a kérdés adja a válasz megítélésének kontextusát. Az első kimeneti sor safe vagy unsafe, a második felsorolja a megsértett kategóriákat.

Az OpenAI bevezette az inline moderációt: ha egy Responses vagy Chat Completions kérésben legfelső szintű moderation objektumot adsz meg omni-moderation-latest modellel, a bemenetre és a kimenetre is kapsz pontszámokat, második hívás nélkül. A dokumentáció két dologban egyértelmű, és ezeket minden csapatnak elmondanám. Egy biztonságtudatos elutasítás is kaphat jelölést, mert ártalmas tartalomról beszél, ezért a pontszámok a saját szabályzatod jelzései, nem automatikus tiltás. Eszközhívásoknál pedig a moderáció a beszélgetésben szereplő argumentumokat és kimeneteket fedi le, az eszközneveket, leírásokat és sémákat nem.

Streaming: előbb megmutatni vagy előbb ellenőrizni

A felhasználók azt várják, hogy a tokenek generálás közben jelenjenek meg, minden kimeneti ellenőrzés viszont a teljes szöveget szeretné. A kettő egyszerre nem megy, és az eszközök ezt ki is mondják. Az OpenAI szerint az inline moderációs pontszámok csak a teljes kimenet elkészülte után érkeznek, a deltákkal nem. Az NVIDIA NeMo Guardrails bekapcsolt streamingnél chunkokon futtatja az output raileket: a chunk_size alapértéke 200 token, a context_size 50 tokent visz át a következő chunkba, hogy a határon átnyúló mondatot is egészként ítélje meg, a stream_first pedig a sorrendet dönti el.

Két mód a kimeneti ellenőrzéssel történő streamingreA tokenek chunkokba pufferelődnek; a chunk vagy elküldésre kerül, majd ellenőrzésre, vagy ellenőrzés után kerül elküldésre.ModelltokenfolyamPuffer200 tokenes chunkRail-ellenőrzés+50 token kontextusKliensmegjelenítés vagy leállítás
stream_first: True esetén a kliens minden chunkot megkap a rail futása előtt; False esetén előbb fut a rail, és minden chunkhoz hozzáadja a késleltetését.

Ha a stream_first értéke true, ami az alapértelmezés, a felhasználó már elolvashatott egy chunkot, amelyet aztán letilt a rendszer, és a stream egy JSON-hibával ér véget, amelyet a frontendnek kezelnie kell, lehetőleg úgy, hogy lecseréli a buborékot, ahelyett hogy félbehagyott mondatot hagyna ott. False esetén letiltott tartalom soha nem jelenik meg, de minden chunk vár a railre. Az én szabályom: előbb streamelj az alacsony kockázatú asszisztenseknél, ahol a legrosszabb eset egy ügyetlen mondat; előbb ellenőrizz, ha a kimenet személyes adatot, árat vagy olyan vállalást tartalmazhat, amelyet a cégnek teljesítenie kellene.

Megalapozottság és szivárgás ellenőrzése

Egy RAG-asszisztensben a legdrágább kimeneti hiba a magabiztos válasz, amelyet a források nem támasztanak alá. Erre az Azure AI Content Safety megalapozottság-felismerést kínál: elküldöd a generált szöveget és a forrásokat, választasz egy domaint, például Medical vagy Generic, és a szolgáltatás megjelöli a megalapozatlan tartalmat. Egy opcionális javító mód a forrásokhoz igazított szöveget ad vissza. Az ilyen ellenőrzéseket inkább mintavételre és riasztásra használom, mint kemény kapuként, mert a megalapozottságot megítélő modell maga is hibázik, és a valódi javítás korábban, a visszakeresésnél és a hivatkozásoknál van, erről a megalapozottság és hivatkozások cikkben írtam.

A szivárgást könnyebb determinisztikusan ellenőrizni. Keresd a kimenetben az ismert mintákat: IBAN-okat, más személyek (nem az aktuális felhasználó) e-mail-címeit és telefonszámait, API-kulcsokat, belső hostneveket és a saját system promptod töredékeit. Ugyanazok a detektorok, amelyeket a bemeneti oldalon PII-kitakarásra használsz, a kimeneti oldalon is működnek; a különbség az, hogy kimenetnél általában kitakarsz és naplózol, ahelyett hogy az egész választ eldobnád.

Blokkolás, kitakarás, átírás vagy eszkalálás

A guardrail döntés, nem pontszám. Minden kategóriánál írd le, mi történik, ha bejelez, és ki látja. Ezzel a táblázattal kezdek, aztán valós forgalom alapján hangolom.

TalálatTeendőA felhasználó ezt látja
Nem biztonságos link- vagy képhostSzöveggé alakítás, naplózásVálasz link nélkül
Egyértelmű tartalombiztonsági sértésBlokkolás, fix szövegre cserélés, pontszámok naplózásaSemleges elutasítás és kapcsolatfelvételi lehetőség
Más személy PII-adata vagy titkaKitakarás, riasztás a biztonsági csapatnakVálasz maszkolt értékkel
Érvénytelen eszközargumentumokElutasítás, egyszeri hibaüzenet a modellnekSemmit, hacsak az újrapróbálás nem bukik el
Megalapozatlan állítás RAG-válaszbanMegjelölés átnézésre, mintavételVálasz a forrásokkal, a megszokott módon

Két szokás tartja ezt tisztességesen. Minden döntésnél naplózd az osztályozó verzióját, a pontszámokat és a teendőt, mert a küszöbök elcsúsznak, amikor a szolgáltató egy -latest alias mögött frissíti a modellt. És blokkoláskor mutass a felhasználónak valami hasznosat: egy fix üzenet és egy út egy emberhez jobb, mint egy néma üres buborék, amelyet az emberek hibának néznek és újrapróbálnak.

Mindez egyetlen szerverútvonalban

Az olcsó determinisztikus ellenőrzések futnak először, az osztályozók utánuk, a megjelenítés pedig utoljára, hogy a drága és tévedhető lépések a lehető legkevesebbet lássák. Nuxtban vagy bármely Node backendben a streaming nélküli változat egy rövid függvény.

Kimeneti guardrail pipelineA modell kimenete szivárgásvizsgálaton, sémaellenőrzésen, moderáción és biztonságos megjelenítésen megy át, mielőtt a felhasználóhoz ér.Kimenetszöveg vagy JSONSzivárgásPII, titkokSémaszabályok, allowlistModerációpontszám, szabályMegjelenítéshostok, CSP
Költség szerinti sorrend: először a determinisztikus ellenőrzések, aztán az osztályozó, végül a megjelenítési szabályok.
// server/utils/guard-output.ts
import { renderAnswer } from './render-answer'
import { redactLeaks } from './redact'          // your PII / secret patterns

const BLOCKED = 'I cannot help with that here. Please contact our support team.'

export async function guardOutput(text: string) {
  const { text: clean, findings } = redactLeaks(text)

  const res = await fetch('https://api.openai.com/v1/moderations', {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ model: 'omni-moderation-latest', input: clean }),
  })
  if (!res.ok) throw new Error(`moderation failed: ${res.status}`)   // fail closed
  const result = (await res.json()).results[0]

  const decision = result.flagged ? 'block' : findings.length ? 'redact' : 'allow'
  console.info(JSON.stringify({ event: 'output_guard', decision, findings, scores: result.category_scores }))

  return { html: renderAnswer(decision === 'block' ? BLOCKED : clean), decision }
}

A vázlatban két döntés tudatos. Ha a moderációs hívás hibára fut, zártan bukik el, ami egy nyilvános asszisztensnél helyes, egy belső eszköznél viszont, ahol a rendelkezésre állás fontosabb, helytelen; ezt döntsd el kifejezetten. És első döntésként a flagged értéket használja, ahogy az OpenAI javasolja, de minden pontszámot naplóz, hogy később kategóriánkénti küszöbökre válthass. Ha inkább keretrendszert használnál: a Guardrails AI ugyanezt az ötletet modellhíváshoz csatolható validátorokba csomagolja, erről a Guardrails AI áttekintésben írtam.

A guardraileket úgy teszteld, mint bármely más funkciót

A guardrailek két irányban hibáznak, és mindkettő pénzbe kerül. A kihagyott sértés a nyilvánvaló. A téves riasztás a csendes: egy támogatási asszisztens, amely elutasítja a „folyamat kilövésére” vagy a „veszélyes áru szállítására” vonatkozó kérdéseket, úgy veszít felhasználókat, hogy senki sem veszi észre. Építs egy kis címkézett készletet mindkét fajtával, beleértve az osztályozókat megzavaró szakszókincset, és futtasd CI-ban, valahányszor küszöböt, promptot vagy osztályozóverziót változtatsz.

  • Vegyél fel szivárogtató payloadokat: query stringes kép-URL-eket, hasonló kinézetű domainekre mutató linkeket, HTML tageket a Markdownban.
  • Vegyél fel tartományon kívüli értékekkel és más ügyfélhez tartozó azonosítókkal végzett eszközhívásokat.
  • A tiltási arányt és a téves riasztási arányt kategóriánként éles mintákon kövesd, ne csak a tesztkészleten.
  • Red-teameld az egész láncot egy olyan eszközzel, mint a Promptfoo, mert egy payload, amely egy rétegen átjut, csak akkor érdekes, ha mindegyiken átjut.

Az output guardrailek nem látványos mérnöki munka: renderer-beállítás, sémák, egy osztályozóhívás és egy döntési táblázat. Pontosan ezért működnek. A modell továbbra is meg fog lepni; az utána következő rendszerek ne tegyék.

Források

  1. OWASP GenAI Security Project: LLM05:2025 Improper Output Handling
  2. OpenAI API Docs: Moderation
  3. Meta: Llama Guard 4 model card and prompt formats
  4. NVIDIA NeMo Guardrails: Output rail streaming configuration
  5. Microsoft Learn: Prompt Shields in Azure AI Content Safety
  6. Microsoft Learn: Groundedness detection in Azure AI Content Safety
  7. Microsoft Learn: Protected material detection in Azure AI Content Safety
  8. Guardrails AI documentation
  9. Vue.js docs: Security best practices
  10. Simon Willison: Remote prompt injection in GitLab Duo leads to source code theft

Gyakori kérdések

Mi az improper output handling?

Az OWASP LLM Top 10 LLM05:2025 pontja: a modell kimenetének elégtelen ellenőrzése, tisztítása és kezelése, mielőtt más komponensekhez kerül. Tipikus következménye az XSS a böngészőben, az SQL injection, a path traversal vagy a távoli kódfuttatás a backenden.

Elég az OpenAI moderációs végpontja output guardrailnek?

A tartalombiztonsági kategóriákat lefedi, például a gyűlöletkeltést, zaklatást, önsértést, szexuális tartalmat, erőszakot és az illegális tevékenységre vonatkozó tanácsokat, és ingyenes. Azt viszont nem vizsgálja, hogy a HTML biztonságosan megjeleníthető-e, hogy a válasz megalapozott-e, vagy hogy egy eszközhívás engedélyezett-e. Egy réteg a sok közül.

Moderálható egy streamelt válasz?

Igen, de választani kell. Az OpenAI az inline moderációs pontszámokat csak a teljes kimenet elkészülte után adja vissza. A NeMo Guardrails alapértelmezetten 200 tokenes chunkokat ellenőriz, és minden chunkot vagy az ellenőrzés előtt streamel, vagy visszatart, amíg az ellenőrzés át nem megy.

Blokkoljam vagy írjam át a megjelölt kimenetet?

A kategóriától függ. Egyértelmű biztonsági sértésnél blokkold és cseréld fix üzenetre, kiszivárgott személyes adatnál vagy titoknál takard ki, a határeseteket és az üzletkritikus eseteket pedig küldd emberhez. Minden döntést naplózz a pontszámokkal együtt, hogy hangolni tudd a küszöböket.

Hogyan előzhető meg a Markdown képekkel történő adatszivárogtatás?

Ne jeleníts meg a modell kimenetéből tetszőleges domainre mutató képet vagy linket. Használj host-allowlistet, minden mást a szerveren távolíts el vagy írj át, kapcsold ki a nyers HTML-t a Markdown rendererben, és állíts be szigorú Content Security Policyt.

Pont erre van szükséged?

Írj a projektedről vagy a pozícióról – szívesen hallok felőled.