🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

Tool Safety와 Sandboxing 완벽 가이드 - 슬라이드 1/6
A

AI Generated

2025. 12. 27. · 4 Views

Tool Safety와 Sandboxing 완벽 가이드

LLM 기반 AI 에이전트가 도구를 안전하게 실행하도록 만드는 방법을 다룹니다. 위험한 명령어 차단부터 샌드박스 환경 구축, 권한 관리까지 실무에서 반드시 알아야 할 보안 기법을 초급자도 이해할 수 있게 설명합니다.


목차

  1. 위험한_도구_실행_방지
  2. 샌드박스_환경
  3. 권한_관리
  4. 안전한_도구_래퍼_실습
  5. 코드_실행_샌드박스_실습

1. 위험한 도구 실행 방지

어느 날 김개발 씨는 회사에서 개발 중인 AI 챗봇에 파일 관리 기능을 추가하고 있었습니다. 테스트 중에 챗봇에게 "임시 파일 정리해줘"라고 말했는데, 갑자기 중요한 설정 파일들이 사라져버렸습니다.

AI가 rm -rf 명령어를 실행해버린 것입니다.

위험한 도구 실행 방지란 AI 에이전트가 시스템에 치명적인 영향을 줄 수 있는 명령어나 작업을 사전에 차단하는 것입니다. 마치 어린아이에게 가위는 주되 칼은 주지 않는 것과 같습니다.

이를 통해 AI가 의도치 않게 시스템을 손상시키거나 민감한 데이터를 유출하는 것을 막을 수 있습니다.

다음 코드를 살펴봅시다.

# 위험한 명령어 패턴 정의
DANGEROUS_PATTERNS = [
    r'rm\s+-rf',           # 강제 재귀 삭제
    r'DROP\s+TABLE',       # 테이블 삭제
    r'DELETE\s+FROM.*WHERE\s+1=1',  # 전체 삭제
    r'chmod\s+777',        # 위험한 권한 설정
    r'eval\s*\(',          # 동적 코드 실행
]

import re

def is_safe_command(command: str) -> tuple[bool, str]:
    """명령어의 안전성을 검사합니다"""
    for pattern in DANGEROUS_PATTERNS:
        if re.search(pattern, command, re.IGNORECASE):
            return False, f"차단된 패턴: {pattern}"
    return True, "안전한 명령어입니다"

# 사용 예시
command = "rm -rf /important/data"
is_safe, message = is_safe_command(command)
print(f"안전 여부: {is_safe}, 메시지: {message}")

김개발 씨는 입사 6개월 차 주니어 개발자입니다. 요즘 회사에서 가장 핫한 프로젝트인 AI 어시스턴트 개발팀에 배치되어 신나게 일하고 있었습니다.

그런데 오늘 아침, 테스트 서버에서 중요한 로그 파일들이 모두 사라지는 사고가 발생했습니다. "대체 무슨 일이죠?" 김개발 씨가 당황해서 물었습니다.

선배 개발자 박시니어 씨가 로그를 확인하더니 한숨을 쉬었습니다. "AI가 정리 작업을 하다가 rm -rf 명령어를 실행했네요.

도구 안전성 검사를 안 넣었군요." 그렇다면 위험한 도구 실행 방지란 정확히 무엇일까요? 쉽게 비유하자면, 이것은 마치 자동차의 안전장치와 같습니다.

자동차는 엄청난 속도로 달릴 수 있지만, 에어백, ABS, 속도 제한 장치 같은 안전장치가 있어서 위험한 상황을 예방합니다. AI 에이전트도 마찬가지입니다.

강력한 도구를 사용할 수 있지만, 그 힘이 잘못된 방향으로 쓰이지 않도록 안전장치가 필요합니다. 이런 안전장치가 없던 시절에는 어땠을까요?

초기 AI 시스템들은 사용자의 요청을 그대로 실행하는 경우가 많았습니다. "모든 파일 삭제해줘"라고 하면 정말로 모든 파일을 삭제했습니다.

프롬프트 인젝션 공격에도 취약했습니다. 악의적인 사용자가 교묘한 질문을 통해 AI를 속여 위험한 작업을 수행하게 만들 수 있었습니다.

바로 이런 문제를 해결하기 위해 명령어 필터링 시스템이 등장했습니다. 위의 코드를 한 줄씩 살펴보겠습니다.

