개요
Coding Agent의 명령 실행을 Bash AST (Abstract Syntax Tree) 기반으로 검사하는 도구 「Vetol」을 만들었습니다.
이 명령으로 설치할 수 있습니다.
go install github.com/tf63/vetol/cmd/vetol@latest
배경
Coding Agent의 PreToolUse
Hook을 사용하면, 실행할 명령을 사전에 검사하여 허가할지 거부할지를 제어할 수 있습니다.
예를 들어, git push -f를 Prefix 기반의 패턴 매칭 git push -f *로 거부하면, Coding Agent에 의한 git push -f origin HEAD와 같은 명령의 실행을 거부할 수 있습니다.
하지만 Bash의 경우, 다음과 같은 명령은 쉽게 우회(bypass)됩니다.
git push -uf origin main
git push -u -f origin main
SAMPLE=1 git push -f origin main
pwd && git push -f origin main
echo $(git push -f origin main)
그 외에도, Claude Code의 PreToolUse 샘플에서는 명령을 검사하기 위해 단순한 정규 표현식(Regular Expression)을 사용하고 있습니다.
if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
...
하지만 이는 rm -fr 등이 실행되면 우회될 수 있습니다.
만든 것
그래서 만든 것이 Vetol입니다. Vetol은 Bash의 AST를 해석하여, 명령 치환(Command Substitution)이나 논리 연산자(&&, ||) 내부에 있는 명령도 추출할 수 있습니다.
예를 들어, 다음 명령으로부터는 다음과 같은 명령들을 추출하여 검사합니다.
echo `ls && rm -rf / || echo $(git push -f origin main)`
echo
ls
rm -rf /
echo
git push -f origin main
이 5개의 명령에 대해 allowlist 또는 denylist 모드의 규칙을 적용하여 허가/거부를 결정합니다.
규칙은 JSON 형식으로 지정합니다. allowlist 모드에서는 허가하고 싶은 명령을 지정하며, 그 외에는 모두 거부됩니다. 또한, include / exclude 필드를 사용하여 특정 플래그나 인자(Argument)가 있는 경우에만 허가/거부할 수도 있습니다.
{
"mode": "allowlist",
"rules": [
...
denylist 모드에서는 거부하고 싶은 명령을 지정하며, 그 외에는 모두 허가됩니다.
{
"mode": "denylist",
"rules": [
...
CLI에서 이 JSON을 지정하여 실행하면, 대상 명령이 허가되어 있을 때는 ALLOW, 거부되어 있을 때는 DENY가 출력됩니다.
vetol --config vetol.json "ls -la /tmp"
ALLOW # 상태 코드 0으로 종료
vetol --config vetol.json "rm -rf /"
DENY # 상태 코드 1로 종료
또한, include / exclude 필드는 명령 인자의 위치나 생략 표기법을 고려하여 검사됩니다. git push의 include에 -f를 지정한 경우, 다음과 같은 명령들을 체크할 수 있습니다.
git push -f origin HEAD
git push -f -u origin HEAD
git push -uf origin HEAD
git push -u -f origin HEAD
이처럼 단순한 문자열 매칭으로는 놓치기 쉬운 명령도 Bash의 구문을 해석함으로써 검사할 수 있습니다.
Claude Code에서의 사용 예시
Claude Code에서 Vetol을 사용하는 경우, 다음과 같은 설정 파일을 작성하여 PreToolUse 훅에서 호출합니다.
{
"mode": "allowlist",
...
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
...
주의사항
Vetol은 AST (Abstract Syntax Tree) 기반으로 명령어를 검사하기 때문에, 문자열을 eval 하여 실행하는 타입의 명령어구나 동적으로 생성되는 명령어는 탐지할 수 없습니다. 예를 들어, 다음과 같은 명령어들입니다.
bash -c "rm -rf /"
eval "git push -f origin main"
python -c "os.system('rm -rf /')"
이러한 경우에 대응하기 위해서는, denylist 모드에서 위험한 인터프리터 호출을 차단합니다.
{
"mode": "denylist",
"rules": [
...
또한, 예상치 못한 명령어가 실행될 가능성도 있으므로, allowlist 모드에서 필요한 명령어만 허용하는 것이 안전하다고 생각합니다.
{
"mode": "allowlist",
"rules": [{ "command": "ls" }, { "command": "git status" }, { "command": "go test" }]
...
마치며
Coding Agent의 권한 제어에서는 단순한 문자열 매칭이나 정규 표현식만으로는 불충분한 경우가 있습니다. Vetol은 Bash AST를 분석함으로써, 명령어 치환(Command Substitution)이나 논리 연산자 내부에 있는 명령어까지도 검사할 수 있도록 했습니다.
만약 개선점이나 버그 등이 있다면, GitHub 리포지토리에 Issue를 남겨주시면 감사하겠습니다. 또한, Star를 눌러주시면 큰 힘이 됩니다.
이상, 무엇인가 참고가 되기를 바랍니다.
Discussion