7. 가드레일 & 권한 제어

AI Agent는 코드를 실행하고, DB를 수정하고, 외부 API를 호출할 수 있습니다. 가드레일(Guardrail)은 Agent가 해서는 안 되는 행동을 막는 안전장치이고, 권한 제어는 Agent가 할 수 있는 범위를 명확히 정의하는 것입니다. 프로덕션에서는 이 두 가지가 없으면 반드시 사고가 납니다.

1. 가드레일 계층 구조

요청이 들어올 때 통과해야 하는 레이어들
사용자 입력
① Input Guardrail
PII 감지, 악성 프롬프트 차단, 길이 제한
↓ (통과 시)
Agent 처리
② Tool 권한 체크
RBAC, 화이트리스트, 인자 검증
↓ (허용된 Tool만)
Tool 실행
③ Output Guardrail
민감 데이터 마스킹, 응답 검증

2. Tool 권한 등급 — 위험도에 따라 3티어

티어위험도예시 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를
// 생성하는 경우도 있음 → 더 위험
올바른 Tool 실행기 — 티어별 권한 체크
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("허용되지 않은 도구");
    };
}

3. Human-in-the-Loop — 위험한 행동 전 사람 승인

승인 대기 흐름 구현
// 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);
}
Input Guardrail — PII & 악성 입력 차단
@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");
    }
}

4. 면접 대비 핵심 Q&A

Q1. "AI Agent에서 권한 체크를 왜 백엔드에서 해야 하나요? 프롬프트로 제한하면 안 되나요?"

프롬프트 지시("다른 사용자 데이터에 접근하지 마세요")는 신뢰할 수 없습니다. LLM은 확률적 모델이라 지시를 100% 따르지 않고, Prompt Injection 공격으로 지시를 무력화할 수도 있습니다. 보안의 기본 원칙은 Trust No Input입니다 — AI 판단에 보안을 위임하는 것은 사용자 입력을 그대로 신뢰하는 것과 같습니다. 권한 체크는 반드시 백엔드 코드에서 수행해야 합니다.

Q2. "Human-in-the-loop는 언제 적용하나요? 모든 쓰기 작업에 적용하면 너무 번거롭지 않나요?"

위험도와 되돌릴 수 없는 정도로 판단합니다. 되돌릴 수 없거나(계정 삭제, 결제), 외부 영향이 있거나(이메일 발송, 슬랙 메시지), 금전적 영향이 큰 경우(환불, 쿠폰 대량 발행)에는 승인이 필요합니다. 주소 업데이트, 장바구니 추가처럼 쉽게 되돌릴 수 있는 작업은 자동 실행해도 됩니다. 실무에서는 위험 Tool 목록을 화이트리스트로 관리하고, 새 Tool 추가 시 보안 검토를 의무화합니다.

핵심 한 줄

가드레일 = Input 검증 → Tool 권한 체크(RBAC) → Human-in-the-loop(위험 행동) → Output 마스킹. 보안은 프롬프트가 아니라 코드에서.