먼저 DANGEROUS_PATTERNS 리스트에는 절대로 실행해서는 안 되는 명령어 패턴들이 정규표현식으로 정의되어 있습니다. rm -rf는 파일을 강제로 재귀 삭제하는 명령어입니다.

DROP TABLE은 데이터베이스 테이블을 통째로 삭제합니다. chmod 777은 모든 사용자에게 모든 권한을 부여해서 보안 구멍을 만듭니다.

is_safe_command 함수는 입력된 명령어가 이 위험한 패턴들과 일치하는지 검사합니다. 정규표현식의 re.search 함수를 사용하여 명령어 문자열 안에 위험한 패턴이 있는지 찾습니다.

하나라도 발견되면 즉시 False를 반환하고 어떤 패턴에 걸렸는지 알려줍니다. 실제 현업에서는 이보다 훨씬 정교한 필터링이 필요합니다.

예를 들어 클라우드 서비스를 운영한다면 AWS 키 삭제, 인스턴스 종료 같은 명령어도 차단해야 합니다. 금융 시스템이라면 대량 송금, 계좌 삭제 같은 작업에 추가 확인 절차를 넣어야 합니다.

하지만 주의할 점도 있습니다. 너무 엄격한 필터링은 정상적인 작업마저 막아버릴 수 있습니다.

예를 들어 "rm"이라는 단어가 들어간 모든 명령어를 차단하면 "confirm", "inform" 같은 단어가 포함된 정상적인 작업도 막힙니다. 화이트리스트와 블랙리스트를 적절히 조합하는 것이 중요합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 조언을 듣고 김개발 씨는 명령어 필터링 시스템을 구현했습니다.

이제 AI가 위험한 명령어를 실행하려고 하면 "이 작업은 보안 정책에 의해 차단되었습니다"라는 메시지가 나타납니다. 테스트 서버는 더 이상 사고가 발생하지 않았습니다.

실전 팁

💡 - 위험 패턴 목록은 주기적으로 업데이트하고, 새로운 공격 기법을 반영하세요

  • 차단된 명령어는 반드시 로깅하여 보안 감사에 활용하세요
  • 정규표현식은 대소문자 구분 없이 검사하도록 re.IGNORECASE 플래그를 사용하세요

2. 샌드박스 환경

김개발 씨가 명령어 필터링을 구현한 후, 박시니어 씨가 물었습니다. "그런데 만약 필터에 걸리지 않는 새로운 위험한 명령어가 나타나면 어떻게 할 건가요?" 김개발 씨는 잠시 생각에 잠겼습니다.

아무리 블랙리스트를 잘 만들어도 모든 위험을 막을 수는 없다는 것을 깨달았습니다.

샌드박스 환경이란 프로그램이 실행될 수 있는 격리된 공간을 말합니다. 마치 어린이 놀이터의 모래 놀이터(sandbox)처럼, 아이들이 마음껏 놀아도 바깥 세상에 영향을 주지 않는 안전한 공간입니다.

AI가 어떤 코드를 실행하든 샌드박스 밖의 시스템에는 영향을 줄 수 없습니다.

다음 코드를 살펴봅시다.

import subprocess
import tempfile
import os

class CodeSandbox:
    """코드를 격리된 환경에서 실행하는 샌드박스"""

    def __init__(self, timeout: int = 5):
        self.timeout = timeout
        self.allowed_modules = ['math', 'json', 'datetime']

    def execute(self, code: str) -> dict:
        """코드를 샌드박스에서 안전하게 실행합니다"""
        # 임시 디렉토리에서 격리 실행
        with tempfile.TemporaryDirectory() as tmpdir:
            script_path = os.path.join(tmpdir, 'script.py')
            with open(script_path, 'w') as f:
                f.write(code)

            try:
                # 제한된 환경에서 실행
                result = subprocess.run(
                    ['python', script_path],
                    capture_output=True,
                    timeout=self.timeout,
                    cwd=tmpdir,  # 작업 디렉토리 격리
                    text=True
                )
                return {'success': True, 'output': result.stdout}
            except subprocess.TimeoutExpired:
                return {'success': False, 'error': '실행 시간 초과'}

박시니어 씨는 화이트보드에 그림을 그리기 시작했습니다. "블랙리스트 방식은 이미 알려진 위험만 막을 수 있어요.

