LLM에 sudo 구현: AI 보안을 위한 Middleware 접근법

발행: (2026년 2월 1일 오전 07:35 GMT+9)
7 분 소요
원문: Dev.to

Source: Dev.to

위 링크에 있는 전체 텍스트를 제공해 주시면, 해당 내용을 한국어로 번역해 드리겠습니다. (코드 블록, URL 및 마크다운 형식은 그대로 유지됩니다.)

“쓰기 접근” 불안

우리는 모두 Agents—도구를 사용할 수 있는 AI, 단순히 채팅만 하는 것이 아니라—를 급히 만들고 있습니다. 그런데 내가 내 LangChain 에이전트에 stripe_api_key를 제공한 순간, 속이 꼬이는 느낌이 들었습니다.

우리는 본질적으로 확률적 모델(LLM)에게 우리 은행 계좌와 클라우드 인프라에 대한 결정적인 접근 권한을 부여하고 있는 것입니다. 만약 LLM이 환각적인 루프에 빠지거나 프롬프트 인젝션을 당한다면, 내 데이터베이스는 사라질 수 있습니다.

**“LLM에게 정중히 요청하는 것”(시스템 프롬프트)은 보안 전략이 아니라는 것을 깨달았습니다. 사용자를 “제발 친절하게 해 주세요”라고 말해서 Linux 서버를 보호하지는 않겠죠. 권한을 사용합니다. sudo를 사용합니다.

그래서 저는 지난 몇 주 동안 AI 에이전트를 위한 거버넌스 레이어를 구축하는 데 매진했습니다. 여기서는 “Human‑in‑the‑Loop” 미들웨어를 설계하고 구현하는 과정과 그 도전 과제에 대해 깊이 있게 살펴보겠습니다.

The Architecture: The “Man-in-the-Middle”

핵심 문제는 에이전트가 툴 호출을 시작하면 개발자가 보통 제어권을 잃는다는 점입니다. 실행은 동기식이며 내부가 투명하지 않습니다.

저는 AgentCritical Resource 사이에 위치하는 프록시, 즉 미들웨어가 필요했습니다.

이를 SudoMode라고 부릅니다. 간단한 Intercept and Verify 루프를 기반으로 동작합니다:

  • Intercept: SDK가 위험한 함수(예: stripe.charge)를 래핑합니다.
  • Evaluate: 로컬 정책 엔진이 policies.yaml을 확인합니다.
  • Pause: 행동이 “High Risk”로 판단되면 Python 스레드가 sleep합니다.
  • Poll: SDK가 2초마다 Governance Server에 폴링합니다.
  • Resume: 대시보드에서 사람이 요청을 승인하면 서버가 인증 토큰을 반환하고, 스크립트가 다시 실행됩니다.

코드: 구성으로서의 정책

Python 코드에 규칙을 직접 넣고 싶지는 않았습니다. Kubernetes 매니페스트나 OPA(Open Policy Agent)와 같이 선언형으로 만들고 싶었습니다.

다음은 정책이 어떻게 보이는지에 대한 예시입니다. 에이전트의 “Blast Radius”(폭발 반경)를 정의하는 간단한 YAML입니다:

rules:
  # Rule 1: Hard Block (The Firewall)
  - id: "protect-production-db"
    resource: "postgres"
    action: "drop_table"
    response: "DENY"

  # Rule 2: Conditional Approval (The Sudo Command)
  - id: "spending-limit"
    resource: "stripe"
    action: "charge"
    condition: "args.amount > 50"
    response: "REQUIRE_APPROVAL"

“대기” 메커니즘 (엔지니어링 도전)

가장 어려운 엔지니어링 도전은 async/sync 불일치를 처리하는 것이었습니다.

대부분의 에이전트 프레임워크(CrewAI, LangChain)는 도구가 즉시 값을 반환하기를 기대합니다. “인간 대기”를 기본적으로 잘 처리하지 못합니다. 오류를 발생시키면 에이전트가 크래시하거나 오류를 환상적으로 “수정”하려고 합니다.

이를 해결하기 위해 클라이언트 SDK에 Long Polling 루프를 구현했습니다. 예외를 발생시키는 대신, 클라이언트는 느린 네트워크 요청을 흉내 내는 while True 루프로 들어갑니다.

def execute(self, resource, action, args):
    # 1. Initial Check
    decision = self.check(resource, action, args)

    # 2. The Waiting Game
    if decision['status'] == 'REQUIRE_APPROVAL':
        # Log to stdout so the developer sees the pause
        logger.info(f"Paused. Waiting for Admin (ID: {decision['request_id']})...")

        while True:
            time.sleep(2)

            # POLL THE SERVER
            status = self._get_request_status(decision['request_id'])

            if status == "APPROVED":
                return True
            elif status == "REJECTED":
                raise PermissionError("Request Denied by Admin.")

에이전트에게는 API가 응답하는 데 시간이 걸리는 것처럼 보이고, 인간에게는 대시보드에 “보류 중인 요청”으로 보입니다.

왜 오픈소스인가?

나는 이것을 필요해서 만들었지만, 에이전트를 만드는 모든 개발자가 이 휠을 다시 만들고 있다는 것을 깨달았다. 우리는 모두 이상한 input() 루프를 해킹해서 에이전트를 확인하고 있다.

나는 SudoMode를 표준적인, 바로 사용할 수 있는 안전망으로 오픈소스화했다. 완벽하지는 않다—MVP이지만—지능(LLM)과 제어(Policy)를 효과적으로 분리한다.

현재 아키텍처에 대한 피드백을 찾고 있다, 특히 에이전트가 대기 중에 충돌했을 때 분산 상태를 어떻게 처리할지에 대해.

에이전트를 만들면서 보안 때문에 잠을 못 이루고 있다면, 레포를 확인해 보라. 이 아키텍처가 너의 사용 사례에 맞는지 보고 싶다.

GitHub Repo:

Back to Blog

관련 글

더 보기 »