OpenCode는 강력합니다. 바로 그 점이 문제입니다.
요약
오픈 소스 코딩 에이전트인 OpenCode의 강력한 기능과 그에 따른 보안 위험성을 다룹니다. 에이전트가 쉘 명령을 직접 실행할 때 발생할 수 있는 예기치 못한 오류를 방지하기 위해 Tensorlake의 일회용 샌드박스를 활용하는 안전한 실행 방법을 제안합니다.
핵심 포인트
- OpenCode는 Claude Code의 무료 오픈 소스 대안으로 쉘 액세스 권한을 가짐
- 에이전트의 무분별한 쉘 명령 실행은 시스템 파일이나 의존성 파괴 위험이 있음
- 보안을 위해 로컬 환경 대신 Tensorlake와 같은 클라우드 샌드박스 사용 권장
- 에이전트의 지능 문제보다 명령 실행의 통제 불가능성이 더 큰 위험 요소임
OpenCode는 Claude Code에 대응하는 무료 오픈 소스 대안으로, 동일하게 전체 쉘 (shell) 액세스 권한을 제공합니다. 제가 제 노트북 대신 샌드박스 (sandbox)를 사용하여 이를 안전하게 실행한 방법을 소개합니다.
OpenCode는 Claude Code와 유사한 워크플로를 가진 오픈 소스 코딩 에이전트 (coding agent)입니다.
원하는 내용을 입력하면, 에이전트가 코드베이스 (codebase)를 읽고, 파일을 수정하며, 스스로 쉘 명령 (shell commands)을 실행합니다. Claude Code와 기본적인 개념은 동일합니다. 다만 무료이며, 오픈 소스이고, 특정 모델에 종속되지 않는다는 장점이 있습니다.
이는 훌륭한 일이지만, "스스로 쉘 명령을 실행한다"는 말이 실제로 무엇을 의미하는지 직면하게 되면 이야기가 달라집니다.
터미널 에이전트에게 그러한 권한을 처음 부여할 때, 떨쳐낼 수 없는 질문이 하나 있습니다: 에이전트가 예상치 못한 무언가를 실행하는 순간 어떤 일이 벌어질까요?
대부분의 사람들은 별로 중요하지 않은 폴더를 선택하고 운이 좋기를 바라거나, 무언가 고장 날 때까지 아예 생각조차 하지 않습니다.
저는 두 가지 옵션 모두 마음에 들지 않았습니다. 그래서 OpenCode를 중요한 데이터 근처에 두기 전에 제3의 방법을 찾아 나섰습니다.
그 결과, 에이전트의 쉘을 Tensorlake의 일회용 샌드박스 (sandbox)로 옮기게 되었습니다. Tensorlake는 이를 클라우드 서비스 (cloud service)로 실행하며, 그 외의 모든 것은 제 노트북에 그대로 두었습니다.
Your Laptop
│
▼
...
이것이 어떤 모습이었는지, 그리고 실제로 실행했을 때 저를 놀라게 했던 점은 무엇인지 설명하겠습니다.
입력을 시작하기 전 내가 계속 상상했던 것
코딩 에이전트의 실제 위험은 지능과는 아무런 관련이 없습니다. 대부분의 경우 에이전트는 일을 정확하게 처리합니다. 문제는 에이전트가 '멈추지 않는다'는 점입니다. 에이전트는 자신이 실행하려는 명령이 당신이 실제로 승인하려 했던 명령인지 다시 한번 확인하기 위해 멈추지 않습니다.
무엇인가를 입력하기 전, 한 가지 시나리오가 계속 저를 괴롭혔습니다. 제가 빌드 아티팩트 (build artifacts)를 정리해달라고 요청했는데, 에이전트가 rm -rf ./build를 실행하는 상황입니다. 그런데 알고 보니 ./build가 여전히 필요한 다른 곳으로 연결된 심볼릭 링크 (symlink)였던 것입니다. 이는 에이전트가 대충 훑고 지나가기 쉬운 종류의 것이며, 솔직히 저 또한 빠르게 작업할 때 항상 잡아내지 못하는 종류의 것이기도 합니다.
또는 의존성(dependency)을 설치하라고 요청할 수도 있습니다. npm install이 실행되면 postinstall 스크립트가 작동합니다. 그 스크립트는 제가 건드리는 것에 동의한 적도 없는 설정 파일을 다시 작성해 버립니다. 저는 사람이 바로 옆에서 지켜보고 있는 상황에서도 postinstall 스크립트가 그보다 더 기괴한 일을 저지르는 것을 본 적이 있습니다.
솔직히 말해서, 둘 중 어느 것도 버그는 아닙니다.
명령어와 사용자의 기기 사이에 아무런 장벽이 없을 때 쉘 액세스(shell access)가 작동하는 방식이 바로 그러할 뿐입니다.
그러다 저는 제 노트북보다 더 큰 범위를 생각하게 되었습니다. 동일한 에이전트이지만, 이제는 10명의 다른 엔지니어가 활발하게 커밋(commit)을 푸시하고 있는 프로덕션 모노레포(production monorepo)를 리팩터링하고 있는 상황입니다. 동일한 심볼릭 링크(symlink) 오독. 동일한 통제 불능의 postinstall 스크립트. 다만 이제는 제 오후 시간만이 위태로운 것이 아닙니다.
bash, write, edit. 이것들은 에이전트 전용 기능이 아닙니다. 이것들은 다음에 무엇을 실행할지에 대해 스스로 판단을 내리는 프로세스에 부여된 쉘(shell) 및 파일 시스템(filesystem) 액세스 권한입니다.
본능적으로는 더 많은 주의를 기울여 이를 해결하려 할 것입니다. 모든 diff를 검토하고, 모든 명령어를 승인하는 것이죠. 하지만 이는 작동하지 않는 순간이 오기 마련입니다. 왜냐하면 자율 에이전트(autonomous agent)의 핵심 목적은 결국 사용자가 모든 단계를 일일이 검토하는 것을 멈추는 데 있기 때문입니다.
진정한 해결책은 폭발 반경(blast radius)을 다르게 설정하는 것이었습니다.
에이전트의 명령어를 제 기기가 아닌 일회용 환경에 배치하면, 이제 잘못된 명령어가 제 시스템을 해치지 않을 것입니다.
분명히 말씀드리자면, 로컬에서 실행하는 것이 틀린 것은 아닙니다. 일회성 프로젝트나 이미 신뢰하고 있는 워크플로우의 경우에는 정확히 당신이 원하는 방식일 것입니다. 계산의 결과가 달라지는 지점은, 자율 쉘 액세스의 대상을 실수했을 때 실제로 비용이 발생하는 곳으로 향하게 할 때입니다.
뇌는 로컬에, 손은 샌드박스에
표 하나가 이 모든 변화를 포괄합니다. 그 이면의 작동 원리를 설명하는 데는 시간이 조금 더 걸립니다.
| Local OpenCode | OpenCode + Tensorlake | |
|---|---|---|
| 명령 실행 위치 | 사용자의 노트북 | 원격, 샌드박스(sandbox) 내 |
| ... |
이것이 결과입니다. 작동 원리는 들리는 것보다 더 간단합니다.
Tensorlake은 tensorlake-opencode라는 플러그인을 배포합니다. 이 플러그인은 OpenCode를 대체하는 것이 아닙니다. 특정 도구 호출 (tool calls)을 가로채서 경로를 재지정 (reroute)할 뿐입니다. 이 차이점을 실제로 이해하고 나니, 개념이 예상보다 더 단순하다는 것을 알게 되었습니다.
당신의 노트북에는 OpenCode가 있고, 도구들은 샌드박스 (sandbox) 안에 있는 것, 이것이 실제 구조입니다. OpenCode 하네스 (harness) 자체, 인터페이스, 그리고 당신의 세션까지, 이 모든 것은 이전과 똑같이 당신의 기기에서 계속 실행됩니다.
모델 호출 (model call)은 여전히 당신이 설정한 제공자 (provider), 즉 Anthropic, OpenAI 등 누구든 기존과 동일하게 외부로 전송됩니다. 그 부분은 애초에 로컬 (local)이 아니었으며, 이 플러그인도 그 점을 바꾸지 않습니다.
바뀐 점은 각각의 개별 도구 호출이 도달하는 위치입니다:
당신의 노트북
│
▼
...
모델 호출은 여전히 당신이 설정한 제공자에게 전달됩니다. 오직 도구 호출, 즉 '손'만이 샌드박스로 이동합니다.
webfetch와 websearch는 파일 시스템 (filesystem)을 건드리지 않기 때문에 로컬에 머무는 두 가지 예외입니다. bash, write, edit, read, ls, glob, 그리고 grep이 경로가 재지정되는 도구들입니다.
가로채진 모든 명령은 이제 내 기기에서 즉시 실행되는 대신 네트워크 왕복 (network round trip)을 거칩니다. Tensorlake의 문서에 따르면 샌드박스는 몇 초 안에 시작되며, 기반이 되는 VM 이미지 자체는 수백 밀리초 (milliseconds) 내에 부팅됩니다. 그들의 GitHub 페이지와 제품 사이트에서는 중단된 상태에서의 재개 (resume) 또한 1초 미만으로 걸린다고 각각 주장합니다. 플러그인이 uname -a 호출 시 자동으로 생성한 저의 첫 번째 샌드박스는 플러그인이 기록한 타임스탬프 기준으로 엔드 투 엔드 (end to end) 2.3초가 소요되었는데, 이는 문서에서 설명하는 범위 내에 충분히 들어옵니다. 이는 단순히 VM이 시작되는 시간뿐만 아니라, 순수 VM 부팅 위에 플러그인 자체의 프로비저닝 (provisioning) 및 연결 오버헤드 (overhead)가 더해진 결과일 가능성이 높습니다. 어느 쪽이든, 마냥 앉아서 기다려야 할 정도의 시간은 아닙니다.
설정 방법 (Setting It Up)
전체 설정은 단 하나의 설정 항목(config entry)과 하나의 환경 변수(environment variable)로 끝났으며, 다음 순서대로 입력했습니다.
먼저, ~/.config/opencode/opencode.json에 추가할 플러그인입니다:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["tensorlake-opencode"]
...
OpenCode가 이를 자동으로 설치하므로 별도의 npm install은 필요하지 않습니다. 그다음, OpenCode를 실행하려는 동일한 셸(shell)에서 API 키를 내보내기(export) 합니다:
export TENSORLAKE_API_KEY=your_api_key_here
opencode
아무 일도 일어나지 않았습니다. 실행 시 샌드박스(sandbox)가 구동되지 않아 잠시 당황했습니다. 처음에는 설정을 어딘가 잘못했나 싶어, 방금 설정한 내용을 다시 읽어보기 전에 JSON 파일에 오타가 있는지 확인하러 돌아갈 뻔했습니다. 하지만 제가 설정한 것은 '지연 생성(lazy creation)' 방식이었지, '즉시 생성(eager)' 방식이 아니었습니다. ~/.local/share/opencode/log/tensorlake.log를 tail 명령어로 확인해 보니, 실제로 에이전트에게 샌드박스가 필요한 작업을 요청하기 전까지는 플러그인이 로드되었다는 내용 외에는 아무것도 나타나지 않았습니다.
첫 번째 실제 테스트 (The First Real Test)
이 플러그인의 샌드박스 생성은 지연(lazy) 방식입니다. 세션을 시작하는 순간이 아니라, 파일 시스템(filesystem)이나 셸(shell)이 실제로 필요한 첫 번째 도구 호출(tool call)이 발생할 때까지 기다립니다.
아직 중요한 것을 대상으로 테스트하고 싶지 않았기에, 제가 실제로 내린 첫 번째 명령은 가장 위험 부담이 적은 것이었습니다:
Run: uname -a
이 단 한 번의 bash 호출이 모든 것을 트리거했습니다. 터미널에 "Sandbox created"라는 토스트(toast) 메시지가 나타났고, 저는 내내 다른 창에서 로그를 tail로 지켜보고 있었습니다. 로그에는 문서에 설명된 것과 거의 정확히 일치하는 내용이 찍혔습니다. 세션을 위해 새로운 샌드박스가 생성되고 있다는 한 줄의 메시지, 그리고 2.3초 만에 활성화되었음을 확인하는 두 번째 줄이 나타났습니다.
출력 결과는 Linux였습니다. 제 노트북은 macOS를 사용합니다. 솔직히 말해서, 그 단어 하나가 그 어떤 아키텍처 다이어그램보다 저를 더 빠르게 납득시켰습니다.
다음으로 파일 시스템 측면도 증명해 보라고 요청했습니다:
Write "Hello Tensorlake" to /tmp/workspace/test.txt, then read it back.
쓰기와 읽기 모두 완전히 샌드박스 (sandbox) 내부에서 발생했으며, 이는 파일 시스템 (filesystem) 작업이 내 컴퓨터로부터 격리되어 있음을 확인시켜 주었습니다.
/tmp/workspace는 샌드박스 내부의 에이전트 (agent) 작업 디렉토리였으며, 내 디스크 어디에도 있는 폴더가 아니었습니다. 두 테스트 중 어느 것이 잘못되었더라도 나에게 어떠한 비용도 발생하지 않았을 것입니다.
샌드박스가 작동하는 것을 확인한 후, 나는 단순한 스모크 테스트 (smoke tests)를 넘어 실제 리포지토리 (repository)에서 전체 개발 워크플로우 (developer workflow)를 검증하고 싶었습니다.
https://github.com/benjaminp/six.git를 클론 (Clone)하고, ensure_str 함수를 찾아,
해당 함수가 어디서 왔는지 이름을 명시하도록 TypeError 메시지를 개선한 뒤,
test suite를 실행하여 무엇인가 깨졌는지 알려줘.
이것은 의도적으로 아주 작은 변경이었습니다. 목표는 기능을 기여하는 것이 아니라, OpenCode가 일반적인 개발 중에 기대하는 것과 동일한 편집-테스트 루프 (edit–test loop)를 완료할 수 있는지 확인하는 것이었습니다.
six는 작고 잘 알려진 Python 호환성 라이브러리 (compatibility library)입니다. 하나의 소스 파일, 간단한 테스트 스위트 (test suite), 그리고 현실적인 편집-테스트 워크플로우를 연습하기에 충분한 구조를 갖추고 있습니다.
OpenCode는 리포지토리를 클론하고, ensure_str을 찾아, 함수를 수정하고, 테스트 스위트를 완전히 샌드박스 내부에서 실행했습니다.
기존 구현은 TypeError("not expecting type '%s'" % type(s))를 발생시켰습니다.
수정된 내용은 TypeError("ensure_str: not expecting type '%s'" % type(s))로 변경되었습니다. 이는 기존 테스트 스위트로 검증하기 쉬운 의도적으로 작은 수정이었습니다. 테스트는 정확한 메시지 텍스트를 확인하기보다 TypeError가 발생하는지만 확인하기 때문에, 이는 안전하고 최소한의 수정이 되었으며, 실행 결과가 이를 확인해 주었습니다: 184개 통과, 16개 건너뜀, 0개 실패.
변경 전과 동일합니다.
그 부분이 나에게 중요했습니다. 특정 리포지토리나 특정 한 줄의 수정 사항이 아니라, 클론, 실제 편집, 그리고 전체 테스트 실행이 모두 실제 프로젝트 상의 샌드박스 내부에서 이루어졌다는 점입니다. 그 과정 중 무엇인가 잘못되더라도 내 컴퓨터에 어떤 일이 벌어질지 단 한 번도 걱정할 필요가 없었습니다.
작동하기 시작했을 때 깨달은 세 가지
나는 격리(Isolation)를 예상했습니다. 나머지 두 가지는 나를 놀라게 했습니다.
격리 (Isolation). 잘못된 명령어가 실행되더라도 실제로 영향을 미칠 곳이 없습니다. 통제 불능의 설치(runaway install), 잘못된 경로를 대상으로 한 rm 명령어, 혹은 절반만 설치되어 시스템을 망가뜨리는 의존성(dependency) 문제까지. 이 모든 일은 나의 실제 작업 트리(working tree)가 아닌, 내가 버릴 수 있는 샌드박스(sandbox) 안에서 일어납니다.
**재현성 (Reproducibility)**은 팀의 다른 누군가가 이와 동일한 설정을 실행하는 모습을 상상하게 되자 훨씬 더 중요해졌습니다. 에이전트(agent)가 더 이상 내 노트북에서 실행되지 않기 때문에, 내 노트북에 실제로 무엇이 설치되어 있는지는 더 이상 중요하지 않게 됩니다. 적절한 툴체인(toolchain)이 포함된 이미지를 하나 등록하고, 향후 모든 세션이 해당 이미지를 가리키도록 설정하면, "내 컴퓨터에서는 잘 되는데"라는 식의 대화는 더 이상 발생하지 않습니다.
그리고 내가 계획하지 않았던 **지속성 (persistence)**이 있습니다. 이름이 지정된 샌드박스는 OpenCode가 재시작된다고 해서 사라지지 않습니다. 그저 그 자리에 주차된 상태로 남아 있을 뿐입니다. 나중에 다시 돌아오면 동일한 작업 디렉토리(working directory), 동일한 설치된 패키지들, 동일한 따뜻한 캐시(warm caches)가 모두 내가 떠났던 그 자리에 그대로 있습니다. 아무것도 다시 빌드할 필요도, 다시 설치할 필요도 없습니다.
일회용 CI 컨테이너는 실행이 끝나는 즉시 사라집니다. 하지만 이것은 다음 날에도 내가 남겨둔 그대로 그 자리에 있었습니다.
그 사실을 실제로 체감하기까지는 설치된 상태가 유실되는 한 시간의 경험이 필요했습니다.
실제 작업을 위한 샌드박스 설정하기
테스트 파일 이상의 것을 믿고 맡길 수 있게 되었을 때, 나는 다시 돌아가서 실제로 적절한 크기를 할당했습니다. 플러그인은 첫 번째 샌드박스가 생성되는 시점에 환경 변수(environment variables)의 작은 집합을 한 번 읽으므로, OpenCode를 실행한 후가 아니라 실행하기 전에 설정해야 합니다:
export TENSORLAKE_CPUS=4
export TENSORLAKE_MEMORY_MB=8192
export TENSORLAKE_DISK_MB=20480
...
| 변수 (Variable) | 기본값 (Default) | 제어 항목 (Controls) |
|---|---|---|
TENSORLAKE_CPUS | 2 | vCPUs |
| ... |
프로젝트 범위 키(project-scoped key) 대신 개인 액세스 토큰(Personal Access Token)을 사용하는 경우, TENSORLAKE_ORGANIZATION_ID와 TENSORLAKE_PROJECT_ID도 필요합니다.
TENSORLAKE_IMAGE가 실제로 사용할 가치가 있는 것입니다. 언어 런타임 (language runtime), 시스템 패키지 (system packages), 그리고 프로젝트 의존성 (project dependencies)을 등록된 이미지에 한 번 구워두세요:
tl sbx image create Dockerfile --registered-name my-custom-image
export TENSORLAKE_IMAGE=my-custom-image
그 이후의 모든 세션은 이미 준비된 (warm) 상태로 시작됩니다. 저는 새로운 세션을 열 때마다 에이전트가 제 스택을 처음부터 다시 설치하는 것을 지켜보는 일을 그만두었습니다.
참고: export는 현재 셸 (shell)에서만 유지되므로, 이 설정을 정기적으로 사용할 것이라는 점을 알게 된 후에는 이를 제 셸 프로필 (shell profile)에 추가했습니다.
내가 실제로 가졌던 운영상의 질문들
이것을 실제 작업에 신뢰하고 맡기기 전에, 짧은 데모에서는 나오지 않는 몇 가지 사항에 대한 답을 원했습니다. 여기서 한 가지 솔직하게 말씀드릴 점은, 다음에 이어지는 내용의 대부분은 Tensorlake SDK 및 플랫폼 수준에서 문서화되어 있다는 것입니다. 저는 OpenCode 플러그인이 구체적으로 이러한 각 항목을 동일한 방식으로 노출하거나 관리하는지는 확인하지 못했으며, 다만 그것이 생성하는 기반 샌드박스 (sandboxes)가 이를 지원한다는 점만 확인했습니다. 그 차이가 중요한 부분에 대해서는 아래에 별도로 명시했습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기