하지만 우리가 모르는 새로운 위험이 항상 있죠. 그래서 샌드박스가 필요합니다." 김개발 씨가 고개를 갸웃거렸습니다.

"샌드박스요? 모래 놀이터 말인가요?" "정확해요!" 박시니어 씨가 웃으며 대답했습니다.

"어린이 놀이터의 모래 놀이터를 생각해보세요. 아이들이 그 안에서 모래성을 쌓든, 구덩이를 파든, 밖의 세상에는 아무 영향이 없죠.

프로그래밍의 샌드박스도 마찬가지입니다." 샌드박스는 프로그램이 실행되는 격리된 환경입니다. 이 안에서는 무슨 짓을 해도 바깥 시스템에 영향을 줄 수 없습니다.

파일을 삭제해도 샌드박스 안의 파일만 삭제됩니다. 네트워크 요청을 보내도 차단되거나 제한됩니다.

샌드박스가 없던 시절에는 어떤 문제가 있었을까요? 테스트 코드가 실수로 프로덕션 데이터베이스에 연결되어 데이터를 날려버리는 사고가 빈번했습니다.

악성 코드가 시스템 전체를 감염시키는 일도 많았습니다. 한 프로그램의 버그가 다른 프로그램에 영향을 주기도 했습니다.

위의 코드에서 CodeSandbox 클래스가 어떻게 격리를 구현하는지 살펴보겠습니다. 먼저 tempfile.TemporaryDirectory를 사용하여 임시 디렉토리를 만듭니다.

이 디렉토리는 코드 블록이 끝나면 자동으로 삭제됩니다. 코드는 이 임시 공간에서만 실행됩니다.

subprocess.runtimeout 파라미터는 무한 루프를 방지합니다. AI가 실수로 while True를 실행해도 5초 후에 강제 종료됩니다.

cwd 파라미터는 작업 디렉토리를 임시 디렉토리로 제한하여 다른 디렉토리에 접근하기 어렵게 만듭니다. 실제 프로덕션 환경에서는 더 강력한 격리가 필요합니다.

Docker 컨테이너를 사용하면 파일 시스템, 네트워크, 프로세스를 완전히 격리할 수 있습니다. gVisorFirecracker 같은 마이크로VM을 사용하면 커널 수준에서 격리됩니다.

AWS Lambda나 Google Cloud Functions 같은 서버리스 플랫폼도 강력한 샌드박스를 제공합니다. 하지만 주의할 점이 있습니다.

샌드박스도 완벽하지 않습니다. 컨테이너 탈출(Container Escape) 취약점이 발견되기도 합니다.

따라서 샌드박스는 심층 방어(Defense in Depth) 전략의 한 층으로 생각해야 합니다. 명령어 필터링, 권한 관리, 샌드박스를 함께 사용해야 안전합니다.

김개발 씨는 고개를 끄덕였습니다. "아, 여러 겹의 보안이 필요하군요.

마치 양파 같네요." 박시니어 씨가 웃었습니다. "맞아요.

보안은 양파처럼 여러 겹이어야 합니다."

실전 팁

💡 - 프로덕션에서는 Docker나 gVisor 같은 컨테이너 기술을 사용하세요

  • 샌드박스 내부에서도 네트워크 접근을 제한하여 데이터 유출을 방지하세요
  • 리소스 제한(CPU, 메모리, 디스크)을 설정하여 서비스 거부 공격을 막으세요

3. 권한 관리

며칠 후 김개발 씨는 새로운 문제에 직면했습니다. AI 어시스턴트가 여러 사용자를 대상으로 서비스되기 시작했는데, 일반 사용자의 AI가 관리자 기능에 접근하려는 시도가 발견된 것입니다.

박시니어 씨는 "이제 권한 관리에 대해 배워야 할 때가 됐네요"라고 말했습니다.

권한 관리란 누가 어떤 도구를 사용할 수 있는지 통제하는 시스템입니다. 마치 회사에서 직급에 따라 접근할 수 있는 문서가 다른 것처럼, AI 에이전트도 역할에 따라 사용할 수 있는 도구가 달라야 합니다.

이를 통해 최소 권한 원칙을 구현할 수 있습니다.

다음 코드를 살펴봅시다.

from enum import Enum
from typing import Callable
from functools import wraps

class Role(Enum):
    GUEST = 1
    USER = 2
    ADMIN = 3

