에이전트에게 열쇠 대신 출입증을: NVIDIA OpenShell의 게이트웨이·슈퍼바이저·샌드박스 분리, 자격 증명 대리 주입, SMT 정책 증명기(openshell-prover) 심층 해부와 박스 실측

코딩 에이전트에게 셸을 열어 주는 순간, 질문은 “무엇을 할 수 있나”에서 “무엇을 못 하게 할 수 있나”로 바뀐다. 패키지를 설치하고 API를 부르고 토큰을 써야 쓸모가 있지만, 그 토큰이 env 한 줄로 새거나 프롬프트 인젝션 한 번에 엉뚱한 호스트로 나가면 끝이다. NVIDIA/OpenShell은 이 문제를 “에이전트에게 열쇠를 주지 말고, 문지기에게 맡겨라”라는 구조로 푼다. 에이전트는 커널 수준으로 격리된 샌드박스 안에서 돌고, 바깥으로 나가는 모든 TCP·DNS는 신뢰 구역의 슈퍼바이저가 정책으로 판정한 뒤 대신 연결한다. 진짜 API 키는 샌드박스 안에 아예 없고, 허용된 목적지로 가는 요청에만 슈퍼바이저가 끼워 넣는다. 여기에 정책 변경이 무엇을 새로 허용하는지 SMT 솔버로 미리 증명하는 openshell-prover가 붙는다.

OpenShell 한눈에: 에이전트는 격리된 샌드박스 안에, 출구는 슈퍼바이저 하나뿐이고 진짜 API 키는 바깥에서만 끼워 넣는다
OpenShell 한눈에: 에이전트는 격리된 샌드박스 안에, 출구는 슈퍼바이저 하나뿐이고 진짜 API 키는 바깥에서만 끼워 넣는다

오늘(2026-10-23) GitHub Trending Rust 일간 3위(+211, 11:52 KST 스냅숏)에 올랐다. 지난주 Tuesday 정기 릴리스인 v0.1.5(10/21 02:28 KST)가 나왔고, 0.1.x부터 “안정 릴리스 주기”를 선언한 직후라 실무 도입을 검토하는 팀이 늘어난 시점이다. 이 글은 저장소 코드와 문서, 릴리스 노트를 기준으로 구조와 타협점을 해부하고, 박스(리눅스 x86_64 VM, 8코어, 커널 6.12)에서 v0.1.5 릴리스 바이너리를 SHA-256 검증 후 받아 정책 증명기 13개 케이스 + 규모 테스트를 직접 돌린 결과를 적는다. 박스에는 Docker/Podman이 없어 실제 샌드박스 생성은 하지 않았다(아래에 정확히 무엇을 돌렸고 무엇을 안 돌렸는지 표로 정리).

한눈에 보기: 오늘 기준 스냅숏

항목 값
저장소 github.com/NVIDIA/OpenShell · 문서 docs.nvidia.com/openshell · 생성 2026-02-25 07:52 KST
라이선스 Apache-2.0 (Copyright NVIDIA). THIRD-PARTY-NOTICES 별도, 기본 워크로드 이미지는 nvcr.io/nvidia/base/ubuntu:24.04
최신 안정판 v0.1.5 (2026-10-21 02:28 KST). v0.1.0(9/26) → 0.1.1 → 0.1.2(9/28) → 0.1.3(10/10) → 0.1.4(10/14) → 0.1.5. 매주 화요일(미국) 목표
GitHub 지표 17,863 stars · 1,838 forks · 열린 이슈+PR 565 · 기여자 약 135명(익명 포함, API) · 마지막 커밋 9a85667(10/23 07:20 KST)
언어·규모 Rust 2024 edition, rust-version 1.94, crates/ 38개 · Rust 약 61만 줄(테스트 포함). 가장 큰 크레이트는 server(17.8만)·supervisor-network(9.3만)·cli(5.1만)
배포물 release당 33개 자산: CLI·gateway·supervisor·sandbox·driver-vm·prover 바이너리, deb/rpm/snap, Homebrew formula, Python wheel(openshell 0.1.5 on PyPI), 체크섬 파일
SDK Python(uv add openshell), TypeScript(GitHub Packages), Go, Rust(git tag)
텔레메트리 기본 켜짐(익명 운영 카운트). OPENSHELL_TELEMETRY_ENABLED=false 또는 Helm server.telemetryEnabled=false, 빌드 시 제거 가능

왜 지금 뜨나: “샌드박스”가 아니라 “에이전트 함대 런타임”

