
LLM이 만든 코드는 “우리 코드”가 아니라 외부 입력(External Input) 과 동일하게 취급해야 합니다.
즉, LLM 생성 코드를 실행하는 순간부터는 서버가 ‘코드 실행 플랫폼’이 되며, 공격자 관점에서 아래가 모두 가능합니다.
- 악성 코드 실행: 파일 삭제/변조, 데이터 유출, 채굴 등
- 샌드박스 탈출: 커널/런타임/설정 실수로 호스트·클러스터 권한 획득
- 리소스 고갈(DoS): 무한 루프/메모리·디스크 폭주로 노드/네임스페이스 장애
- 네트워크 악용: 내부망 스캔, C2 통신, 데이터 외부 반출
따라서 핵심은 단일 기법이 아니라 “다단계 격리 + 최소권한 + 정책 강제 + 감시/증적” 조합입니다.
(A) LLM/에이전트
→ 코드/입력/리소스 한도/필요 권한(capabilities)을 “선언”
(B) Code Execution Gateway(API)
→ 정적 검증(길이/금칙어/의존성/출력 제한) + 실행 요청 서명/감사로그
(C) Sandbox Orchestrator
→ K8s에 ephemeral Pod/Job 생성(요청당 1개) 후 실행
(D) 다단계 격리(Defense-in-Depth)
- K8s 정책(PSA/PSS, RBAC, Quota, NetworkPolicy)
- 컨테이너 하드닝(seccomp, AppArmor/SELinux, non-root, read-only, drop caps)
- (선택) 런타임 격리 gVisor/Kata
(E) 결과 수집
→ stdout/stderr + 지정된 output 디렉터리(아티팩트)만 회수 후 Pod 삭제
이 구조 자체가 “언트러스트드 실행”의 표준 패턴에 가깝습니다.
K8s에서 가장 중요한 보안 설계 포인트 12가지
아래 12가지는 “기본값으로 강제”가 목표입니다. (사용자가 선택하는 옵션이 되면 운영 중 틈이 생깁니다)
네임스페이스 분리 + 전용 노드 풀(권장)
sandbox-exec전용 namespace- 가능하면 전용 node pool(taint/toleration)로 일반 워크로드와 분리
ServiceAccount 최소권한(RBAC) “생성 전용”
- 실행 Pod는 클러스터 API 접근이 필요 없도록(기본 목표)
- Orchestrator만 Pod/Job 생성 권한 보유
- 실행 Pod에는
automountServiceAccountToken: false권장(기본 토큰 차단)
Pod Security Admission(PSA)로 “Restricted” 강제
Kubernetes의 Pod Security Standards(PSS)는 Privileged/Baseline/Restricted 3단계이며, 실행 샌드박스는 원칙적으로 Restricted를 목표로 합니다.
컨테이너 보안 컨텍스트 “3종 세트”
runAsNonRoot: trueallowPrivilegeEscalation: falsereadOnlyRootFilesystem: true
Linux capabilities “ALL drop” 기본
- 기본적으로
capabilities.drop: ["ALL"] - 정말 필요한 경우만 최소 추가
seccomp: RuntimeDefault 기본 + 필요시 커스텀
- 일단
RuntimeDefault는 거의 필수(기본 syscall 면적 축소) - 커스텀은 운영 성숙도 올라간 뒤 단계적으로(차단 로그 기반)
NetworkPolicy: 기본 egress/ingress 모두 deny
K8s는 기본이 “모든 egress 허용”이기 때문에, 정책이 없으면 내부망 스캔/외부 유출이 가능합니다.
- 원칙: sandbox Pod는 네트워크 0
- 예외: “허용된 프록시”로만 egress 허용(도메인/목적지 통제)
리소스 한도: CPU/Mem/Ephemeral storage + 시간 제한
resources.limits에ephemeral-storage까지 포함- 실행 시간 timeout(예: 10~30초)
- 파일 개수/출력 크기 제한(게이트웨이에서 stdout/stderr 상한)
Quota/LimitRange로 네임스페이스 전체 폭주 방지
- 동시 실행 Pod 수, 총 CPU/Mem 상한을 namespace 단위로 묶기
이미지/의존성 전략
- “인터넷 없이” 실행이 기본이면: 미리 빌드된 런타임 이미지 + 내부 패키지 레지스트리 사용
- pip install 같은 동적 설치는 보안·재현성·성능 모두 악화
감사(Audit)와 증적
K8s Audit 로그는 “누가 무엇을 만들고/exec 했는지”를 추적하는 핵심 증적입니다.
- sandbox namespace의
create pods/jobs,exec,portforward같은 이벤트를 반드시 로깅
(선택) 런타임 격리: gVisor / Kata
컨테이너만으로는 커널 공격면이 남습니다. 더 강한 격리가 필요하면
- gVisor: userspace kernel로 격리 강화(특히 멀티테넌트/고위험 실행에 유리)
- Kata Containers: 경량 VM 계열 격리(보다 강한 경계)
llm-sandbox(vndee)로 K8s Ephemeral 실행 구성하기
도구 위치
vndee/llm-sandbox는 Docker/Kubernetes/Podman 같은 백엔드에서 세션 기반으로 코드를 실행하도록 설계된 런타임입니다. K8s 백엔드 및 Pod manifest 커스터마이징 예시도 제공합니다.
파이썬 실행 예시(개념)
아래는 “K8s 백엔드 + Pod 보안옵션을 강제”하는 형태의 전형적인 코드 골격입니다.
from llm_sandbox import SandboxSession
from llm_sandbox.backends import SandboxBackend
pod_manifest = {
"apiVersion": "v1",
"kind": "Pod",
"metadata": {"namespace": "sandbox-exec", "labels": {"app": "llm-sandbox"}},
"spec": {
"automountServiceAccountToken": False,
"securityContext": {
"runAsNonRoot": True,
"runAsUser": 1000,
"seccompProfile": {"type": "RuntimeDefault"},
},
"containers": [{
"name": "py",
"image": "python:3.12-slim",
"securityContext": {
"allowPrivilegeEscalation": False,
"readOnlyRootFilesystem": True,
"capabilities": {"drop": ["ALL"]},
},
"resources": {
"limits": {"cpu":"500m","memory":"512Mi","ephemeral-storage":"1Gi"},
"requests": {"cpu":"100m","memory":"128Mi"}
},
"volumeMounts": [{"name":"tmp","mountPath":"/tmp"}],
}],
"volumes": [{"name":"tmp","emptyDir": {}}],
"restartPolicy": "Never",
}
}
with SandboxSession(
backend=SandboxBackend.KUBERNETES,
lang="python",
pod_manifest=pod_manifest,
) as s:
r = s.run("print(2+2)")
print(r.stdout, r.stderr, r.exit_code)
포인트: “사용자가 실행 옵션을 바꾸게 두지 말고”, 오케스트레이터가 보안 템플릿을 강제하는 구조로 잡으셔야 합니다.
NetworkPolicy 기본 템플릿 (강추)
기본 deny (ingress+egress)
K8s 네트워크 정책은 “적용되는 순간부터 허용 목록만 통과”입니다.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: sandbox-default-deny
namespace: sandbox-exec
spec:
podSelector: {}
policyTypes: ["Ingress", "Egress"]
(예외) 프록시로만 egress 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: sandbox-egress-only-proxy
namespace: sandbox-exec
spec:
podSelector: {}
policyTypes: ["Egress"]
egress:
- to:
- namespaceSelector:
matchLabels:
name: proxy-ns
podSelector:
matchLabels:
app: egress-proxy
ports:
- protocol: TCP
port: 3128
gVisor/Kata 적용 방법(선택지)
gVisor (runtimeClassName)
K8s에서 runtimeClassName: gvisor로 샌드박스 런타임을 선택하는 패턴이 널리 쓰입니다.
spec:
runtimeClassName: gvisor
Kata (runtimeClassName)
Kata도 RuntimeClass로 선택합니다.
spec:
runtimeClassName: kata
선택 가이드
- “단일테넌트 + 낮은 위험도” → 컨테이너 하드닝 + 네트워크 차단만으로 PoC 가능
- “멀티테넌트/고위험 코드 실행/보안팀이 책임지는 플랫폼” → gVisor 또는 Kata를 2차 격리로 고려
보안 운영 가이드/점검 포인트
아래는 내부 사용자(개발/AI팀)에게 “필수 준수사항”으로 제시하기 좋은 형태입니다.
아키텍처/권한
- 실행 Pod에 SA 토큰 자동 마운트 금지(
automountServiceAccountToken: false) - 실행 Pod는 K8s API 접근 불필요(원칙)
- 오케스트레이터만 Pod/Job 생성 권한(RBAC 최소화)
Pod/컨테이너 하드닝
- Non-root 강제 + UID 고정
- Privilege escalation 금지
- Read-only root FS + /tmp만 쓰기
- Capabilities ALL drop
- seccomp RuntimeDefault 기본
네트워크/데이터 유출
- NetworkPolicy 기본 deny(ingress/egress)
- 예외 통신은 “프록시 1곳”으로만(감사·DLP·도메인 통제 가능)
- 내부망 대역(예: RFC1918) 접근 원천 차단 정책 포함
리소스/DoS
- CPU/Mem/Ephemeral storage limit 필수
- 실행 시간 timeout 필수(예: 10~30초)
- 동시 실행 수 제한(ResourceQuota)
- stdout/stderr 및 결과물 크기 제한
로깅/감사/탐지
- K8s Audit 로그 활성화 및 sandbox namespace 집중 수집
- “누가 어떤 코드 실행 요청을 했는지” 애플리케이션 레벨 감사로그(요청ID/사용자/해시)
- 비정상 행위 탐지 룰(예: privileged 시도, exec 시도, 네트워크 차단 위반 등) — SandboxEval 같은 관점의 테스트로 정기 검증
PoC에서 “최소 세트” 추천
K8s + 파이썬 위주 PoC라면, 우선순위를 이렇게 두시면 실패 확률이 확 내려갑니다.
- 요청당 1 Pod(또는 1 Job) 생성 → 실행 → 삭제 (ephemeral)
- PSA(PSS Restricted 지향) + securityContext 3종세트(non-root/RO/NoPrivEsc)
- NetworkPolicy 기본 deny
- 리소스 limit + timeout + quota
- Audit 로그 수집
- (필요 시) gVisor/Kata로 한 단계 더 격리
- 실행 프레임워크는 llm-sandbox로 시작(템플릿 강제 가능)
댓글