class ToolPermission:
    """도구별 권한을 관리하는 시스템"""

    def __init__(self):
        self.tool_permissions: dict[str, Role] = {}

    def require_role(self, min_role: Role):
        """최소 필요 권한을 지정하는 데코레이터"""
        def decorator(func: Callable):
            self.tool_permissions[func.__name__] = min_role

            @wraps(func)
            def wrapper(user_role: Role, *args, **kwargs):
                if user_role.value < min_role.value:
                    raise PermissionError(
                        f"권한 부족: {min_role.name} 이상 필요"
                    )
                return func(*args, **kwargs)
            return wrapper
        return decorator

# 사용 예시
perm = ToolPermission()

@perm.require_role(Role.USER)
def read_file(path: str) -> str:
    return f"파일 읽기: {path}"

@perm.require_role(Role.ADMIN)
def delete_file(path: str) -> str:
    return f"파일 삭제: {path}"

박시니어 씨가 화이트보드에 피라미드를 그렸습니다. 맨 아래는 GUEST, 중간은 USER, 꼭대기는 ADMIN입니다.

"권한 관리는 이 피라미드와 같아요. 위로 갈수록 더 많은 것을 할 수 있죠." 김개발 씨가 물었습니다.

"왜 모든 사용자에게 모든 기능을 주면 안 되나요? 편하잖아요." 박시니어 씨가 고개를 저었습니다.

"보안의 황금률 중 하나가 **최소 권한 원칙(Principle of Least Privilege)**입니다. 각 사용자에게는 업무에 필요한 최소한의 권한만 부여해야 합니다." 이것은 마치 호텔 방 열쇠와 같습니다.

투숙객은 자기 방 열쇠만 받습니다. 청소 직원은 여러 방을 열 수 있는 마스터키가 있지만, 금고는 열지 못합니다.

호텔 매니저만 모든 곳에 접근할 수 있습니다. 각자 필요한 만큼만 접근 권한을 가지는 것입니다.

권한 관리가 없던 시절에는 큰 문제가 있었습니다. 한 직원의 계정이 해킹되면 회사 전체 시스템이 위험해졌습니다.

실수로 중요한 데이터를 삭제하는 사고도 빈번했습니다. 내부자 위협에도 취약했습니다.

위의 코드를 살펴보겠습니다. Role 열거형은 세 가지 역할을 정의합니다.

각 역할에는 숫자 값이 있어서 권한 수준을 비교할 수 있습니다. GUEST는 1, USER는 2, ADMIN은 3입니다.

require_role 데코레이터가 핵심입니다. 이 데코레이터를 함수에 붙이면, 그 함수는 지정된 최소 권한 이상을 가진 사용자만 실행할 수 있습니다.

권한이 부족하면 PermissionError가 발생합니다. 예를 들어 read_file 함수는 USER 권한이 필요합니다.

GUEST는 실행할 수 없지만 USER와 ADMIN은 가능합니다. delete_file 함수는 ADMIN 권한이 필요합니다.

USER도 실행할 수 없습니다. 실제 서비스에서는 더 세밀한 권한 관리가 필요합니다.

**역할 기반 접근 제어(RBAC)**를 사용하면 역할별로 권한 묶음을 정의할 수 있습니다. **속성 기반 접근 제어(ABAC)**를 사용하면 시간, 위치, 데이터 민감도 등 다양한 조건을 고려할 수 있습니다.

주의할 점도 있습니다. 권한 검사를 클라이언트 측에서만 하면 안 됩니다.

악의적인 사용자가 클라이언트를 조작하여 우회할 수 있기 때문입니다. 서버 측에서 반드시 권한을 검증해야 합니다.

김개발 씨는 이해가 되었습니다. "아, 그래서 프론트엔드에서 버튼을 숨기는 것만으로는 부족하고, 백엔드에서도 검사해야 하는 거군요." 박시니어 씨가 엄지를 치켜세웠습니다.

"정확합니다!"

실전 팁

💡 - 권한 검사는 반드시 서버 측에서 수행하세요

  • 역할을 세분화하되 너무 복잡하지 않게 유지하세요
  • 권한 변경 내역을 로깅하여 감사 추적이 가능하게 하세요

4. 안전한 도구 래퍼 실습

이론 공부를 마친 김개발 씨는 실제로 코드를 작성해보기로 했습니다. "직접 만들어봐야 제대로 이해할 수 있죠." 박시니어 씨가 첫 번째 실습 과제를 주었습니다.