에이전트 샌드박스는 이미 많다. Codex CLI와 Claude Code는 OS 샌드박스를 내장했고, E2B·Daytona 같은 호스팅형도 있으며, 이 블로그에서 10월 11일 다룬 microsoft/mxc는 앱에 넣는 크로스 플랫폼 격리 SDK였다. OpenShell이 노리는 층은 그 위다. README 첫 문장이 “safe, private runtime for fleets of autonomous AI agents”다. 즉 한 개발자의 터미널이 아니라 여러 에이전트를 여러 런타임(Docker·Podman·Kubernetes·MicroVM)에서 같은 정책으로 돌리는 컨트롤 플레인이다. 지금 주목받는 이유를 정리하면 세 가지다.

포인트 내용 실무 의미
자격 증명 문제를 정면으로 다룸 에이전트 사고의 상당수는 “토큰이 프로세스 환경에 있었다”에서 시작한다. OpenShell은 샌드박스에 불투명 플레이스홀더만 주고, 슈퍼바이저가 허용된 엔드포인트로 가는 요청에서만 진짜 값으로 치환한다 프롬프트 인젝션으로 키를 출력시켜도 얻는 건 플레이스홀더
정책 변경의 ‘증명’ 에이전트가 막히면 새 네트워크 규칙을 제안(policy advisor)하고, 게이트웨이가 Z3 기반 prover로 위험한 확장(새 호스트에 자격 증명 전송, 새 HTTP 메서드, 메타데이터 주소)을 찾아내면 자동 승인을 막는다 “사람이 YAML diff를 눈으로 본다”를 기계 검사로 보강
안정 주기 선언 0.1.0부터 주간 stable·N-1 지원·Stable 인터페이스 하위 호환 약속(RFC 0014). 0.0.x 실험판에서 운영 후보로 넘어온 시점 도입 검토 문턱이 낮아짐
구조: 게이트웨이(수명 주기·정책·키·JWT·prover) → 슈퍼바이저(판정·DNS·키 치환·중계) ⇄ mTLS HTTP/2 ⇄ 샌드박스(키도 판정도 없음), 외부 펜스가 직접 송신을 막는다
구조: 게이트웨이(수명 주기·정책·키·JWT·prover) → 슈퍼바이저(판정·DNS·키 치환·중계) ⇄ mTLS HTTP/2 ⇄ 샌드박스(키도 판정도 없음), 외부 펜스가 직접 송신을 막는다

아키텍처: 게이트웨이 · 슈퍼바이저 · 샌드박스

핵심은 판정하는 쪽과 실행되는 쪽을 물리적으로 갈라 놓은 것이다. 문서(docs/about/architecture.mdx)의 구성 요소를 책임 기준으로 다시 묶으면 다음과 같다.

구성 요소 신뢰 구역 책임 코드
Gateway 신뢰 샌드박스 수명 주기, 정책·설정 배포, provider(자격 증명) 저장, 사용자 인증, JWT 서명, 정책 prover 실행 openshell-gateway, openshell-server
Compute driver 신뢰 워크로드와 슈퍼바이저를 띄우고 둘 사이 사설 채널·외부 네트워크 펜스를 만든 뒤 ‘경계가 섰다’는 증거를 보고 driver-docker / podman / kubernetes / vm / mxc
Supervisor 신뢰 요청마다 정책 판정, DNS 응답, 자격 증명 치환, 실제 업스트림 연결, 로그(OCSF) 전송 openshell-supervisor*
Sandbox 비신뢰 구역과 동거 에이전트를 자식 프로세스로 소유, 실행 파일 신원 확인(/proc), TCP·DNS를 seccomp로 가로채 슈퍼바이저에 넘김. 판정은 하지 않음 openshell-sandbox(static musl)
Agent 비신뢰 OpenCode, Claude Code, Codex 등 사용자가 이미지에 넣은 무엇이든 기본 이미지엔 에이전트 없음

샌드박스와 슈퍼바이저는 OpenShell Sandbox Protocol, 즉 상호 인증된 HTTP/2 연결 하나 위의 여러 스트림으로 대화한다. TCP 연결마다 별도 스트림과 백프레셔가 있어서 느린 다운로드가 DNS나 exec를 막지 않는다. 전송 계층은 드라이버가 고른다.

