본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 24. 10:44

런타임 간 LLM의 안전성 약속을 신뢰하지 마세요

요약

폴리글랏 스택 환경에서 단일 런타임 기반의 LLM 안전 가드레일이 가진 한계를 지적합니다. 여러 런타임에 걸쳐 작동하는 에이전트 시스템에서 보안 공백을 메우기 위한 '패리티 계약(parity contracts)' 패턴과 보안 프리미티브를 제안합니다.

핵심 포인트

  • 단일 런타임 기반 가드레일은 폴리글랏 배포 환경에서 무력화될 수 있음
  • 런타임 우회 공격자(Adversary D) 개념을 통한 보안 위협 정의
  • LLM 도구 인자를 브라우저 JSON과 같은 신뢰할 수 없는 입력으로 취급해야 함
  • 여러 런타임에 걸쳐 일관된 보안을 유지하는 패리티 계약 패턴 필요

대부분의 LLM 안전 가드레일(safety guardrails)은 하나의 암묵적인 가정을 바탕으로 구축됩니다. 바로 고객과 접하는 모든 트래픽이 단일 런타임(single runtime)을 통해 실행된다는 가정입니다. 하나의 프로세스, 하나의 인프로세스(in-process) 안전 점검으로 모든 것이 끝난다는 것이죠. 하지만 폴리글랏 스택(polyglot stack)을 배포하는 순간 이 가정은 깨집니다. 이 글은 우리가 '패리티 계약(parity contracts)'이라고 부르는 패턴에 대한 기록입니다. 이는 여러 런타임에 걸쳐 작동하는 LLM 커머스 에이전트를 위한 배포 가능한 보안 프리미티브(security primitive)입니다. 우리는 이를 BrewHub PHL에서 실제 운영 환경에 구현했습니다. 필라델피아의 한 카페인 이곳의 AI 에이전트 Franklin은 인간의 승인 단계 없이 주문을 넣고, 고객의 지갑을 결제하며, 로열티 변동(loyalty mutations)을 처리합니다. 전체 학술 논문, 레드팀 코퍼스(red-team corpus), 그리고 패리티 테스트 러너(parity test runner)는 github.com/BrewHubPHL/allergen-parity-corpus 에 오픈 소스로 공개되어 있습니다.

문제점: 폴리글랏 배포는 인프로세스 안전성을 파괴합니다.

BrewHub의 아키텍처는 세 가지 런타임에 걸쳐 있습니다:
런타임 1 — Netlify의 Next.js (AWS Lambda): 고객 접점 SSE 채팅 엔드포인트, Franklin의 도구 호출(tool calls), 가격 재계산
런타임 2 — Netlify Functions (AWS Lambda): POS 체크아웃, 결제 처리, Square 웹훅 핸들러
런타임 3 — Google Cloud Run (Python ADK): 6개의 특화된 워크플로우 에이전트 — 운영(ops), 마케팅, 바리스타 교육, 서비스 복구, 컨시어지, 기원 스토리텔러(provenance storyteller)

Llama Guard, NeMo Guardrails, LlamaFirewall, MCP-Guard와 같은 기존의 모든 LLM 안전 라이브러리는 단일 서빙 런타임(single serving runtime)을 가정합니다. 이들의 보증은 인프로세스 보증입니다. 즉, 트래픽이 이 런타임을 통과한다면 게이트가 유지된다는 것입니다. 단일 런타임 배포에서는 문제가 없습니다. 하지만 각각이 고객 접점 또는 고객 인접 출력을 생성하는, 독립적으로 접근 가능한 런타임들이 존재할 때는 위험 요소가 됩니다. 우리의 경우를 보면, Cloud Run의 워크플로우 에이전트 경로가 생성한 텍스트가 이메일이나 직원 검토를 통해 결국 고객에게 도달할 수 있습니다. 만약 안전 게이트가 Next.js Lambda에만 존재하고 누군가 Cloud Run으로 가는 경로를 설계한다면, 게이트는 누락됩니다. 이것이 바로 '배포된 에이전트 간극(deployed-agent gap)'입니다.

우리는 이를 'Adversary D — 런타임 우회 공격자(runtime-bypass attacker)'로 공식화합니다. 즉, 기본 안전 게이트(primary safety gate)를 우회하여 보조 런타임(secondary runtime)에 도달하는 경로를 발견하거나 설계하는 공격자를 의미합니다.

