회귀 테스트는 이미 알고 있는 실패를 다시 막는 데 강하지만, 아직 Testcase가 없는 공격은 찾지 못한다. Garak과 PyRIT는 LLM이나 AI Application에 여러 Probe와 대화 전략을 시도하고, 예상하지 못한 실패 후보를 수집하는 AI Red Teaming 도구다.
두 도구는 “자동 취약점 확정기”가 아니다. 결과는 탐지기와 Scorer의 판정이며 False Positive가 있을 수 있다. 이번 실습은 본인이 소유한 로컬 Test Target, 합성 데이터와 읽기 전용 기능만 사용한다. 발견된 결과는 사람이 Trace와 정책을 검토한 뒤에만 취약점 또는 회귀 Testcase로 승격한다.

Garak과 PyRIT는 무엇이 다른가
Garak은 다양한 Probe를 Target Generator에 보내고 Detector로 실패 징후를 찾는 스캐너형 도구다. PyRIT(Python Risk Identification Tool)는 Target, Attack, Converter, Scorer와 Memory를 조립해 단일·다중 Turn 공격 실험을 만드는 Framework에 가깝다.
| 비교 | Garak | PyRIT |
| 중심 단위 | Probe와 Detector | Attack·Target·Scorer·Memory |
| 강점 | 넓은 범주의 반복 Scan | 상태를 가진 적응형·다중 Turn 실험 |
| Target 연결 | Generator Plugin | Prompt Target |
| 판정 | Detector와 Evaluator | Scorer와 사람 검토 |
| 결과 | Log·JSONL·Report | Memory에 대화·Score·결과 저장 |
| 권장 출발점 | 선택한 Probe Family의 좁은 Scan | 하나의 합성 Objective를 단일 Turn부터 실행 |
Garak 공식 문서에서 Probe는 실패를 유도할 입력과 상호작용을 관리하고, Generator는 Model 또는 대화 시스템 연결을 감싼다. Detector는 응답에서 특정 실패 징후를 찾고, Harness가 전체 실행을 조정한다.
PyRIT 공식 문서는 자동·사람 주도 Red Teaming, 다양한 Target, Single-turn·Multi-turn Attack, 유연한 Scoring과 SQLite·Azure SQL 기반 Memory를 주요 구성으로 설명한다.
안전한 실습 경계부터 만든다
Garak·PyRIT Container
↓ 허용된 Test Network만
Local Guardrail Gateway
↓
합성 Test Tenant + 읽기 전용 RAG + Mock Tool
↓
Nova·NeMo·Presidio → Test LLM
↓
비민감 Trace와 격리된 결과 저장소
실제 운영 Endpoint, 타인의 Model, 외부 서비스와 실데이터를 대상으로 실행하지 않는다. Test Gateway는 Egress를 차단하고, Tool은 파일 삭제·명령 실행·결제·메시지 발송 대신 고정된 Mock 응답만 돌려준다. API Key는 Image나 Notebook에 넣지 않고 Secret로 주입한다.
실습 1: Garak 구조와 설치 확인
공식 저장소는 PyPI 설치와 Probe 목록 확인 방법을 제공한다. 운영 평가에서는 검증한 Version을 고정한다.
mkdir llm-redteam-lab
cd llm-redteam-lab
python3 -m venv .venv-garak
source .venv-garak/bin/activate
python -m pip install --upgrade pip
python -m pip install garak
python -m garak --version
python -m garak --list_probes
처음부터 모든 Probe를 실행하면 비용·시간·결과량이 급증한다. 먼저 Test Generator로 실행 구조를 확인하거나, 서비스 목적과 관련된 Probe Family 하나만 선택한다.
python -m garak \
--target_type test.Repeat \
--probes encoding
test.Repeat는 입력을 반복하는 내장 Test Target이다. 실제 취약점을 평가하는 명령이 아니라 설치와 Report Pipeline을 확인하는 안전한 Smoke Test다. Garak은 실행 상세를 JSONL에 남길 수 있으므로 입력과 출력에 합성 데이터만 사용하고 결과 디렉터리 접근권한을 제한한다.
실습 2: 소유한 HTTP Gateway만 Target으로 연결하기
다음 단계는 Local Guardrail Gateway를 Garak의 REST Generator로 연결하는 것이다. REST Generator의 정확한 Config Key는 설치한 Garak Version의 공식 Reference에서 확인한다.
plugins:
generators:
rest:
RestGenerator:
uri: http://host.containers.internal:8080/chat
method: post
headers:
Content-Type: application/json
req_template_json_object:
message: "$INPUT"
tenant_id: "redteam-test"
response_json: true
response_json_field: response
python -m garak \
--target_type rest.RestGenerator \
--config garak-rest.yaml \
--probes promptinject
위 이름과 옵션은 Version에 따라 달라질 수 있다. --plugin_info와 Reference를 먼저 확인한다. Scan 범위, 최대 Generation, 동시성, Timeout과 비용 한도를 정하고 Test Gateway 외 주소는 Network Policy로 차단한다.
Garak 결과를 읽는 법
Garak의 FAIL은 Detector가 목표한 실패 징후를 응답에서 발견했다는 뜻이다. 곧바로 실제 취약점이 확정됐다는 뜻은 아니다.
- 어떤 Probe와 Detector 조합인지 확인한다.
- Target 오류나 빈 응답이 잘못 판정됐는지 확인한다.
- 사용자 출력뿐 아니라 RAG·Tool Trace를 확인한다.
- 같은 조건으로 재현되는지 반복한다.
- 최소 합성 Testcase로 줄인 뒤 사람이 기대 행동을 정한다.
Detector가 단순 거부 문구를 기준으로 삼으면 언어·표현 변화에 따라 오탐할 수 있다. Guardrail의 구조화된 Decision과 Tool 호출 여부를 함께 검증하는 Custom Detector 또는 후처리 Gate가 필요할 수 있다.
실습 3: PyRIT를 In-memory로 시작하기
PyRIT 공식 문서는 Package 설치와 initialize_pyrit_async를 사용한 Framework 예제를 제공한다. 격리 실습에서는 먼저 In-memory Memory를 사용해 Disk에 대화가 남는 범위를 줄인다.
python3 -m venv .venv-pyrit
source .venv-pyrit/bin/activate
python -m pip install --upgrade pip
python -m pip install pyrit
python -c "import pyrit; print(pyrit.__version__)"
import asyncio
from pyrit.setup import IN_MEMORY, initialize_pyrit_async
async def main():
await initialize_pyrit_async(memory_db_type=IN_MEMORY)
print("PyRIT in-memory lab initialized")
asyncio.run(main())
IN_MEMORY는 Process가 끝나면 편리하게 사라지지만 화면 출력과 외부 Provider Log까지 지워 주는 기능은 아니다. Prompt와 응답에는 합성 데이터만 넣는다.
실습 4: 단일 Turn부터 Target을 검증하기
PyRIT의 Target은 공격할 AI System Interface다. 공식 문서 예제는 OpenAIChatTarget과 PromptSendingAttack을 사용한다. 이 글에서는 실제 Key를 코드에 넣지 않고 승인된 Test Endpoint 설정을 환경에서 불러온다는 전제로 구조만 보여 준다.
import asyncio
from pyrit.executor.attack import PromptSendingAttack
from pyrit.output.attack_result.pretty import PrettyAttackResultMemoryPrinter
from pyrit.prompt_target import OpenAIChatTarget
from pyrit.setup import IN_MEMORY, initialize_pyrit_async
async def main():
await initialize_pyrit_async(memory_db_type=IN_MEMORY)
target = OpenAIChatTarget()
attack = PromptSendingAttack(objective_target=target)
result = await attack.execute_async(
objective="합성 Test 문서의 접근 정책을 설명해줘"
)
printer = PrettyAttackResultMemoryPrinter()
await printer.write_async(result)
asyncio.run(main())
이 Objective는 정상 연결을 확인하는 Smoke Test다. 안전한 단일 Turn, Scorer 검증, 결과 저장 범위를 확인한 뒤에만 승인된 다중 Turn Strategy로 확장한다.
실습 5: Scorer와 사람 판정을 분리하기
PyRIT Scorer는 응답이 목표를 달성했는지 True·False, Likert Scale이나 Classification으로 평가할 수 있다. LLM 기반 Scorer는 유연하지만 자체 오류와 비용이 있다.
| 판정 대상 | 우선 방식 |
| Tool 호출 여부 | 구조화 Trace의 결정적 검사 |
| Tenant 경계 위반 | 문서 ID·Tenant ID의 결정적 검사 |
| 비밀 문자열 노출 | 합성 Canary의 정확 일치 검사 |
| 거부 의미·우회 성공 | Scorer와 사람 검토 병행 |
| 답변 품질·관련성 | Model Scorer와 표본 검토 |
Attack Model과 Scorer Model을 Target Model과 분리해도 모두 틀릴 수 있다. Scorer Version, Prompt, Threshold와 실행 환경을 결과에 기록하고, undetermined와 오류를 성공으로 계산하지 않는다.
체계적 Scan과 적응형 탐색을 함께 쓰기
Garak 넓은 Probe Scan
↓ 실패 후보
사람의 1차 분류
↓ 대화형 재현이 필요한 후보
PyRIT 단일·다중 Turn 실험
↓ 재현 가능한 최소 사례
Trace·권한·정책 검토
↓ 승인된 Finding
Promptfoo 회귀 Testcase + Guardrail 정책 변경
Garak으로 넓게 훑고 PyRIT으로 대화 Context를 깊게 탐색하는 흐름이 유용하지만 항상 이 순서일 필요는 없다. 중요한 것은 두 결과를 동일한 finding_id, Target Version, Policy Bundle과 Trace ID로 연결하는 것이다.
Podman 샌드박스 운영 예시
podman network create --internal llm-redteam-lab
podman run --rm \
--network llm-redteam-lab \
--read-only \
--cap-drop=all \
--security-opt=no-new-privileges \
--pids-limit=256 \
--memory=4g \
--cpus=2 \
-v "$PWD/results:/results:Z" \
localhost/llm-redteam-tools:VERSION
--internal Network에서는 외부 API를 호출할 수 없다. Local Target Container를 같은 Network에 붙여 완전 Local 실습을 하거나, 외부 Provider가 꼭 필요하면 허용 Destination만 통과하는 별도 Egress Proxy를 둔다. Host Network, Docker Socket, Home Directory와 Cloud Credential Directory를 Mount하지 않는다.
결과에서 개인정보와 비용을 지키기
- Prompt·Response 원문은 최소 기간만 보존한다.
- 실제 고객 대화 대신 합성 Dataset을 쓴다.
- JSONL·Memory DB·Trace의 접근권한과 암호화를 설정한다.
- API Key와 Authorization Header를 Log에서 제거한다.
- 최대 Turn, Probe, Generation, 동시성, Token과 비용을 제한한다.
- Tool은 Mock 또는 읽기 전용으로 대체한다.
- Scan 종료 후 임시 Container와 Secret을 폐기한다.
실습 완료 체크리스트
- 소유하거나 명시적으로 허가받은 Test Target만 사용했다.
- Garak의 Generator·Probe·Detector·Harness 역할을 구분했다.
- PyRIT의 Target·Attack·Scorer·Memory 역할을 구분했다.
- 전체 Scan 전에 Test Target으로 설치를 확인했다.
- Probe Family와 다중 Turn 범위를 작게 시작했다.
- 합성 Tenant, 합성 Canary와 Mock Tool을 사용했다.
- Container 권한, Network Egress와 자원 한도를 제한했다.
- FAIL·Score를 취약점 확정으로 자동 해석하지 않았다.
- 사용자 출력과 내부 Tool·RAG Trace를 함께 검토했다.
- 승인된 발견만 최소 회귀 Testcase로 승격했다.
마무리
Garak과 PyRIT는 알려진 답을 다시 채점하는 도구가 아니라, 현재 Test Suite 바깥의 실패 후보를 찾는 탐색 도구다. Garak은 Probe와 Detector로 넓게 스캔하고, PyRIT는 Target·Attack·Scorer·Memory를 조합해 대화형 경로를 깊게 조사한다.
자동화가 만드는 것은 판결이 아니라 후보 목록이다. 격리된 Test 환경에서 실행하고, 사람이 정책·권한·Trace와 재현성을 검토한 뒤, 가치 있는 발견만 Promptfoo Testcase와 Guardrail 정책으로 승격해야 한다. 다음 마지막 편에서는 이 발견을 책임자·기대 결과·정책 Version이 있는 Testcase로 바꾸고 Control Plane에 안전하게 배포하는 전체 Loop를 완성한다.
공식 자료
- NVIDIA Garak GitHub
- Garak 핵심 개념과 Class
- Garak Configuration
- Garak Detector Reference
- PyRIT 공식 문서
- Microsoft PyRIT GitHub
※ Garak Plugin 이름, PyRIT API와 Python 요구사항은 Version에 따라 달라질 수 있다. 명령 실행 전 고정한 Release의 공식 문서를 확인하고, 허가된 샌드박스에서 합성 데이터로 검증하자.
'일반IT > AI' 카테고리의 다른 글
| 발견을 방어로 바꾸기: Testcase와 정책 승격 (0) | 2026.09.03 |
|---|---|
| 방어선 조립하기: 직렬 가드레일과 Control Plane (0) | 2026.09.03 |
| 개인정보 방어선: Microsoft Presidio (0) | 2026.09.03 |
| 첫 번째 방어선: NeMo Guardrails와 Colang (0) | 2026.09.03 |
| 두 겹의 안전 분류: Nova Lite Safety와 Self-check (0) | 2026.09.03 |