런타임 슈퍼바이저 위치 샌드박스와의 채널 직접 송신 차단 방식
Docker 별도 컨테이너 드라이버 소유 볼륨의 인증된 Unix 소켓 워크로드 컨테이너 네트워킹 끔
Podman 별도 컨테이너 드라이버 소유 볼륨의 인증된 Unix 소켓 워크로드 컨테이너 네트워킹 끔
Kubernetes 별도 Pod mTLS 사설 서비스 NetworkPolicy가 슈퍼바이저 서비스만 허용(CNI가 NetworkPolicy를 집행해야 함)
MicroVM 호스트 프로세스 인증된 vsock 게스트에 네트워크 장치 없음(libkrun, KVM/HVF)
설계 포인트. 런타임은 “경계를 세우고 증명”만 하고 허용 여부는 절대 판단하지 않는다. 판정은 항상 공용 슈퍼바이저+정책 엔진이 한다. 그래서 같은 정책 YAML이 Docker에서든 Kubernetes에서든 같은 의미를 갖는다. 반대로 Windows 네이티브용 MXC 드라이버는 슈퍼바이저 없이 드라이버가 자체 보고하는 구조라 seccomp·interactive exec·네트워크 미들웨어가 빠져 있다(드라이버 README의 capability matrix에 ❌로 명시).

네트워크 요청 한 건의 여정

단계 무슨 일이 어디서
1 에이전트가 connect() 또는 DNS 조회 샌드박스 안
2 seccomp user notification이 호출을 멈추고, 샌드박스가 /proc에서 호출한 실행 파일의 실경로·해시를 확인 샌드박스(판정 없음)
3 Sandbox Protocol 스트림으로 슈퍼바이저에 전달 mTLS + sandbox JWT
4 network_policies에서 (바이너리, 호스트, 포트) 매칭 → protocol: rest면 TLS 종단 후 메서드·경로까지 검사 → 허용된 provider면 플레이스홀더를 진짜 자격 증명으로 치환 슈퍼바이저
5 허용 시 슈퍼바이저가 실제 연결을 열고 중계, 거부 시 HTTP 클라이언트는 policy_denied 응답 슈퍼바이저

세부 동작 중 운영자가 알아야 할 것들: 루프백(127/8)·링크 로컬(169.254/16, 클라우드 메타데이터 포함)·0.0.0.0은 정책과 무관하게 항상 차단되고 allowed_ips로도 열 수 없다. 바이너리는 심볼릭 링크가 아닌 커널이 보고하는 실경로로 매칭하므로 Ubuntu 24.04에서 pip 규칙은 /usr/bin/python3.12를 적어야 한다. 처음 연결에 참여한 실행 파일의 해시를 기록해 두고, 그 경로의 파일이 바뀌면 이후 연결을 거부한다. 슈퍼바이저와의 연결이 끊기면 샌드박스는 에이전트를 얼리고, 같은 슈퍼바이저 프로세스만 재접속해 재개할 수 있다.

인증: 토큰은 모두 ‘샌드박스 하나, 실행 한 번’에 묶인다

연결 보호 수단 범위
Supervisor → Gateway gateway JWT (+TLS) 정책 조회·로그 푸시·세션 중계만 가능. 다른 샌드박스 관리 불가
Supervisor → Sandbox mTLS + sandbox JWT 샌드박스는 게이트웨이 공개키만 가짐 → 검증은 가능, 위조는 불가
Agent → Supervisor 직접 연결 없음 샌드박스가 위 채널로 중계
최초 자격 Docker/Podman/VM: 드라이버가 슈퍼바이저 전용 파일로 전달 · K8s: ServiceAccount 토큰을 드라이버가 검증 재시작 = 새 generation, 새 토큰·새 인증서

정책 모델: 다섯 섹션, 두 개의 적용 시점

섹션 통제 대상 집행 주체 적용 시점
filesystem_policy 읽기 / 읽기쓰기 경로 Landlock LSM 시작 시 고정
landlock 규칙 적용 실패 시 그래도 시작할지(best_effort / hard_requirement) 샌드박스 런타임 시작 시 고정
process run_as_user / run_as_group Docker·Podman 생성 시 생성 시 고정
network_policies 바이너리별 목적지, REST·WebSocket·GraphQL·MCP·JSON-RPC 요청 규칙 슈퍼바이저 프록시 실행 중 핫 리로드
network_middlewares 허용된 트래픽의 추가 검사·변환·차단 슈퍼바이저 미들웨어 실행 중 변경 가능