"위험한 도구를 안전하게 감싸는 래퍼를 만들어보세요."

안전한 도구 래퍼란 위험할 수 있는 도구를 감싸서 안전하게 만드는 계층입니다. 마치 날카로운 칼에 칼집을 씌우는 것처럼, 도구의 기능은 그대로 유지하면서 위험한 부분은 제어합니다.

입력 검증, 출력 필터링, 로깅, 속도 제한 등을 추가합니다.

다음 코드를 살펴봅시다.

import re
import logging
from datetime import datetime
from typing import Any

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class SafeToolWrapper:
    """도구를 안전하게 감싸는 래퍼 클래스"""

    def __init__(self, name: str):
        self.name = name
        self.call_count = 0
        self.max_calls_per_minute = 10
        self.last_reset = datetime.now()

    def validate_input(self, input_data: str) -> bool:
        """입력값의 안전성을 검증합니다"""
        dangerous = [';', '&&', '||', '`', '$(',]
        return not any(d in input_data for d in dangerous)

    def sanitize_output(self, output: str) -> str:
        """출력에서 민감한 정보를 제거합니다"""
        # API 키, 비밀번호 패턴 마스킹
        patterns = [
            (r'api[_-]?key["\s:=]+\w+', 'api_key=*****'),
            (r'password["\s:=]+\w+', 'password=*****'),
        ]
        for pattern, replacement in patterns:
            output = re.sub(pattern, replacement, output, flags=re.I)
        return output

    def execute(self, tool_func, *args) -> dict[str, Any]:
        """도구를 안전하게 실행합니다"""
        # 속도 제한 검사
        if self.call_count >= self.max_calls_per_minute:
            return {'error': '호출 한도 초과'}

        # 입력 검증
        for arg in args:
            if isinstance(arg, str) and not self.validate_input(arg):
                logger.warning(f"위험한 입력 차단: {arg}")
                return {'error': '유효하지 않은 입력'}

        # 실행 및 결과 처리
        self.call_count += 1
        result = tool_func(*args)
        return {'result': self.sanitize_output(str(result))}

김개발 씨는 빈 에디터 화면을 바라보며 생각에 잠겼습니다. 어디서부터 시작해야 할까요?

박시니어 씨가 힌트를 주었습니다. "래퍼는 선물 포장과 같아요.

내용물은 그대로 두고 겉에 보호 층을 씌우는 거죠." 도구 래퍼의 핵심은 원래 도구의 기능을 해치지 않으면서 안전장치를 추가하는 것입니다. 마치 콘센트에 안전 덮개를 씌우는 것과 같습니다.

전기는 여전히 사용할 수 있지만, 아이들이 손가락을 넣는 사고는 막을 수 있습니다. 위의 코드에서 SafeToolWrapper 클래스는 세 가지 보호 기능을 제공합니다.

첫째, validate_input 메서드는 입력값에 위험한 문자가 있는지 검사합니다. 세미콜론(;)이나 파이프(|) 같은 문자는 명령어 인젝션 공격에 사용될 수 있습니다.

예를 들어 "file.txt; rm -rf /"라는 입력이 들어오면 원래 명령어 뒤에 위험한 명령어가 실행될 수 있습니다. 둘째, sanitize_output 메서드는 출력에서 민감한 정보를 숨깁니다.

API 키나 비밀번호가 실수로 노출되는 것을 방지합니다. 정규표현식을 사용하여 "api_key=abc123" 같은 패턴을 "api_key=*****"로 바꿉니다.

셋째, **속도 제한(Rate Limiting)**입니다. 분당 최대 호출 횟수를 제한하여 서비스 남용을 방지합니다.

악의적인 사용자가 무한 루프로 도구를 호출하는 것을 막을 수 있습니다. 실제 프로덕션에서는 더 많은 기능이 필요합니다.

재시도 로직을 추가하여 일시적인 오류에서 복구할 수 있습니다. 캐싱을 추가하여 동일한 요청에 대한 반복 호출을 줄일 수 있습니다.

타임아웃을 설정하여 느린 도구가 전체 시스템을 막지 않게 할 수 있습니다. 김개발 씨가 코드를 실행해보았습니다.

"file.txt; cat /etc/passwd"라는 위험한 입력을 넣자 "유효하지 않은 입력"이라는 에러가 반환되었습니다. "오, 잘 막히네요!" 박시니어 씨가 웃었습니다.