핵심 통찰: LLM 도구 인자(Tool Arguments)를 신뢰할 수 없는 입력(Untrusted Input)으로 취급하십시오.

패리티 계약(parity contracts)을 달성하기 전, 구체적으로는 다음과 같은 근본적인 재분류가 필요합니다: LLM 도구 인자는 브라우저의 JSON과 동일하게 신뢰할 수 없는 입력(untrusted input)으로 분류되어야 합니다. 모든 프로덕션 웹 개발자는 클라이언트가 제공한 값을 신뢰하는 대신, 서버 측에서 진실을 재도출(re-derive truth)해야 한다는 점을 알고 있습니다. 우리는 동일한 규칙을 도구 호출 경계(tool-call boundary)에 적용합니다. Franklin의 place_order 도구가 실행될 때, 우리의 백엔드(_pricing.js)는 모델이 제공한 price_cents가 무엇이든 무시하고, Supabase로부터 merch_products.price_centsmodifiers.price_delta_cents를 직접 다시 가져옵니다. 만약 모델이 제공한 총액이 서버에서 계산된 총액과 단 1페니라도 차이가 나면, 트랜잭션은 실패합니다. 신원(identity)에 대해서도 마찬가지입니다. 우리는 도구 인자에서 customer_id를 절대 읽지 않습니다. 신원은 python-agents/lib/supabase_clients.py 내의 bearer_client를 통해 Bearer JWT로부터 독점적으로 해결됩니다. 환각(hallucinating)을 일으키는 모델과 프롬프트 인젝션(prompt-injection) 공격자는 도구 호출 경계에서 구분이 불가능하며, 이 방어책은 두 경우를 모두 커버합니다.

패리티 계약(Parity Contracts): 패턴의 정의.
$f_A$와 $f_B$를 서로 다른 런타임 $A$와 $B$에 구현된 결정론적(deterministic) 안전 분류기(safety classifiers)라고 가정합니다. 이들 사이의 패리티 계약은 세 가지 의무로 구성됩니다:

  1. 등가성(Equivalence): $ orall s. f_A(s) = f_B(s)$
  2. 공유 테스트 코퍼스(Shared test corpus): 두 구현체가 모두 통과해야 하는 레이블이 지정된 입력의 유한 집합으로, 긍정(positive), 부정(negative), 경계(boundary) 사례를 모두 포함합니다.
  3. CI 강제(CI enforcement): 모든 커밋 시 코퍼스를 기준으로 두 구현체를 평가하고, 불일치가 발생할 경우 배포를 차단하는 게이트입니다.

핵심 단어는 '결정론적(deterministic)'입니다. 이 패턴은 확률론적인 LLM 기반 가드레일(guardrails)이 아니라, 정규 표현식(regex) 분류기, 유한 상태 오토마타(finite-state automata), 그리고 규칙 엔진(rule engines)에 적용됩니다. 결정론적 분류기는 바이트 등가성 테스트(byte-equivalence testing)를 허용합니다. 바로 그 속성이 CI 게이트가 적용된 등가성 검증을 가능하게 만듭니다.

구현: 3계층 알레르기 유발 물질 차단 스위치 (The Three-Layer Allergen Kill Switch)

구체적인 구현체는 우리의 알레르기 안전 게이트 (allergen safety gate)입니다. 실패 모드는 구체적이며 위험도가 높습니다. 예를 들어, 특정 음료에 땅콩이 들어있지 않다는 환각 (hallucination)된 주장은 아나필락시스 (anaphylaxis)를 유발할 수 있습니다. 프롬프트 (Prompts)는 우회될 수 있고, 시스템 지침 (System instructions)은 우회 테스트 (bypass-tested)를 당할 수 있습니다. 따라서 게이트는 LLM의 추론 (reasoning) 과정 안에 존재해서는 안 됩니다.

계층 1 — LLM 이전 차단 (lib/safety/allergen.py): 사용자의 메시지가 Anthropic 또는 Gemini에 도달하기 전에, 동기식 정규식 엔진 (synchronous regex engine)이 이를 가로챕니다. 원문 텍스트에 대해 ALLERGEN_KEYWORDS, MEDICAL_KEYWORDS, 그리고 DIETARY_SAFETY_KEYWORDS 패턴을 실행합니다. 만약 일치하는 항목이 있다면, 단 하나의 토큰 (token)이 과금되기 전에 요청이 차단되며, 표준화된 ALLERGEN_SAFE_RESPONSE 문자열을 반환합니다. 중간 지연 시간 (Median latency): 3.4 μs.

