- GitHub 스타 16.1만 개를 받은 오픈소스 AI 코딩 에이전트 OpenCode를 로컬 Qwen3.6-27B로 시험한 결과, 도구 품질과 보안 설계 모두 사용을 중단해야 할 수준이었음
- AGENTS.md 재로딩, 고정 거리 컨텍스트 가지치기, 현재 날짜 삽입, 모드 전환이 프롬프트 캐시를 반복해서 무효화해 M4 Max에서도 응답 생성까지 최대 10분이 걸릴 수 있음
- 세션 압축, 시스템 프롬프트, 권한 확인, 하위 에이전트 제어, TUI가 제대로 맞물리지 않아 컨텍스트와 메시지가 유실되고, 중요한 명세를 잊은 채 코드를 작성할 수 있음
- Bash AST와 문자열 패턴에 의존하는 권한 필터는 간접 실행, 절대 경로, 변수, Python, 리디렉션 등을 막지 못하며 외부 파일 접근 제한과 영구 권한도 쉽게 우회됨
- 원격 모델 기본 연결, 무제한 인터넷 접근, 과거 HTTP 서버 RCE 취약점을 고려하면 Docker만으로는 부족하며, 실행 파일 차단·읽기 전용 경로·운영체제 수준 격리가 필요함
평가 범위와 전제
- OpenCode는 제작사가 AI 코딩 에이전트로 소개하는 프로젝트이며, 검토 당시 GitHub 스타는 16.1만 개였음
- 로컬 LLM인 Qwen3.6-27B와 OpenCode Git 버전
baef5cd4를 기준으로 시험함 - 별도의 보안 공개라기보다 LLM 출력을 Bash로 전달하는 구조에서 파이프 계층이 어떻게 실패하는지 살펴봄
- LLM 사용 자체와 사용자의 머신이 손쉽게 침해되거나 삭제될 수 있는지는 별개의 문제로 구분함
프롬프트 캐시를 반복해서 깨뜨리는 구조
- OpenAI
/v1/chat/completions계열 API는 지금까지의 전체 대화를 JSON으로 보내고, JSON 메타데이터가 붙은 SSE 델타 스트림으로 응답함- 세션이 길어질수록 업로드 비용은 제곱으로 증가함
- 도구 호출은 여러 JSON 델타를 다시 JSON으로 조립하는 이중 인코딩을 사용함
- 서버는 상태 비저장 구조지만 성능을 위해 평가 결과를 캐시함
- 요청과 일치하는 가장 긴 캐시 접두사를 찾음
- 접두사 끝부터 마지막 메시지까지 프리필(prefill)함
- 종료 토큰까지 새 토큰을 생성함
- 약 0.5TB/s 메모리 대역폭의 M4 Max에서 Qwen3.6-27B의 토큰 생성은 사용할 만했지만, 긴 컨텍스트의 프리필은 계산량이 매우 컸음
- 적절한 접두사 캐시를 찾지 못하면 GPU를 최대로 사용하면서 약 10분을 기다린 뒤에야 응답 생성이 시작될 수 있음
- OpenCode는 매 SSE 턴마다 파일시스템을 glob하고, 첫 시스템 프롬프트에 삽입하는
AGENTS.md를 다시 읽음- 다음 세션을 위해
AGENTS.md를 수정해도 현재 세션 전체가 재평가됨
- 다음 세션을 위해
- 에이전트에서 사용자로 전환할 때 도구 호출 컨텍스트를 가지치기해 큰 캐시 구간을 무효화함
PRUNE_PROTECT = 40_000보다 오래된 도구 결과를 버리므로 최선의 경우에도 4만 토큰 캐시 미스가 발생함- 중단도 사용자 전환으로 처리되므로 잘못된 진행을 바로잡으면 캐시가 폐기되고 다시 기다려야 함
- 첫 시스템 프롬프트에 현재 날짜를 넣고 매 SSE 턴 재평가하므로 자정을 넘기면 전체 캐시 미스가 발생함
가지치기와 세션 압축
- 가지치기는
skill을 제외한 모든 도구 결과에 동일하게 적용되며, 초기에 읽은 중요한 자료를 별도로 보호하지 않음- 새 세션에서 명세를 먼저 읽음
- 관련 코드를 추가로 읽으면서 4만 토큰 기준을 넘음
- 모델이 불필요한 추론이나 잘못된 방향으로 빠져 사용자가 중단함
- 중단으로 명세가 컨텍스트에서 삭제됨
- 모델은 원래 명세를 참조하지 못한 채 구현함
- 세션 압축(compaction) 은 기존 세션 앞에 새 프롬프트를 붙이고 전체를 다시 프리필한 뒤 몇 개의 불릿으로 요약함
- 요약 프롬프트를 세션 끝에 넣으면 전체 프리필을 피할 수 있음
- 모델이 직접 인계 메모를 파일로 작성하게 하는 방식이 더 잘 작동했으며, 결과를 편집하거나 여러 세션에서 재사용할 수도 있었음
- 압축은 유한한 컨텍스트 창을 무한한 것처럼 보이게 만드는 누수 있는 추상화이며, 가지치기와 함께 쓰면 문제가 커짐
- 컨텍스트 창과 프롬프트 캐시를 기본 제약으로 인정하고 관리 수단을 제공하는 편이 나으며, Pi의 세션 트리는 프롬프트 캐시를 의도적으로 활용함
시스템 프롬프트와 Plan 모드
- 기본 시스템 프롬프트는 매우 길고, 상당한 분량을 모델에게 간결하게 답하라고 지시하는 데 사용함
- 하위 에이전트에게
ABSOLUTELY NO COMMENTS라고 지시하게 만드는 등 강한 코딩 취향도 포함됨 - Plan에서 Build로 넘기는 과정이 매끄럽지 않아 계획을 충분히 구체화하면 컨텍스트 창 끝에 가까워질 수 있음
- 논의 결과를 파일로 작성하고 편집한 뒤 새 세션으로 넘기는 방식을 선호함
- Plan 모드 알림은 어떤 디렉터리에도 쓸 수 없다고 하지만 실제로는
.opencode/plans에 쓸 수 있음- 지시하지 않았는데 해당 디렉터리에 쓰거나, 명시적으로 요청해도 쓰기를 거부하는 양쪽 실패를 모두 경험함
- 기본 시스템 프롬프트를 전역 수정할 수 없어 프로젝트마다 복사해야 함
- Build 모드 프롬프트만 재정의하면 Plan 모드로 전환할 때 전체 프롬프트 캐시 미스가 발생함
- 모델별 프롬프트는 내용과 품질 차이가 큼
- GPT-4, o1, o3용 Beast Mode는 서드파티 패키지와 의존성을 이해하려면 Google 검증이 반드시 필요하다고 지시함
권한 확인이 만드는 판단 피로
- 프로젝트 밖 파일 접근을 임시 문자열 분석으로 감지하면
Yes,No,Always권한 창을 띄우고 응답 전까지 실행을 중단함- 앞으로 같은 작업을 계속 거부하는
Never선택지는 없음
- 앞으로 같은 작업을 계속 거부하는
- 하위 에이전트가
/tmp의 스크립트 출력을 읽으려 할 때No를 선택하면 에이전트가 종료되고 작업 컨텍스트도 사라짐- 진행 내용을 보존하려면 원하지 않는 외부 접근에도
Yes를 선택해야 하는 상황이 생김
- 진행 내용을 보존하려면 원하지 않는 외부 접근에도
- 허용 요청이 반복되고 생산성을 유지할 선택지가
Yes뿐이면 사용자가 위험한 요청까지 승인할 수 있음 - 디렉터리 밖 쓰기를 막는 기본 안전장치가 인간의 지속적인 주의력에 의존해서는 안 됨
메시지와 하위 에이전트 상호작용
- SSE 스트리밍 중 보낸 메시지는 대기열에 들어가지만 실제 전송 시점이 불명확함
- 코드는 도구 호출 턴이 끝날 때 전송하는 것으로 보이지만, 대기 메시지를 보내지 않은 채 도구에서 생각 과정으로 넘어가는 상황도 경험함
- 사용자가 중단하면 메시지는 대기 상태에서 빠져 로그에만 남고 전송할 수 없게 되므로, 새 스트림을 시작할 두 번째 메시지가 필요함
- 메시지 실행 취소가 로그에서 해당 메시지를 제거하지 못하는 경우도 있음
- 하위 에이전트와 직접 대화하거나 진행을 중단시킬 수 없음
- 잘못된 방향으로 가면 종료해 컨텍스트를 잃거나 토큰 소모를 지켜봐야 함
- 과거에는 이 기능이 있었던 것으로 보이지만 현재는 사라졌음
- 기본 채팅에서 하위 에이전트를
@mention해도 유용하게 작동하지 않으며 중단도 할 수 없음
- Qwen이 생각 과정에 도구 호출을 넣는 것처럼 하위 에이전트의 도구 호출이 실패하면 치명적 오류가 발생하고, 그때까지의 컨텍스트가 사라짐
- 하위 에이전트 재사용은 작은 컨텍스트로 작업을 분리하려는 목적과 충돌함
- 관련 없는 작업에 기존 하위 에이전트를 재사용할 수 있음
- 큰 컨텍스트를 가진 주 에이전트와 하위 에이전트 사이를 오가며 캐시 미스를 일으킴
- 인간용 상호작용은 풍부해도 되지만 모델에 제공하는 선택지는 줄여야 함
- 하위 에이전트 동작과 관련된 GitHub 이슈도 있음
에이전트 도구 설계
edit는 기본적으로 유일하게 일치하는 텍스트를 정확 검색·치환함- 모델은 파일 내용을 정확히 기억하면서도 여러 차례 편집 후 바뀐 줄 번호는 놓칠 수 있어 적합한 방식임
- 전역 치환 옵션은 여러 번의 후속 수정을 유발했으며, 이를 제거하면 Pi의
edit설계와 같아짐
- Plan 모드의 객관식
question도구는 시스템 프롬프트에서 자연어로 질문하게 하는 것보다 불편함 grep과glob는bash로 대체할 수 있으며, 실제로 모델도grep이나rg를 Bash에서 실행함Explore같은 읽기 전용 에이전트가 Bash를 사용하지 못하도록 제한하려는 목적일 수 있음- 이는 Bash 명령을 실행하지 않고 부작용을 판별하기 어렵다는 문제와 연결됨
todo는 대체로 유용하지만 모델이 TODO 확인 자체를 잊음
TUI와 문서 품질
- OpenCode TUI는 텍스트 렌더링에 약 1GB RAM을 사용함
- 메시지 입력창에서 Shift+Enter 줄바꿈이 작동하지 않았고, 기존 이슈는 “내 컴퓨터에서는 된다”는 답변 후 종료됨
- 긴 메시지가 자동 줄바꿈될 때 입력창과 커서는 이동하지만 새 줄의 문자가 보이지 않을 수 있음
- 스트리밍 도중 텍스트를 선택하면 자동 스크롤로 선택이 해제됨
Ctrl-C가 실행 중인 명령을 중단하는 대신 세션을 즉시 닫음- 대화형 셸 관례대로라면
Ctrl-C는 명령을 중단하고, 실행 중인 명령이 없을 때Ctrl-D가 세션을 종료해야 함
- 대화형 셸 관례대로라면
- Mac의 Option+좌우 화살표 같은 일반적인 단어 단위 이동 단축키를 지원하지 않음
- 메시지나 생각 과정이 길어지면 Markdown 재렌더링 등에 수 초가 걸리며, 제곱 시간 복잡도로 보이는 성능 문제가 있음
- 입력 문제 때문에 외부 편집기에서 메시지를 작성해 붙여넣어야 했음
- 문서는 일관성이 없고 사람이 아니라 모델이 읽도록 작성된 형태에 가까움
원격 우선 연결과 데이터 노출
- OpenCode는 기본적으로 원격 모델에 연결함
- 로컬 모델 설정의 간단한 예제가 문서에 없으며, 설정을 잘못하면 원격 모델로 연결됨
- 로컬 모델을 올바르게 지정해도 프로그램 실행 후 대화형으로 선택해야 하며, 그동안 원격 모델과 로컬 셸은 이미 연결된 상태가 됨
- 기본 모델 URL은 배포판에 고정되지 않고 OpenCode와 연계된 models.dev에서 다운로드함
- 관련 코드는
opencode/src/provider/provider.ts1684행에 있음
- 관련 코드는
- 새로 설치한 뒤
opencode를 실행하고 문자 하나와 Enter를 입력하는 것만으로, 사용자 설정 없이 원격 모델이 로컬 셸에 연결될 수 있음 - 첫 메시지가 비어 있거나 모호하면 에이전트 모델은 현재 디렉터리를 glob하고 파일을 읽는 경우가 많으며, 읽은 데이터는 다음 POST 요청에 포함됨
인터넷 접근과 시스템 프롬프트
- OpenCode는
WebFetch도구를 제공하며 시스템 프롬프트에서 이를 사용하도록 명시적으로 지시함 - 기본 프롬프트는 사용자가 제공한 메시지나 로컬 파일의 URL을 사용할 수 있게 하면서, 프로그래밍 지원 URL이라고 확신하면 URL을 생성하거나 추측할 수 있도록 모호하게 허용함
- Bash에는 네트워크 샌드박스가 없으므로
WebFetch보다 더 큰 문제는 모델이curl | bash같은 명령을 실행하지 않으리라 기대하는 구조임
Bash 권한 필터를 우회하는 방법
opencode.json의"bash": {"git *": "deny"}는git status나echo hello && git push --force를 차단함- 구현은 tree-sitter의 Bash·PowerShell 문법으로 명령을 AST로 파싱하고, 명령 노드를 순회해 설정에서 만든 정규식과 비교함
- 그러나 텍스트 기반 검사는 다양한 간접 실행을 허용함
echo 'git clean -fdx .' | bashenv git status- alias로
git을 다른 명령 이름에 연결하는 방식 /usr/bin/git status,$(which git) statusGIT=git && $GIT status- Base64로 인코딩한
git reset --hard를 디코딩해 Bash로 전달하는 방식 - heredoc 안의
git push --force - Python
subprocess.run으로git checkout .실행
- 모델이 보통 악의적이지 않더라도 실패를 끈질기게 우회하도록 훈련됐기 때문에 자연스럽게 적대적 입력처럼 행동할 수 있음
- 문자열 명령 필터는 안전장치가 아니라 잘못된 안도감을 줌
영구 권한과 CWD 예외
python3 -c 'print("hello")'에Always를 선택하면python3접두사 전체가 영구 허용됨- 이후 Python으로 SSH 개인 키를 읽는 명령도 이미 승인된 것으로 처리될 수 있음
- 권한은 디스크에 저장돼 이후 세션에도 유지됨
cd,chdir,popd,pushd,push-location,set-location은 부작용이 없다고 가정하는 CWD 예외 목록에 포함됨- 이 명령들은 모든 Bash 명령을 거부하도록 설정해도 권한 검사를 명시적으로 우회함
파일 접근 검사의 허점
- 기본 설정은 OpenCode를 실행한 디렉터리와 Git 저장소 중 더 짧은 경로 밖의 파일 접근을 막으려 함
- Bash 도구에서는 tree-sitter AST를 순회해 경로처럼 보이는 값을 해석하고 검사함
cat /tmp/logfile은 권한을 요구함python3 -c 'import shutil; shutil.rmtree("/")'는 검사하지 못함
cargo는 전역~/.cargo에서 읽기·쓰기·실행을 자유롭게 수행하지만, 모델이~/.cargo/registry/src의 패키지 소스를 직접 읽으려 하면 권한을 요구함- 파일에 접근할 수 있다고 간주하는 명령은 고정된
FILES목록으로 제한됨rm,cp,mv,mkdir,touch,chmod,chown,cat과 일부 PowerShell 명령 등이 들어 있음- 목록에 없는 명령은 파일에 접근하지 않는다고 가정하므로 전달된 경로도 검사하지 않음
리디렉션과 허용 명령의 결합
echo "hello world!"에Always를 선택하면 이후echo를 이용한 파일·장치 쓰기도 허용됨- GPIO 관련
/sys/class/gpio경로로 리디렉션하는 명령도 실행할 수 있음
- GPIO 관련
echo foo > bar.txt의 AST에서redirection은command의 자식이 아니라 형제 노드임- 경로 검사는
command자식만 대상으로 하므로 리디렉션 목적지를 검사하지 않음 echo자체도FILES목록에 없어 경로 검증이 시작되지 않음
- 경로 검사는
자체 업그레이드와 원격 코드 실행 사례
- OpenCode에는 여러 자체 업그레이드 경로가 있으며, curl 기반 설치판에서
opencode upgrade를 실행하면https://opencode.ai/install의 응답을 내려받아 Bash 표준 입력으로 실행함 - curl 설치 프로그램을 사용한 시점의 위험과 크게 다르지는 않지만, 프로덕션에서 원격 스크립트를 직접 실행하는 사례임
- CVE-2026-22812 당시 OpenCode는 기본 HTTP 서버에 다음 기능을 노출했음
- 완전히 허용적인 CORS 헤더
- 임의 셸 명령을 실행하는 POST API
- 임의 파일을 읽는 GET API
- 사용자가 방문한 웹사이트가 알려진 기본 포트로 요청해 사용자 권한 수준의 시스템 접근을 얻을 수 있었음
- 개발팀은 서버를 기본 비활성화하고,
opencode.ai가 머신에서 원격 코드를 실행할 수 있도록 CORS 예외가 필요하다고 답한 뒤 후속 대응을 이어가지 않았으며 이슈는 stale bot이 닫음 - 별도의 이슈는 인증 명령이 사용자가 전달한 임의 URL에서 콘텐츠를 가져와 실행한다고 보고했으며, 이 역시 stale bot으로 종료됨
Docker만으로 해결할 수 없는 이유
- 개발 의존성을 새 머신에 설치하기 어려울 정도로 복잡하게 만든 뒤 Docker에 의존하는 방식을 원하지 않음
- Docker 자체도 보안 문제를 만들 수 있음
- root로 실행되는 강력한 서비스를 만듦
ufw방화벽에 의도적으로 통로를 냄
- 보호할 모든 데이터가 컨테이너 안에 있고 내부 로컬 셸이 인터넷과 연결돼 있다면 보호 범위가 불분명함
- 목표가 루트 파일시스템의 재귀 삭제 방지라면 Landlock, Seatbelt, Restricted Tokens 같은 더 직접적인 운영체제 수단을 사용할 수 있음
- 코딩 에이전트 보안은 별도 컨테이너에 책임을 넘길 문제가 아니라 하네스의 최우선 과제여야 함
- Git 차단은 명령 문자열이 아니라
git실행 파일 자체를 막아야 함 .git디렉터리는 읽기 전용으로 만들어야 함- Bash 명령을 텍스트로 정화하는 대신 네이티브 운영체제 격리를 사용해야 함
- Git 차단은 명령 문자열이 아니라
로컬 LLM 사용 경험
- Qwen3.6-27B 같은 로컬 모델도 프런티어 모델처럼 코드베이스의 안정성과 개념적 일관성을 훼손할 수 있지만 세 가지 차이가 있음
- 지능적으로 보이다가 어리석게 행동하는 불쾌한 골짜기가 덜하며, 한계가 명확해 상호작용을 조정하기 쉬움
- 가중치 수가 훈련 데이터를 그대로 재현하기에는 적어 출력물 오염 여부에 대한 판단이 달라짐
- 클라우드 제공자를 지원하거나 의존하지 않아도 됨
- 코드와 증상, 추정 원인을 제공하고 관련 코드를 읽은 뒤 호출 경로와 코드 인용을 요구하는 입력 중심 검색 작업에서는 유용한 결과를 얻음
- 검색 문제로 범위를 정하면 모델이 사실을 만들어내는 성향을 줄일 수 있음
- 코드 생성은 아키텍처 계획을 반복적으로 무너뜨림
- 여러 구성요소가 공유하도록 설계 중간에 가변 상태를 옮기는 식의 지름길을 택함
- 직접 작성하지 않았다는 문제를 넘어 코드 이해 능력 자체를 해침
- 모델 가중치의 지식에서 직접 답을 꺼내는 방식은 수조 개 매개변수 모델에서도 환각을 일으킴
- LLM이 보통의 도구가 되려면 주변 소프트웨어에 실제 시스템 엔지니어링을 적용해 보안 공백을 제거해야 하며, 그 작업은 사람이 해야 함