"좋아요. 하지만 이건 시작일 뿐이에요.

실제 서비스에서는 더 정교한 필터가 필요합니다." 주의할 점이 있습니다. 래퍼가 너무 많은 일을 하면 성능에 영향을 줍니다.

모든 입력과 출력을 검사하는 것은 비용이 듭니다. 핵심적인 보안 검사에 집중하고, 나머지는 다른 계층에서 처리하는 것이 좋습니다.

실전 팁

💡 - 입력 검증은 화이트리스트 방식이 블랙리스트보다 안전합니다

  • 출력 필터링 패턴은 정기적으로 업데이트하세요
  • 속도 제한은 사용자별로 다르게 설정하는 것이 유연합니다

5. 코드 실행 샌드박스 실습

두 번째 실습 시간입니다. 김개발 씨는 이제 실제로 사용자가 입력한 코드를 안전하게 실행하는 샌드박스를 만들어야 합니다.

"이건 정말 위험한 기능이에요. 신중하게 구현해야 합니다." 박시니어 씨가 진지하게 말했습니다.

코드 실행 샌드박스는 사용자가 제출한 코드를 격리된 환경에서 실행하는 시스템입니다. 마치 실험실의 안전 후드 안에서 위험한 화학 실험을 하는 것처럼, 어떤 코드가 실행되든 시스템 전체에 영향을 주지 않습니다.

리소스 제한, 시간 제한, 네트워크 차단 등 여러 안전장치를 포함합니다.

다음 코드를 살펴봅시다.

import ast
import sys
from io import StringIO
from types import CodeType
from typing import Any

class SecurePythonSandbox:
    """Python 코드를 안전하게 실행하는 샌드박스"""

    FORBIDDEN_NAMES = {
        'open', 'exec', 'eval', 'compile', '__import__',
        'globals', 'locals', 'vars', 'dir', 'getattr',
        'setattr', 'delattr', 'input', 'breakpoint'
    }

    def __init__(self, max_ops: int = 10000):
        self.max_ops = max_ops
        self.op_count = 0

    def check_code_safety(self, code: str) -> tuple[bool, str]:
        """코드의 안전성을 정적 분석합니다"""
        try:
            tree = ast.parse(code)
        except SyntaxError as e:
            return False, f"문법 오류: {e}"

        for node in ast.walk(tree):
            if isinstance(node, ast.Name):
                if node.id in self.FORBIDDEN_NAMES:
                    return False, f"금지된 함수: {node.id}"
            elif isinstance(node, ast.Import):
                return False, "import 문 사용 불가"
        return True, "안전한 코드"

    def execute(self, code: str) -> dict[str, Any]:
        """코드를 안전하게 실행합니다"""
        is_safe, message = self.check_code_safety(code)
        if not is_safe:
            return {'success': False, 'error': message}

        # 제한된 내장 함수만 제공
        safe_builtins = {
            'print': print, 'len': len, 'range': range,
            'int': int, 'str': str, 'list': list,
            'dict': dict, 'sum': sum, 'max': max, 'min': min,
        }

        # 출력 캡처
        old_stdout = sys.stdout
        sys.stdout = StringIO()

        try:
            exec(code, {'__builtins__': safe_builtins}, {})
            output = sys.stdout.getvalue()
            return {'success': True, 'output': output}
        except Exception as e:
            return {'success': False, 'error': str(e)}
        finally:
            sys.stdout = old_stdout

김개발 씨는 긴장했습니다. 사용자 코드를 실행한다는 것은 마치 낯선 사람에게 집 열쇠를 맡기는 것과 같습니다.

무슨 일이 벌어질지 알 수 없습니다. 하지만 코드 학습 플랫폼이나 온라인 IDE에서는 꼭 필요한 기능입니다.

박시니어 씨가 설명을 시작했습니다. "코드 실행 샌드박스는 두 단계 방어를 사용합니다.

첫째, 실행 전에 코드를 분석합니다. 둘째, 실행 환경 자체를 제한합니다." 첫 번째 단계는 정적 분석입니다.

위의 코드에서 check_code_safety 메서드가 이 역할을 합니다. Python의 ast 모듈을 사용하여 코드를 구문 트리로 변환합니다.