계층 2 — 스트림 중간 정화 (lib/chat/allergen-safety.ts): 만약 프롬프트가 계층 1을 회피한다면, 외부로 나가는 토큰 스트림 (outbound token stream)은 50자 앞을 내다보는 룩어헤드 버퍼 (lookahead buffer)를 유지하는 scrubbing_text_stream()에 의해 감싸집니다. DANGEROUS_REPLY_RE는 \b100%\s+(?:\w+[- ])?free\b 및 \bguaranteed\s+(?:safe|free)\b와 같은 패턴을 매칭합니다. 전송 도중 매칭이 발생하면 SSE 스트림을 중단하고, 위험한 확언의 바이트가 클라이언트에 도달하기 전에 ALLERGEN_SAFE_RESPONSE로 대체합니다.

계층 3 — 응답 후 감사 (Post-response audit): 모든 안전 차단 내역은 Supabase의 franklin_safety_audit에 기록됩니다. 계층 3은 명시적으로 최선의 노력에 의한 포렌식 증거 (best-effort forensic evidence)로 정의됩니다. AWS Lambda의 fire-and-forget 실행 모델은 감사 행 (audit row)이 없다고 해서 실행되지 않았음을 증명하는 것은 아님을 의미합니다. 오직 양성 증거 (Positive evidence)만 유효합니다.

패리티 테스트 (The Parity Test): Python에서 TypeScript 파싱하기
패리티 계약 준수 (parity contract enforcement)는 python-agents/tests/safety/test_allergen_parity.py 에서 관리됩니다.

그 메커니즘은 검토할 가치가 있습니다: 테스트 시점에 저장소 파일 시스템에서 src/lib/chat/allergen-safety.ts를 읽습니다. export const NAME = /pattern/i; 형태의 각 선언을 정규 표현식 (Regex)으로 추출합니다. 추출된 각 패턴을 Python의 re 엔진에서 re.IGNORECASE 옵션과 함께 컴파일합니다. 명명된 4개의 정규 표현식 모두에 대해 90개 케이스로 구성된 배터리 (battery)를 사용하여 행동적 동등성 (behavioral equivalence)을 단언 (Assert)합니다. ALLERGEN_SAFE_RESPONSE가 TypeScript 템플릿 리터럴 (template literal)과 Python 문자열 상수 (string constant) 사이에서 바이트 단위로 동일함을 단언합니다. 이는 두 개의 독립적인 구현체를 공유 코퍼스 (corpus)에 대해 테스트하는 것보다 구조적으로 더 강력합니다. 이 테스트는 TypeScript 소스를 파싱하고 이를 Python 엔진 하에서 재컴파일합니다. 가장 흔한 패리티 버그 (parity-bug) 형태, 즉 엔지니어가 한쪽의 정규 표현식을 수정하고 다른 쪽을 잊어버리는 상황을 구조적으로 잡아냅니다. Python 정규 표현식의 변경 없이 TypeScript 정규 표현식만 변경하여 배포하는 것은 불가능합니다. 새로운 정규 표현식이 Python 하에서 배터리를 통과하거나 (우연히 패리티가 유지됨), 실패하거나 (CI가 배포를 차단함) 둘 중 하나입니다. 90개 케이스 배터리의 구성은 다음과 같습니다: 알레르기 양성 (allergen-positive) 27개, 의료 양성 (medical-positive) 27개, 식이 안전 양성 (dietary-safety-positive) 10개, 위험 답변 양성 (dangerous-reply-positive) 19개, 음성 대조군 (negative controls) 7개입니다.

레드팀 (Red-Team) 결과
우리는 4개 카테고리에 걸쳐 100개의 프롬프트 코퍼스 (75개의 적대적 프롬프트 + 25개의 양성 대조군)를 대상으로 평가를 수행했습니다:

카테고리실행됨 (Executed)차단율 (Block rate)오탐율 (False positive rate)
A — 알레르기 우회 (Allergen bypass)25100%해당 없음 (n/a)
B — 가격/신원 (상거래 언어)200%0%
D — 크로스 런타임 / 유니코드 (Cross-runtime / Unicode)20100%해당 없음 (n/a)
N — 양성 대조군 (Benign controls)250%0%