정책이 없으면 쓰는 기본 정책은 꽤 빡빡하다. 작업 디렉터리 읽기쓰기, /bin /usr /lib /proc /etc /var/log /dev/urandom 읽기, /tmp·/dev/null 쓰기, 네트워크 규칙 0개 = 외부 전부 거부. Docker/Podman은 이미지 USER가 root면 정책에서 non-root를 지정하지 않는 한 거부한다. 그리고 /.openshell 사설 디렉터리는 Landlock ABI v3 필수 베이스라인으로 보호되며 best_effort로도 끌 수 없다. 우선순위는 글로벌 정책 → 샌드박스 저장 정책(--policy) → 이미지 내장 정책 → 기본 정책이고, 이미지 정책이 깨져 있으면 기본값으로 떨어지지 않고 시작 자체를 거부한다(fail closed).

# 읽기 전용 GitHub API 규칙을 실행 중인 샌드박스에 추가 (문서 예시)
openshell policy update my-sandbox \
  --rule-name github_readonly \
  --binary /usr/bin/curl \
  --add-endpoint api.github.com:443:read-only:rest:enforce \
  --wait

# 같은 규칙의 YAML 형태
network_policies:
  github_readonly:
    endpoints:
      - host: api.github.com
        port: 443
        protocol: rest
        enforcement: enforce     # 기본값은 audit(로그만)
        access: read-only        # GET / HEAD / OPTIONS
    binaries:
      - path: /usr/bin/curl
함정: enforcement 기본값은 audit. REST 규칙을 쓰고 enforcement를 빼면 위반 요청은 허용되고 로그만 남는다. 문서가 권하는 흐름은 audit로 돌려 openshell logs <sb> --source sandbox로 위반을 본 뒤 enforce로 바꾸는 것. 이때 --level warn으로 거르면 정책 이벤트가 숨겨진다는 점도 문서에 적혀 있다. 그리고 prover는 audit 엔드포인트를 unsupported로 거절한다(아래 실측).

Providers: 키는 게이트웨이에, 샌드박스엔 플레이스홀더

Provider는 “서비스 이름 ↔ 저장된 자격 증명”이고, provider 프로필이 그 자격 증명이 갈 수 있는 호스트·포트·경로·바이너리를 정의한다. 저장소 providers/에는 anthropic, openai, nvidia, openrouter, github, aws, aws-bedrock, google-vertex-ai, oci-genai, pypi, claude-code, codex, copilot, cursor 등 예시 프로필이 있다. 예컨대 github.yaml은 GITHUB_TOKEN/GH_TOKEN을 api.github.com REST·GraphQL 읽기 전용과 github.com clone/fetch에만 보내고 push(git-receive-pack)는 막는 형태다. 파일 머리말이 “그대로 import하지 말고 binaries를 당신 이미지의 경로로 고쳐라”라고 못 박아 둔 점이 인상적이다.

# 로컬 환경변수에서 키를 읽어 provider 생성 → 샌드박스에 붙이기
export ANTHROPIC_API_KEY=sk-ant-...        # 게이트웨이에만 저장됨
openshell provider create --name my-claude --type claude-code --from-existing
openshell sandbox create --name dev --provider my-claude --policy ./policy.yaml

# 프로필 확인 / 커스텀 프로필 검사
openshell profile list
openshell profile describe github
openshell provider profile lint -f providers/github.yaml
규칙 의미
네트워크 규칙 허용 ≠ 자격 증명 허용 어떤 호스트로의 연결을 열어도, provider 프로필이나 명시적 credential binding이 그 호스트를 포함하지 않으면 키는 붙지 않는다. 실패하면 규칙을 넓히지 말고 바인딩을 고치라는 것이 문서의 권고
플레이스홀더가 본문에 섞이면 요청 본문에 무효/폐기된 플레이스홀더가 있으면 credential_placeholder_in_request_body로 거부
유효 정책 = 베이스 + provider 규칙 provider가 네트워크 규칙을 보탠다. 감사·증명할 땐 openshell policy get --full(effective)을 대상으로 해야 함
글로벌 정책 활성 시 샌드박스별 정책 변경·제안 승인 차단, provider 기여 규칙도 억제

Policy advisor와 prover: 사람 승인 앞에 기계 검사

Policy advisor는 기본 꺼짐이다. 켜면(agent_policy_proposals_enabled=true) 샌드박스 안에 /etc/openshell/skills/policy_advisor.md와 SKILL.md, (없으면) /AGENTS.md가 생기고, 샌드박스 안에서만 닿는 http://policy.local API로 에이전트가 거부 내역을 보고 좁은 규칙을 제안한다. 제안은 절대 스스로 적용되지 않는다. 게이트웨이는 제안마다 proposal risk check(prover로 “이 규칙이 있을 때와 없을 때 도달 가능 범위 차이”를 계산)와 휴리스틱(사설 IP, 와일드카드 호스트, 링크 로컬 등)을 돌리고, 발견이 하나라도 있으면 자동 승인을 막는다. advisor를 꺼도 OpenShell은 차단된 연결로부터 “바이너리 1개 → 호스트:포트 1개” 초안을 스스로 만든다.