그런 다음 트리를 순회하면서 위험한 패턴을 찾습니다. FORBIDDEN_NAMES에는 사용을 금지할 함수 이름들이 정의되어 있습니다.

open은 파일을 열 수 있고, execeval은 동적으로 코드를 실행할 수 있습니다. import는 모듈을 불러올 수 있습니다.

이런 함수들이 코드에 있으면 즉시 거부됩니다. import 문도 금지됩니다.

import os를 허용하면 os.system으로 시스템 명령어를 실행할 수 있습니다. import subprocess도 마찬가지입니다.

외부 모듈 접근을 완전히 차단하는 것이 안전합니다. 두 번째 단계는 제한된 실행 환경입니다.

Python의 exec 함수는 전역 변수와 내장 함수를 직접 지정할 수 있습니다. safe_builtins 딕셔너리에는 안전한 함수만 포함됩니다.

print, len, range 같은 기본적인 함수만 허용하고, 나머지는 차단합니다. 출력 캡처도 중요합니다.

sys.stdout을 StringIO로 교체하면 print 출력을 문자열로 받을 수 있습니다. 코드 실행이 끝나면 원래 stdout으로 복원합니다.

finally 블록을 사용하여 예외가 발생해도 반드시 복원되도록 합니다. 실제 프로덕션에서는 이것만으로 부족합니다.

리소스 제한이 필요합니다. 무한 루프를 돌리는 코드는 CPU를 100% 사용합니다.

메모리를 계속 할당하는 코드는 시스템을 다운시킬 수 있습니다. Linux의 cgroupsulimit를 사용하여 CPU 시간, 메모리, 프로세스 수를 제한해야 합니다.

김개발 씨가 테스트를 해보았습니다. "print(1+1)"은 정상적으로 "2"를 출력했습니다.

하지만 "import os"를 입력하자 "import 문 사용 불가"라는 에러가 반환되었습니다. "open('/etc/passwd')"도 "금지된 함수: open"으로 차단되었습니다.

박시니어 씨가 마무리했습니다. "잘했어요.

하지만 기억하세요. 보안은 끝이 없는 싸움입니다.

새로운 우회 기법이 계속 발견됩니다. 정기적으로 업데이트하고 테스트해야 합니다."

실전 팁

💡 - 프로덕션에서는 Docker와 함께 사용하여 운영체제 수준 격리를 추가하세요

  • 실행 시간 제한과 메모리 제한을 반드시 설정하세요
  • 새로운 우회 기법에 대비하여 정기적으로 보안 테스트를 수행하세요

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Python#LLM#Sandbox#Security#ToolSafety#LLM,안전성,Sandbox

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스

Phase 2 공격 기법 이해와 방어 실전 가이드

웹 애플리케이션 보안의 핵심인 공격 기법과 방어 전략을 실습 중심으로 배웁니다. 인증 우회부터 SQL Injection, XSS, CSRF까지 실제 공격 시나리오를 이해하고 방어 코드를 직접 작성해봅니다.

Context Fundamentals - AI 컨텍스트의 기본 원리

AI 에이전트 개발의 핵심인 컨텍스트 관리를 다룹니다. 시스템 프롬프트 구조부터 Attention Budget, Progressive Disclosure까지 실무에서 바로 적용할 수 있는 컨텍스트 최적화 전략을 배웁니다.

Phase 1 보안 사고방식 구축 완벽 가이드

초급 개발자가 보안 전문가로 성장하기 위한 첫걸음입니다. 해커의 관점에서 시스템을 바라보는 방법부터 OWASP Top 10, 포트 스캐너 구현, 실제 침해사고 분석까지 보안의 기초 체력을 다집니다.

프로덕션 워크플로 배포 완벽 가이드

LLM 기반 애플리케이션을 실제 운영 환경에 배포하기 위한 워크플로 최적화, 캐싱 전략, 비용 관리 방법을 다룹니다. Airflow와 서버리스 아키텍처를 활용한 실습까지 포함하여 초급 개발자도 프로덕션 수준의 배포를 할 수 있도록 안내합니다.

워크플로 모니터링과 디버깅 완벽 가이드

LLM 기반 워크플로의 실행 상태를 추적하고, 문제를 진단하며, 성능을 최적화하는 방법을 다룹니다. LangSmith 통합부터 커스텀 모니터링 시스템 구축까지 실무에서 바로 적용할 수 있는 내용을 담았습니다.