두 가지 정직한 레이어-1 (Layer-1) 격차가 드러났습니다: 아황산염 (sulfites)이 단순한 \bsulfite\b 패턴과 일치하지 않았고 (복수형 경계 문제), "여기에 견과류가 들어있나요?"라는 양식은 키워드를 .{0,30} 윈도우 (window) 밖으로 밀어냈습니다. 두 경우 모두 레이어-2 (Layer-2)에서 포착되었으며 — 킬 스위치 (kill switch)가 작동했습니다 — 하지만 게이트 (gate) 배치가 이상적인 것보다 한 단계 더 깊은 곳에 있었습니다. 두 사례 모두 출판 전 조용히 수정되기보다는 실행 가능한 발견 사항 (actionable findings)으로 문서화되었습니다.

Layer-1 p99 지연 시간 (latency): 8.79 μs — 이는 중앙 집중식 안전 서비스 (safety service)로의 리전 내 (intra-region) HTTPS 왕복 비용인 10–50ms보다 대략 4자릿수(four orders of magnitude) 낮은 수치입니다.

이 패턴을 사용해야 하는 시점
동등성 계약 (parity contract)은 다음 세 가지 조건이 모두 충족될 때 유지 관리 비용을 들일 가치가 있습니다:

  1. 둘 이상의 런타임 (runtime)이 독립적으로 고객 대면 (customer-facing) 또는 고객 인접 (customer-adjacent) 출력을 생성할 수 있음
  2. 안전 속성 (safety property)이 결정론적 (deterministic)임 (정규 표현식 (regex), 오토마타 (automaton), 규칙 엔진 (rule engine))
  3. 런타임 소유권이 팀 또는 언어 경계를 넘나듦 — 이로 인해 실제 환경에서 조용한 불일치 (silent divergence)가 발생할 가능성이 높음

만약 단 하나의 런타임만이 고객 대면 출력을 생성한다면, 게이트 (gate)를 한 번만 강제하면 됩니다. 만약 안전 속성이 확률적 (probabilistic)이라면, 동등성 계약은 적절한 형태가 아닙니다. 이 경우에는 더 어려운 문제인 분포적 등가성 (distributional equivalence)이 필요합니다.

HMAC 와이어 계약 (Wire Contract)
언급할 가치가 있는 또 다른 프리미티브 (primitive): Next.js edge에서 Cloud Run으로 가는 모든 요청은 internal-hmac.ts (TypeScript)를 통해 HMAC-SHA256으로 서명되며, ADK 호출 전 hmac_auth.py (Python)에 의해 ASGI 미들웨어로서 검증됩니다. 공유 비밀값 (shared secret)은 두 런타임을 위한 분리된 Doppler 설정에 저장됩니다. 타임스탬프 신선도 (freshness)는 60초로 강제됩니다. 이는 두 번째 동등성 계약으로, 형태는 같지만 도메인이 다릅니다. 이 계약은 두 파일의 공동 수정 시 보안 태그가 지정된 검토자 (security-tagged reviewer)를 요구하는 CODEOWNERS 규칙에 의해 강제됩니다.

전체 논문 및 코퍼스 (Corpus)
전체 논문, 공식 정의, 절제 연구 방법론 (ablation methodology), 그리고 실행 가능한 러너 (runner)가 포함된 100개 프롬프트 레드팀 (red-team) 코퍼스는 다음에서 확인할 수 있습니다: github.com/BrewHubPHL/allergen-parity-corpus

러너는 Python 안전 레이어에 대해 로컬 인프로세스 (in-process) 모드로 실행되며, 실제 인프라에 대한 전체 SSE (Server-Sent Events)를 사용하는 스테이징 인스턴스 (staging-instance) 모드는 최종 검증 (camera-ready validation)을 위해 설명되어 있습니다. 코퍼스는 리뷰어들이 테스트 세트 (battery)를 확장하고 방법론을 처음부터 끝까지 재실행할 수 있도록 오픈 소스로 공개되었습니다.

이 패턴은 언어에 구애받지 않습니다 (language-agnostic). TypeScript와 Python을 임의의 두 언어로 교체하고, 알레르기 유발 물질 정규 표현식 (allergen regex)을 임의의 결정론적 분류기 (deterministic classifier)로 교체하며, Jest와 pytest를 임의의 공유 러너 (shared runner)로 교체하면 됩니다.

위의 세 가지 채택 조건은 필요충분조건입니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0