별도 바이너리 openshell-prover는 boundary check를 한다. 운영자가 쓴 “최대 허용 범위” 정책(boundary) 안에 후보 정책(candidate)이 들어가는지 Z3(SMT)로 검사한다. 게이트웨이 없이 파일만 읽으므로 CI 게이트나 “부모 에이전트가 서브에이전트용 정책을 쓸 때 자기 범위를 넘지 않는지” 확인하는 용도로 설계됐다. 두 검사는 서로 다른 질문이라, 하나를 통과해도 다른 하나는 실패할 수 있다고 문서가 분명히 한다.

result exit 의미
within_boundary 0 검사 가능한 영역에서 후보가 경계를 넘지 않음 — 유일한 통과
exceeds_boundary 1 넘음. counterexample 한 건 제시
error 2 파일 없음·정책 무효 등
unsupported 3 모델링 못 하는 형태(GraphQL·MCP·audit REST·서로 다른 경로 비교 등). 추측하지 않고 거절
inconclusive 3 솔버 시간 초과·리소스 한도(규칙 1,024 / 엔드포인트 4,096 초과)
박스 실측: openshell-prover v0.1.5 경계 검사 13케이스 중 within 3·exceeds 6·unsupported 4. exit 0만 통과로 다뤄야 한다
박스 실측: openshell-prover v0.1.5 경계 검사 13케이스 중 within 3·exceeds 6·unsupported 4. exit 0만 통과로 다뤄야 한다

박스 실측 ①: openshell-prover 13개 케이스

환경: v0.1.5 openshell-prover-x86_64-unknown-linux-musl.tar.gz(14.6MB → 바이너리 32.9MB, static), 체크섬 파일과 SHA-256 일치(79e6c13d…0914). CLI(openshell 26.5MB, ca1ad6e6…af69)도 같은 방식으로 검증. 격리 HOME, OPENSHELL_TELEMETRY_ENABLED=false. 경계 정책은 “/usr /etc /lib 읽기 + /tmp 쓰기, Landlock hard_requirement, 사용자 sandbox, curl → api.github.com:443 REST 읽기 전용(enforce), python3.12 → pypi.org·files.pythonhosted.org:443”으로 잡고, 후보를 한 군데씩 바꿨다.

openshell-prover check --boundary boundary.yaml c03-rest-readwrite.yaml
result: exceeds_boundary
coverage: domains=filesystem,network_l4,network_rest,process,landlock
counterexample: network binary=- ancestor_binary=- binary_identity_required=false
  host=api.github.com:443 destination_ip=::1:0:0 trusted_gateway=false
  protocol=rest method=PUT path=/
후보(변경점) result exit ms 해석
c01 경계의 부분집합 within_boundary 0 34 정상
c02 /home/sandbox 쓰기 추가 unsupported 3 29 경계에 없는 경로 → “경로 해석에 의존” 거절. exceeds가 아님에 주의
c03 REST read-only → read-write exceeds_boundary 1 32 반례: PUT /
c04 호스트를 api.openai.com으로 exceeds_boundary 1 14 반례: GET / to api.openai.com
c05 바이너리에 wget 추가 exceeds_boundary 1 39 반례: ancestor_binary=/usr/bin/wget, 신원 강제 on/off 둘 다 검사
c06 protocol: graphql unsupported 3 3 “only L4 TCP and REST are modeled”
c07 enforcement: audit unsupported 3 3 “REST without enforced inspection”
c08 run_as_user: root exceeds_boundary 1 3 non-root → root
c09 Landlock hard → best_effort exceeds_boundary 1 2 설정 비교(커널 집행 여부는 아님)
c10 /tmp/cache 쓰기(경계는 /tmp) unsupported 3 35 하위 경로도 심볼릭 링크 가능성 때문에 거절
c11 169.254.169.254:80 규칙 within_boundary 0 32 링크 로컬은 런타임이 항상 차단 → 모델상 ‘추가 도달 없음’
c12 pypi.org만 남긴 부분집합 within_boundary 0 54 정상
c13 203.0.113.10:80 (공인 IP) exceeds_boundary 1 – c11과 대조: 반례 protocol=l4
읽는 법. ① 13건 중 4건이 unsupported였다. 그중 2건(c02, c10)은 사람 눈엔 “명백히 넓어짐/좁아짐”인 파일 경로인데 prover는 판단을 거절한다. 그래서 CI에서 exit != 0을 모두 실패로 다뤄야 하고, 문서도 “within만 통과”라고 쓴다. ② c11은 처음엔 버그처럼 보이지만, 메타데이터 주소가 정책과 무관하게 차단되는 런타임 의미론과 일치한다. 증명기의 결과는 “정책 파일의 의미”이지 “내 YAML이 깔끔한가”가 아니다. ③ 반례 destination_ip=::1:0:0 같은 값은 솔버가 고른 임의의 증인이라 그대로 해석하면 안 된다.
박스 실측: REST 규칙 N개 경계 대비 0.8N 후보 증명 시간. 200개 6.8초, 500개는 기본 10초 타임아웃에서 inconclusive, 120초를 주면 20초에 통과
박스 실측: REST 규칙 N개 경계 대비 0.8N 후보 증명 시간. 200개 6.8초, 500개는 기본 10초 타임아웃에서 inconclusive, 120초를 주면 20초에 통과

