
go-busybox 저장소는 BusyBox 유틸리티를 Go로 구현한 프로젝트다. TinyGo로 WebAssembly(WASM) 컴파일이 가능해 AI 에이전트에서 샌드박스 방식으로 실행할 수 있다. 여러 애플릿을 하나로 묶는 busybox 실행 파일과 cmd/ 아래의 독립 실행 파일을 만든다.
기준 구현은 BusyBox v1.35.0(Debian 1:1.35.0-4+b7)이며, 저장소가 제시한 기준 테스트 결과는 387개 중 387개 통과다.
설치와 실행
개발 환경에는 Go 1.22 이상, WASM 빌드용 TinyGo 0.34 이상, Make가 필요하다. 저장소는 도구 설치, 개발 도구 설치, 네이티브 및 WASM 빌드, 테스트를 다음 명령으로 안내한다.
make setup-toolchain # Go + TinyGo via Homebrew
make install-dev # linters, security tools
make build # native binary → _build/busybox
make build-wasm # WASM binary → _build/busybox.wasm (4.7MB)
make build-wasm-optimized # size-optimised → _build/busybox.wasm (2.0MB)
make test
./_build/busybox sed 's/foo/bar/g' input.txt
ln -s _build/busybox _build/grep && ./_build/grep -i pattern file.txt
wasmtime --dir=. _build/busybox.wasm cat file.txt
네이티브 빌드는 _build/busybox를 만든다. 기본 WASM 빌드는 _build/busybox.wasm을 만들며, 저장소 설명에 따르면 크기는 4.7MB다. 크기 최적화 빌드도 같은 경로에 출력되며 설명된 크기는 2.0MB다. 예시에서는 sed를 통합 실행 파일로 실행하고, 심볼릭 링크 이름으로 grep을 호출하며, Wasmtime에서 현재 디렉터리를 열어 cat을 실행한다.
애플릿과 테스트 결과
프로젝트에는 전체 Godoc(플래그 목록 포함)이 있는 애플릿 57개가 있다. 범주별 목록은 다음과 같다.
- 셸:
ash(sh) - 텍스트 처리:
awk,cat,cut,diff,echo,grep,head,printf,sed,sort,tail,tr,uniq,wc - 파일:
cp,find,ls,mkdir,mv,pwd,rm,rmdir - 아카이브:
gunzip,gzip,tar - 프로세스:
kill,killall,nice,nohup,pgrep,pidof,pkill,ps,renice,setsid,start-stop-daemon,taskset,time,timeout,top,xargs - 시스템:
free,ionice,logname,nproc,sleep,uptime,users,w,watch,who,whoami - 네트워크:
dig,nc,ss,wget
저장소의 BusyBox 기준 테스트는 /bin/bash에서 ECHO="echo" 설정으로 실행됐다. 새 형식의 .tests 테스트는 308/308, 디렉터리 기반 구형 테스트는 79/79를 통과했다. 구형 테스트 대상은 cat, cp, cut, echo, find, gzip, ls, mkdir, mv, pwd, rm, rmdir, tail, tr, wc, wget이다. 두 결과를 합친 기준 테스트 수가 387/387이다.
새 형식 테스트의 애플릿별 결과는 다음과 같다.
| 애플릿 | 통과 | 애플릿 | 통과 | 애플릿 | 통과 |
|---|---|---|---|---|---|
awk | 53/53 | gunzip | 5/5 | sort | 5/5 |
cp | 13/13 | head | 2/2 | tail | 2/2 |
cut | 22/22 | pidof | 3/3 | tar | 3/3 |
diff | 12/12 | printf | 24/24 | taskset | 3/3 |
find | 2/2 | sed | 92/92 | tr | 2/2 |
grep | 44/44 | uniq | 14/14 | xargs | 7/7 |
ash 셸 테스트는 349/349를 통과했다. 세부 결과는 ash-alias 5/5, ash-arith 6/6, ash-comm 3/3, ash-getopts 8/8, ash-glob 10/10, ash-heredoc 25/25, ash-invert 3/3, ash-misc 99/99, ash-parsing 35/35, ash-quoting 24/24, ash-read 10/10, ash-redir 27/27, ash-signals 22/22, ash-standalone 6/6, ash-vars 69/69, ash-z_slow 3/3이다.
셸과 텍스트 처리 기능
ash는 파이프라인, 입력·출력 리디렉션(<, >, >>, 2>, >&, <&, >&-), 조건문과 반복문, 명령 치환($(...), 백틱), 산술식($(())), 함수, 매개변수 확장, 25개 이상의 내장 명령을 지원한다. 백그라운드 작업(&, jobs/fg/wait), here-document, 서브셸, 트랩과 신호 처리도 포함한다. 저장소는 349개 테스트 통과에 이르기까지 21개 수정 사항을 적용했다고 설명한다.
수정 사항에는 파서의 백슬래시-줄바꿈 처리, 줄 연속에 걸친 if, 빈 단어 처리($empty""), 인수가 없을 때의 "$@", if() 예약어 판별, case 패턴 인용 및 본문 안의 별칭 처리가 포함된다. 확장 기능에서는 내장 명령 앞의 IFS=, heredoc 안의 ${x#'*'} 패턴 인용, 작은따옴표 패턴, 작은따옴표 안의 백틱, 명령 치환 결과의 줄바꿈 제거, unset -ff, 파일 끝의 \<newline>을 다뤘다. 리디렉션 수정은 exec 1>&-의 지속성, 1>&-·2>&- 구문 분석, exec <file 뒤의 read와 cat을 포함한다. 신호 처리에서는 트랩 종료 코드, 중첩 트랩, 트랩 처리기 안의 return, 서브셸의 신호 초기화를 수정했다.
텍스트 처리 애플릿의 구현 범위와 해당 기준 테스트 결과는 다음과 같다.
sed는 BRE/ERE(-E/-r),\1부터\9까지의 역참조, 표준 명령s,d,p,q,a,i,c,y,=,l,r,w,h,H,g,G,x,N,P,D,b,t,T를 지원한다. 줄 번호·$·정규식·범위·first~step·0,/regex/주소, 제자리 편집(-i),\n·\t·\r이스케이프, 레이블과 분기, 여러 표현식(-e,-f),--version도 포함한다. 입력의 마지막 줄에 끝 줄바꿈이 없으면 그 줄에서 유래한 마지막 출력 줄에도\n을 붙이지 않도록noNLLines map[int]bool로 줄별 끝 줄바꿈을 추적한다.w명령도wfileLastLine을 이용해 출력 파일에서 같은 방식으로 처리한다. 테스트는 92/92다.diff는-u,-U N,-b,-w,-B,-a,-q,-r,-N을 지원하며 테스트는 12/12다.-B는 빈 줄을 diff 전에 제거하는 대신 결과에서 빈 줄만 있는 헝크를 걸러낸다.grep은-E,-F,-i,-v,-c,-l,-L,-n,-h,-H,-o,-q,-r,-w,-x,-s,-e,-f를 지원한다.egrep과fgrep이름으로도 호출할 수 있으며 테스트는 44/44다.awk는BEGIN/END, 패턴, 배열, 제어 흐름, 30개 이상의 내장 함수,printf/sprintf,getline, 정규식, 입출력 리디렉션을 포함한 파서와 평가기를 제공한다. 테스트는 53/53이다.
저장소의 애플릿 상태 요약에는 ash 349/349, awk 53/53, sed 92/92, grep 44/44, diff 12/12, cp 13/13, printf 24/24, cut 22/22, uniq 14/14, sort 5/5, tar 3/3, gunzip 5/5, wget 4/4, pidof 3/3, taskset 3/3, xargs 7/7, tr 2/2를 기록했다. 주요 기능으로는 cp의 재귀 복사·심볼릭 링크·--parents·하드 링크, tar의 생성·추출·gzip·표준 입력·심볼릭 링크·하드 링크, wget의 -q·-O·-P·결합 플래그·자동 디렉터리 생성 등이 제시돼 있다. start-stop-daemon은 테스트 결과가 없으며 네이티브에서 --start/--exec와 --pidfile을 부분 지원한다. top은 정적 목록 스텁이다. 저장소는 그 밖의 애플릿은 완전 구현됐지만 전용 기준 테스트는 없다고 적었다.
테스트와 퍼징
저장소는 테스트를 네 층으로 구성한다. 애플릿별 단위 테스트는 *_test.go 파일 62개, 퍼즈 테스트는 *_fuzz_test.go 파일 58개에 함수 100개와 시드 353개가 있다. 통합 테스트는 pkg/integration/의 BusyBox 비교 행렬을 사용하며, 별도 기준 테스트 모음은 387/387이다.
모든 애플릿에는 퍼즈 테스트가 하나 이상 있다. 복잡도가 높은 22개 애플릿은 여러 함수로 시험한다. 주요 대상과 함수 수·시드 수·시험 범위는 다음과 같다.
| 애플릿 | 함수 | 시드 | 범위 |
|---|---|---|---|
sed | 4 | 33 | 입력, 표현식 분석, 여러 명령, 플래그 |
grep | 3 | 22 | 입력, 패턴 분석, 플래그 |
cut | 4 | 20 | 구분 필드, 필드 지정, 문자 모드, 플래그 |
tr | 5 | 22 | 변환, 삭제, 압축, 보수 집합, SET 피연산자 |
diff | 5 | 15 | 동일·차이 입력, 플래그, 바이너리, 문맥 |
awk | 2 | 25 | 프로그램 분석, 고정 프로그램을 이용한 입력 |
tar | 3 | 10 | 생성, 생성 후 추출 왕복, 잘못된 아카이브 |
gzip | 3 | 11 | 압축, 압축 후 해제 왕복, 레벨 |
gunzip | 3 | 10 | 압축 해제, 잘못된 데이터, 표준 입력 |
cat | 3 | 11 | 파일, 표준 입력, 여러 파일 |
head | 3 | 14 | 고정 -n, 다양한 -n, 표준 입력 |
tail | 3 | 11 | 고정 -n, 표준 입력, 플래그 |
sort | 2 | 10 | 입력, -r·-n·-u·-f 플래그 |
uniq | 2 | 9 | 입력, -c·-d·-u·-i 플래그 |
wc | 2 | 10 | 입력, -l·-w·-c·-L 플래그 |
echo | 2 | 10 | 인수, -n·-e·-ne·-E 플래그 |
find | 3 | 9 | 순회, -name 패턴, 조건식 |
ls | 2 | 4 | 목록, -1·-a·-l·-R·-S·-r·-t 플래그 |
cp | 3 | 10 | 복사, 복사 검증 왕복, 플래그 |
xargs | 3 | 11 | echo, 명령 생략 시 기본값, -n·-I 플래그 |
printf | 2 | 20 | 형식 문자열, 인수 |
procutil | 2 | 8 | 신호 분석, UID 조회 |
왕복 테스트는 데이터가 온전히 유지되는지 확인한다. gzip -c | gunzip은 compress/gzip을 이용해 바이트 단위로 비교하고, tar -cf | tar -xf는 추출한 파일을 원본과 비교하며, cp src dst는 대상 파일을 원본과 비교한다. 퍼징 중 무한 실행을 막기 위해 sed는 분기 레이블·N;D 순환·과도한 수량자를 거부하고, printf는 인수를 소비하지 않는 형식 문자열을 건너뛴다. 모든 입력은 2048바이트로 제한한다.
테스트 실행 명령은 다음과 같다.
make test # unit tests
make test-race # with race detector
make coverage # with coverage
# fuzz seeds only (quick)
go test ./pkg/applets/sed -run='Fuzz' -count=1
# actual fuzzing (10s)
go test ./pkg/applets/sed -run='^$' -fuzz='^FuzzSedExpr$' -fuzztime=10s
# all fuzz seeds
for d in pkg/applets/*/; do go test ./$d -run='Fuzz' -count=1 -timeout 30s; done
WASM 샌드박스와 제약
WASM에서 유틸리티는 WASI의 기능 기반 권한 모델 안에서 실행된다. 미리 열어 둔 디렉터리만 접근할 수 있고 네트워크는 기본적으로 꺼져 있다. 메모리는 WASM 선형 메모리로 격리되며 시스템 호출은 WASI preview1로 제한된다. 저장소는 경로별 읽기·쓰기 권한과 현재 디렉터리 접근을 다음처럼 설정하는 예를 제시한다.
sandbox.Init(&sandbox.Config{
AllowedPaths: []sandbox.PathRule{
{Path: "/data", Permission: sandbox.PermRead | sandbox.PermWrite},
{Path: "/config", Permission: sandbox.PermRead},
},
AllowCwd: true,
CwdPermission: sandbox.PermRead,
})
WASI에서 사용할 수 없는 운영체제 API가 필요한 애플릿에는 *_wasm.go 스텁을 둔다. 해당 스텁은 오류를 출력하고 종료한다. 스텁 처리되는 16개 애플릿은 ash, ionice, nice, nohup, renice, setsid, ss, start-stop-daemon, taskset, time, timeout, top, users, w, watch, who다. 나머지 41개는 WASM으로 컴파일해 실행할 수 있다.
kill, killall, pgrep, pkill, pidof, ps 같은 프로세스 관리 애플릿은 컴파일되지만 실행 시 /proc에 의존한다. 파일 시스템에 /proc가 마운트되지 않은 환경에서는 정상적으로 실패한다. WASI의 procutil 신호 표에서도 SIGHUP, SIGUSR1, SIGUSR2, SIGALRM은 제외된다.
저장소 구성과 개발
저장소는 cmd/에 독립 실행 진입점 51개와 통합 실행 파일을 두고, pkg/applets/에 애플릿 패키지 57개를 둔다. ash는 5,700줄 이상, sed는 1,200줄 이상이다. 공통 코드는 pkg/core/의 fs/, archiveutil/, headtail, textutil에 있고, pkg/integration/은 BusyBox 비교 테스트, pkg/sandbox/는 기능 기반 샌드박스, pkg/testutil/은 FuzzCompare와 CaptureStdio 등을 담는다. busybox-reference/에는 v1.35.0 기준 바이너리와 테스트 모음이 있으며, docs/SPEC.md, plan.md, Makefile도 포함한다.
저장소가 제시한 규모는 애플릿 패키지 57개(여기에 procutil 추가), 비테스트 소스 파일 138개, 소스 약 29,000줄, 단위 테스트 파일 62개, 퍼즈 테스트 파일 58개, 퍼즈 함수 100개, 퍼즈 시드 353개다.
주요 Make 대상은 네이티브 빌드 make build, WASM 빌드 make build-wasm, 단위 테스트 make test, 레이스 감지기 테스트 make test-race, 커버리지 make coverage, golangci-lint를 실행하는 make lint, vet·lint·포맷 검사를 하는 make check, gosec을 실행하는 make security, 퍼즈 커버리지 80% 이상을 확인하는 make fuzz-coverage, 생성물을 지우는 make clean이다.
새 애플릿을 추가할 때는 pkg/applets/<name>/<name>.go에 Run(stdio *core.Stdio, args []string) int를 구현하고, 패키지와 Run의 Godoc에 플래그 목록을 적는다. 이어 cmd/busybox/main.go에 등록하고 cmd/<name>/main.go 진입점을 추가한 뒤 단위 테스트와 퍼즈 테스트를 작성한다. 필요한 경우 *_wasm.go 스텁도 추가한다. Godoc은 지원 플래그를 함수 설명에 나열하는 형식을 따른다.
프로젝트 라이선스는 MIT다. 기여 절차는 포크와 브랜치 생성 후 작업하고, 커밋 전에 make check, 린트 뒤 make security, 모든 퍼즈 시드를 확인하는 go test ./pkg/applets/... -run='Fuzz' -count=1을 실행한 다음 풀 리퀘스트를 보내는 방식이다. 전체 명세는 SPEC.md에 있다.
댓글 (0)
로그인하면 이 기사에 내 생각을 남길 수 있어요