AI Agent는 코드를 실행하고, DB를 수정하고, 외부 API를 호출할 수 있습니다. 가드레일(Guardrail)은 Agent가 해서는 안 되는 행동을 막는 안전장치이고, 권한 제어는 Agent가 할 수 있는 범위를 명확히 정의하는 것입니다. 프로덕션에서는 이 두 가지가 없으면 반드시 사고가 납니다.
| 티어 | 위험도 | 예시 Tool | 처리 방식 |
|---|---|---|---|
| Tier 1 — 읽기 | 낮음 | get_order_status, search_products, get_faq | 자동 실행 허용 |
| Tier 2 — 쓰기 | 중간 | update_address, add_to_cart, send_notification | 사용자 소유권 확인 후 실행 |
| Tier 3 — 위험 | 높음 | cancel_order, process_refund, delete_account, send_email | Human-in-the-loop 승인 필요 |
// 사용자 A가 로그인한 상태에서:
// "ORD-9999 주문 취소해줘"라고 요청
// → ORD-9999는 사용자 B의 주문
// ❌ 나쁜 구현: 권한 체크 없음
case "cancel_order" -> {
String orderId = input.get("order_id").asText();
orderService.cancel(orderId); // 누구 주문이든 취소됨!
}
// LLM이 hallucination으로 엉뚱한 주문 ID를
// 생성하는 경우도 있음 → 더 위험
private String executeTool(
String name, JsonNode input, String userId) {
return switch (name) {
// Tier 1: 소유권만 확인
case "get_order_status" -> {
String id = input.get("order_id").asText();
if (!orderSvc.isOwner(userId, id))
yield error("권한 없음");
yield orderSvc.getStatus(id);
}
// Tier 3: Human-in-the-loop
case "cancel_order" -> {
String id = input.get("order_id").asText();
if (!orderSvc.isOwner(userId, id))
yield error("권한 없음");
// 사용자에게 확인 요청
yield requestApproval(
"주문 " + id + "을 취소할까요?", input);
}
// 화이트리스트 외 도구 차단
default -> error("허용되지 않은 도구");
};
}
// 1. Agent가 위험 Tool 요청 감지
if (toolTier(toolName) == Tier.DANGEROUS) {
// 2. 승인 토큰 생성 및 Redis 저장
String token = UUID.randomUUID().toString();
redis.setex("approval:" + token, 300, // 5분 TTL
toJson(Map.of(
"userId", userId,
"tool", toolName,
"input", input
)));
// 3. 사용자에게 확인 메시지 반환
return "확인이 필요합니다: "
+ buildApprovalMessage(toolName, input)
+ " [승인: /approve/" + token + "]";
}
// 4. 사용자가 /approve/{token} 클릭 시
@PostMapping("/approve/{token}")
public String approve(@PathVariable String token,
@AuthUserId String userId) {
var data = redis.get("approval:" + token);
if (data == null) return "만료됨";
// 소유자 확인 후 실행
if (!data.userId().equals(userId))
return "권한 없음";
return executeTool(data.tool(), data.input(), userId);
}
@Component
public class InputGuardrail {
// 정규식으로 PII 감지
private static final Pattern PII = Pattern.compile(
"(\\d{6}-\\d{7})" + // 주민등록번호
"|(\\d{4}[- ]?\\d{4}[- ]?\\d{4}[- ]?\\d{4})" + // 카드번호
"|(01[016789]-?\\d{3,4}-?\\d{4})" // 전화번호
);
public void check(String input) {
if (PII.matcher(input).find())
throw new GuardrailException("PII_DETECTED");
// 길이 제한 (토큰 비용 + DoS 방지)
if (input.length() > 2000)
throw new GuardrailException("TOO_LONG");
// Prompt Injection 키워드 감지
if (input.toLowerCase()
.contains("ignore previous instructions"))
throw new GuardrailException("INJECTION");
}
}
프롬프트 지시("다른 사용자 데이터에 접근하지 마세요")는 신뢰할 수 없습니다. LLM은 확률적 모델이라 지시를 100% 따르지 않고, Prompt Injection 공격으로 지시를 무력화할 수도 있습니다. 보안의 기본 원칙은 Trust No Input입니다 — AI 판단에 보안을 위임하는 것은 사용자 입력을 그대로 신뢰하는 것과 같습니다. 권한 체크는 반드시 백엔드 코드에서 수행해야 합니다.
위험도와 되돌릴 수 없는 정도로 판단합니다. 되돌릴 수 없거나(계정 삭제, 결제), 외부 영향이 있거나(이메일 발송, 슬랙 메시지), 금전적 영향이 큰 경우(환불, 쿠폰 대량 발행)에는 승인이 필요합니다. 주소 업데이트, 장바구니 추가처럼 쉽게 되돌릴 수 있는 작업은 자동 실행해도 됩니다. 실무에서는 위험 Tool 목록을 화이트리스트로 관리하고, 새 Tool 추가 시 보안 검토를 의무화합니다.
가드레일 = Input 검증 → Tool 권한 체크(RBAC) → Human-in-the-loop(위험 행동) → Output 마스킹. 보안은 프롬프트가 아니라 코드에서.