박스 실측 ②: 규칙 수와 증명 시간

실무 정책은 13줄짜리가 아니다. 그래서 REST 읽기 전용 규칙 N개짜리 경계와, 그중 0.8N개를 쓰는 후보(within) / 거기에 read-write 규칙 1개를 더한 후보(exceeds)를 생성해 시간을 쟀다(--timeout 60s, 8코어 VM, 단일 실행).

경계 / 후보 규칙 수 within 소요 exceeds 소요
10 / 8 0.23초 0.14초
50 / 40 1.32초 0.62초
100 / 80 2.51초 1.38초
200 / 160 6.84초 3.79초
500 / 400 (기본 10s) inconclusive (10.9초) inconclusive (10.5초)
500 / 400 (--timeout 120s) within, 20.0초 –

규칙 수에 대해 대략 선형보다 약간 가파르게 늘고, exceeds가 within보다 빠르다(반례 하나만 찾으면 끝나니까). 기본 타임아웃 10초에서는 수백 규칙대에서 inconclusive가 나온다. 게이트웨이의 proposal risk check는 “현재 정책 + 제안 1건”이라 규모가 다르지만, CI에서 회사 전체 boundary를 검사한다면 --timeout을 명시하고 --output json의 reason_code(solver_timeout, resource_limit 등)로 분기하는 게 맞다. 측정은 합성 정책 한 종류·1회 실행 기준이라 절대값보다는 경향으로 보길 권한다.

# CI 게이트 예시: 샌드박스의 유효 정책이 조직 boundary 안에 있는지
openshell sandbox get my-sandbox --policy-only > candidate.yaml
openshell-prover check candidate.yaml --boundary org-boundary.yaml \
  --output json --timeout 60s > prover.json
jq -e '.result == "within_boundary"' prover.json   # 그 외 결과는 모두 실패 처리
jq -r '.reason_code // .counterexample' prover.json

박스 실측 ③: 돌린 것과 못 돌린 것

항목 결과 비고
릴리스 자산 다운로드·SHA-256 검증 ✅ CLI·prover 둘 다 체크섬 일치
openshell --version / openshell-prover --version ✅ 0.1.5 / 0.1.5
openshell status ✅ “No gateway configured” 안내 후 정상 종료
openshell doctor check ⚠️ Docker 없음 → failed to execute docker info에서 중단
커널 전제 조건 부분 확인 커널 6.12, LSM 목록에 landlock, Landlock ABI 6(요구 ≥3), /dev/kvm 존재. seccomp 중첩 notify·ADDFD 등은 샌드박스가 실행 시 능동 프로브하므로 미검증
prover 13케이스 + 규모 테스트 ✅ 위 표
게이트웨이 + 실제 샌드박스 생성 ❌ 미실행 컨테이너 런타임 부재. MicroVM 경로는 KVM이 있으나 deb/systemd 설치(루트)가 필요해 이번엔 범위 밖
Policy advisor·credential 치환·OCSF 로그 ❌ 미실행 게이트웨이 필요. 본문 설명은 문서·코드 기준
# 박스에서 실제로 쓴 명령 (요약)
B=https://github.com/NVIDIA/OpenShell/releases/download/v0.1.5
curl -sSLO $B/openshell-prover-x86_64-unknown-linux-musl.tar.gz
curl -sSLO $B/openshell-prover-checksums-sha256.txt
sha256sum -c --ignore-missing openshell-prover-checksums-sha256.txt
tar -xzf openshell-prover-x86_64-unknown-linux-musl.tar.gz
export OPENSHELL_TELEMETRY_ENABLED=false HOME=$PWD/home
./openshell-prover check --boundary boundary.yaml c01-within.yaml; echo $?

# 일반적인 설치 (공식 Quickstart) — Docker/Podman/가상화가 있는 호스트
curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | OPENSHELL_VERSION=v0.1.5 sh
openshell sandbox create --name demo

저장소 구조: 어디부터 읽을까

경로 내용
crates/openshell-server, -gateway 컨트롤 플레인, gRPC(tonic)·HTTP(axum), 영속 상태, 인증
crates/openshell-supervisor* 정책 판정, 네트워크 중계(-network 9.3만 줄로 두 번째로 큼), 미들웨어, 프로세스 제어
crates/openshell-sandbox, -sandbox-backend, -isolation-interface 워크로드 측 런타임, Landlock/seccomp, 격리 백엔드 계약
crates/openshell-driver-{docker,podman,kubernetes,vm,mxc} 런타임별 경계 구성. vm은 libkrun, mxc는 Windows AppContainer
crates/openshell-policy, -policy-schema, -prover, -prover-cli 정책 파싱·검증·SMT 모델(Z3 0.21 크레이트)
crates/openshell-providers, driver-vault, driver-db-credstore, driver-kubernetes-secrets 자격 증명 저장소 백엔드
crates/openshell-ocsf, -otel OCSF 스키마 로그, OpenTelemetry 내보내기
providers/, examples/, skills/ 프로필 예시 17종, 예제 20여 개(policy-advisor, local-inference, governance-interceptor 등), 에이전트용 스킬 4종
rfc/ 0001 core architecture ~ 0017 service exposure. 0014 release stability가 지원 정책의 근거

흥미로운 점은 저장소 자체가 “agent-first”로 개발된다는 것이다. AGENTS.md, CLAUDE.md, .agents/skills/에 triage·release·security-fix용 스킬 20여 개가 있고, npx skills add NVIDIA/OpenShell로 사용자 에이전트에게 CLI 사용법·정책 작성·게이트웨이 디버깅 스킬을 설치할 수 있다. 에이전트에게 정책 YAML 초안을 쓰게 하고 prover로 검사하는 흐름이 의도된 사용 방식이다.

대안과 비교

도구 층위 격리 수단 네트워크 통제 단위 자격 증명 정책 검증 라이선스
NVIDIA OpenShell 에이전트 함대 런타임(컨트롤 플레인+슈퍼바이저) Docker·Podman·K8s·MicroVM(+Windows MXC) 바이너리별 L4 + REST/WS/GraphQL/MCP L7, 핫 리로드 플레이스홀더 치환(키가 샌드박스에 없음) SMT prover, advisor Apache-2.0
microsoft/mxc (10/11 글) 앱 내장 격리 SDK bubblewrap / AppContainer / Seatbelt JSON 정책의 네트워크 허용 목록 범위 밖(호출 앱 몫) 없음 MIT
Codex CLI 내장 샌드박스 단일 에이전트 로컬 실행 Seatbelt(macOS), Landlock/seccomp(Linux) 대체로 on/off 수준 + 승인 모드 환경변수 그대로 없음 Apache-2.0
anthropic sandbox-runtime 로컬 프로세스 래퍼 bubblewrap / sandbox-exec 프록시 기반 도메인 허용 목록 범위 밖 없음 Apache-2.0
E2B 호스팅형 코드 실행 샌드박스 Firecracker microVM 샌드박스 단위 인터넷 on/off·허용 목록 사용자 코드가 주입 없음 Apache-2.0(SDK·인프라)
gVisor / Kata 범용 컨테이너 격리 계층 유저스페이스 커널 / 경량 VM 에이전트 의미론 없음(CNI·방화벽 몫) 없음 없음 Apache-2.0
상황 권장 이유
개발자 노트북에서 Claude Code·Codex 하나를 안전하게 하네스 내장 샌드박스 또는 sandbox-runtime OpenShell은 게이트웨이까지 띄워야 해서 과함
우리 앱이 LLM이 만든 코드를 실행 MXC 같은 내장 SDK, 또는 E2B 프로세스 단위 격리가 목적
팀/조직이 여러 에이전트를 K8s에서 같은 정책으로 운영 OpenShell 정책·자격 증명·감사 로그·승인 흐름이 한 컨트롤 플레인
API 키 유출을 구조적으로 막아야 함 OpenShell 플레이스홀더 치환이 가장 큰 차별점
정책 변경을 CI에서 기계 검증 openshell-prover 단독 게이트웨이 없이 바이너리 하나로 가능

타협점과 주의 사항

주제 내용 대응
운영 복잡도 게이트웨이·슈퍼바이저·드라이버·PKI·JWT TTL까지 관리 대상. 공유 배포는 gateway_jwt.ttl_secs 설정 권장(로컬 단일 사용자는 무기한 토큰) 로컬은 install.sh, 공유는 Helm으로 시작
커널 요구 사항 Landlock ABI≥3(리눅스 6.2+), seccomp 중첩 user-notify·ADDFD_SEND, process_vm_readv 또는 /proc/<pid>/mem, SO_BINDTODEVICE. 버전 숫자가 아니라 능동 프로브로 판정하고 실패 시 fail closed 배포판 백포트 커널·하드닝된 런타임 프로필에서 먼저 시험
getpeername() 의미 변화 중계된 아웃바운드 TCP에서 피어 주소가 업스트림이 아니라 로컬 릴레이(127.0.0.1/::1)로 보임 피어 IP로 로직을 짜는 도구는 깨질 수 있음
L7 검사 = TLS 종단 inspected 엔드포인트는 슈퍼바이저가 TLS를 종단(MITM). 샌드박스 CA를 신뢰시켜야 하고, 인증서 핀닝 클라이언트는 tls: skip 등 별도 처리 핀닝 SDK 목록을 미리 점검
prover 커버리지 GraphQL·MCP·WebSocket·JSON-RPC·쿼리 매칭·audit REST는 unsupported. 파일 경로는 동일 경로끼리만 비교 unsupported를 실패로 처리, 경로는 경계와 같은 표기로
K8s 의존 CNI가 NetworkPolicy를 집행하지 않으면 외부 펜스가 없다 Calico·Cilium 등 확인
버전 업 시 깨지는 변경 0.1.5만 해도 OCSF 로그 확장자 .log→.jsonl, K8s 템플릿 라벨·어노테이션 허용 목록, Go SDK 시그니처, 드라이버 outer_fence→provisioning_evidence, snap 사용자 서비스 변경 릴리스 노트의 Upgrade notes를 매주 확인
텔레메트리·외부 자료 익명 텔레메트리 기본 켜짐. README에 ‘외부 자료를 자동으로 가져온다’는 면책 조항 OPENSHELL_TELEMETRY_ENABLED=false, 이미지·프로필 출처 검토
Windows WSL 2 + Docker Desktop은 Experimental, 네이티브 MXC 드라이버는 seccomp·interactive exec 없음 운영은 Linux 호스트 권장

도입 체크리스트

순서 할 일 목적
1 openshell-prover만 먼저 CI에 넣어 조직 boundary YAML을 만든다 게이트웨이 없이 효과 확인
2 로컬에서 install.sh → openshell sandbox create → Run Your First Agent(OpenCode + OpenRouter 무료 모델) 튜토리얼 기본 정책(네트워크 0)에서 무엇이 막히는지 체감
3 provider 프로필을 복사해 binaries를 실경로로 고치고 import pip→python3.12 같은 실경로 함정 회피
4 새 REST 규칙은 audit로 돌려 위반 로그 확인 후 enforce --level warn 필터 금지
5 advisor는 수동 승인으로 시작, 자동 승인은 risk check 결과가 쌓인 뒤 자동 승인은 opt-in
6 공유 배포는 Helm + NetworkPolicy 집행 CNI + JWT TTL + 텔레메트리 정책 결정 N-1 지원이므로 최소 격주 업그레이드 계획

정리

OpenShell의 가치는 “또 하나의 샌드박스”가 아니라 판정과 실행의 분리, 자격 증명을 샌드박스 밖에 두는 치환 모델, 정책 변경을 SMT로 검사하는 습관 세 가지에 있다. 박스에서 돌려 본 prover는 빠르고(수십 ms), 판단할 수 없는 것은 솔직하게 unsupported로 돌려주며, 수백 규칙대에선 타임아웃을 명시해야 했다. 반면 진짜 이점인 런타임 집행은 Docker/Podman/K8s/KVM 위의 게이트웨이까지 올려야 체감할 수 있고, 커널·CNI·TLS 종단이라는 운영 비용이 따라온다. 에이전트 한두 개를 노트북에서 돌리는 팀에겐 과하고, 여러 팀의 에이전트를 같은 정책·같은 감사 로그로 묶어야 하는 조직에겐 지금 오픈소스로 볼 수 있는 가장 완결된 설계 중 하나다. 시작은 prover 하나를 CI에 넣는 것부터가 가장 싸다.