원문 캡처 · github.com
원문 캡처 · github.com

shellfirm은 셸 명령이 실행되기 전에 위험한 명령을 가로채는 안전장치다. AI 에이전트가 실행하는 명령에도 연결할 수 있지만, 제작자는 이를 샌드박스가 아닌 실수를 막기 위한 장치라고 설명한다.

실행하기 전에 생각하라.

프로젝트가 제시한 실제 출력 예시에서는 보호된 main 브랜치에서 git push origin main --force를 실행하려 하자 위험 명령으로 감지했다. 심각도는 HIGH, 영향 범위는 [PROJECT]로 표시되고, 원격 기록을 덮어쓸 수 있다는 설명과 함께 git push --force-with-lease를 대안으로 제안했다. 보호 브랜치라는 맥락 때문에 확인 절차가 강화돼 사용자는 yes를 입력해야 했다. 이 출력은 기본 설정의 zsh에서 캡처한 것이다. bash에서는 배너 앞에 빈 줄이 나오며, 보호 브랜치가 아니면 확인 절차가 강화되지 않는다. 그런 경우 배너에는 shellfirm config ignore <id>로 해당 확인을 끄는 방법도 표시되지만, 맥락이 확인 절차를 강화한 경우에는 이 안내가 생략된다.

표시되는 내용은 명령, 설정, 실행 환경에 따라 다르다. 확인 방식은 간단한 수학 문제 풀기, Enter 누르기, yes 입력 중 하나다. 심각도가 높거나 SSH 접속, root 권한, 보호 브랜치, 운영 Kubernetes 클러스터처럼 위험한 맥락이면 더 엄격한 확인을 요구한다. 차단 목록에 있는 명령은 확인 없이 막는다. 수학 문제를 원하지 않는 경우 접근성 안내를 참고할 수 있다.

기능과 적용 범위

  • 파일 시스템, Git, Kubernetes, Terraform, Docker, AWS, GCP, Azure, Heroku, Fly.io, Vercel, Netlify, GitHub, npm, 데이터베이스, Redis, MongoDB, 네트워크, curl … | bash 같은 원격 스크립트 실행(shell), 자격 증명 유출(exfil) 등을 포함해 23개 그룹에 걸친 기본 검사 496개를 제공하며, 모두 기본 활성화돼 있다. shellfirm check --list --all로 검사 목록을 볼 수 있다. 검사 카탈로그에서 현재 정확한 개수를 확인할 수 있다.
  • Bash, Zsh, Fish, Elvish, Xonsh, Nushell, PowerShell, Oils 등 8개 셸에 훅을 제공한다. Bash·Zsh·Fish·Elvish·Xonsh는 종단 간 테스트를 마쳤다. Nushell은 제한 사항이 있는 상태로 테스트됐고, PowerShell과 Oils의 훅은 제공되지만 종단 간 테스트는 아직 이뤄지지 않았다. shellfirm status에서 훅 설치 여부를 확인할 수 있다. 자세한 내용은 셸 지원을 참고한다.
  • SSH 연결, root 권한, 보호된 Git 브랜치, 운영 Kubernetes 클러스터에서는 상황을 고려해 더 어려운 확인 절차를 요구한다.
  • 많은 검사에서 --force-with-lease 같은 더 안전한 대체 명령을 제안한다.
  • Critical, High, Medium, Low, Info 심각도와 설정 가능한 임계값을 지원한다.
  • 저장소에 .shellfirm.yaml을 커밋하면 팀 전체에 적용되는 규칙을 배포할 수 있다. 조직 공통 기준은 /etc/shellfirm/policy.yaml에 설치할 수 있다. 정책은 추가 적용되며 강제된다. 개인 설정으로 정책을 약화할 수 없다. 자세한 내용은 팀 정책 문서에 있다.
  • 가로챈 명령과 결정은 사용자, 호스트, 저장소, 브랜치, 결과, 차단한 정책 등을 포함한 JSON Lines 형식으로 감사 로그에 기록된다. 비밀 정보는 가리고 로그 회전 범위를 제한하며, 필터와 표 보기 기능도 제공한다. 예를 들어 shellfirm audit show --since 7d --outcome denied를 사용할 수 있다. 자세한 내용은 감사 로그 문서를 참고한다.
  • 명령이 영향을 줄 파일, 브랜치, 리소스 등의 범위를 보여준다. 명령 하나에 적용되는 총 시간 예산은 blast_radius_budget_ms로 정하며 기본값은 400ms, 최댓값은 5000이다. shellfirm config impact-budget <ms>로 설정한다. 확인 창이 오래 기다리지 않도록 시간 예산을 넘긴 파일 수는 more than 120,000 files (stopped counting)으로 표시하고, 시작하지 못한 단계는 skipped, no time left라고 알린다. 200ms 넘게 걸리면 shellfirm: checking impact… (Ctrl-C cancels)라는 임시 문구를 표시한다. 이때 Ctrl-C를 누르면 명령을 취소하고 감사 로그에 기록한다.
  • Git으로 복구할 수 없는 미커밋·미추적 파일이 들어 있는 폴더를 재귀적으로 삭제하려 하면 High로 분류한다. 따라서 min_severity를 낮게 설정해도 확인을 요구한다.
  • 확인을 통과한 뒤에는 ✓ approved — running: <command>가 터미널에 흐린 글씨로 표시된다. helm uninstall처럼 오래 걸리는 명령이 멈춘 것처럼 보이지 않도록 하는 안내이며, shellfirm config approved-notice off로 끌 수 있다.
  • MCP 서버와 에이전트 훅을 제공해 Claude Code, Cursor 및 다른 에이전트의 안전장치로 사용할 수 있다.
  • LLM 분석은 실험 기능이다. shellfirm config llm으로 설정하며, agent.llm_review가 활성화된 경우에만 AI 에이전트 명령을 검토하는 데 사용된다.

AI 도구 연결

Claude Code는 shellfirm connect claude-code 한 번으로 자동 안전 확인용 훅과 필요할 때 분석을 요청하는 MCP를 함께 설정한다.

text
shellfirm connect claude-code

이 설정은 모든 Bash 명령을 실행 전에 검사해 위험한 명령을 차단하는 훅과, Claude가 위험을 설명하고 대안을 제안하도록 호출할 수 있는 MCP 도구를 추가한다. 안전한 명령은 훅이 응답하지 않으므로 Claude Code 자체의 권한 규칙에 따라 확인을 요청할지 결정한다. shellfirm 0.3.10의 훅은 안전한 명령을 그대로 승인했다. 0.3.10에서 업그레이드한 뒤에는 shellfirm connect claude-code를 다시 실행해야 한다. 자세한 내용은 0.3.10에서 업그레이드하기를 참고한다.

MCP 도구는 다음과 같다.

  • check_command: 명령의 위험 여부를 검사해 심각도, 일치한 규칙, 대안을 반환한다.
  • suggest_alternative: 더 안전한 대체 명령을 제안한다.
  • explain_risk: 명령이 위험한 이유를 자세히 설명한다.
  • get_policy: 현재 적용 중인 shellfirm 설정을 읽는다.

설치와 시작

0.3.10에서 업그레이드할 때 패키지 업그레이드는 바이너리만 교체한다. 업그레이드 안내에 따라 셸을 다시 시작하고, Elvish·Xonsh·Nushell에서는 shellfirm init <shell>을 다시 실행해야 한다. 각 에이전트에는 shellfirm connect <agent>를 다시 실행하고, 새 종료 코드에 맞게 CI 스크립트를 고치며, 새 기본 그룹도 활성화해야 한다. 필요한 명령은 shellfirm status에서 확인할 수 있다.

설치 방법은 npm, Homebrew, Cargo와 사전 빌드 바이너리 다운로드다.

text
npm install -g @shellfirm/cli
text
brew tap kaplanelad/tap && brew install shellfirm
text
cargo install shellfirm
text
cargo binstall shellfirm

Cargo의 binstall 워크플로로 사전 빌드 바이너리를 설치할 수 있으며, 릴리스 페이지에서 바이너리를 내려받아도 된다.

빠른 시작은 셸 훅 설치, 셸 재시작, 동작 확인 순서로 진행한다.

text
shellfirm init # 현재 셸에 훅을 설치한 뒤 다른 셸도 설치할지 묻는다 [y/N]
shellfirm init zsh # zsh에만 설치
shellfirm init --all # 지원하는 모든 셸에 설치
shellfirm init --dry-run # 변경 사항 미리 보기
shellfirm init --yes # 스크립트·프로비저닝용으로 질문 없이 설치하고 권장 Balanced 수준 설정

shellfirm init은 설치를 확인한 뒤 확인 방식(Math, Enter, Yes)과 보호 수준을 묻는다. 설치한 뒤에는 exec zsh, exec bash 등을 실행하거나 새 터미널을 열어 셸을 다시 시작한다. 다음 명령으로 상태와 동작을 점검할 수 있다.

text
shellfirm status # 훅, 설정, 정책, 임계값 표시
shellfirm doctor # 새 셸에서 명령이 실제로 shellfirm을 거치는지 확인
git reset --hard # shellfirm 확인이 실행돼야 한다

셸별 동작과 한계

Bash, Zsh, Fish, Elvish, Xonsh에서는 위험 명령을 실행 전에 확인하는 동작을 테스트했다. 다만 Elvish는 fn 함수를 검사 전에 확장하지 않으며, Xonsh는 계산된 값으로 프로그램을 시작하는 Python 코드를 검사하지 않는다.

Nushell은 테스트했지만 훅에서 명령행을 취소할 수 없다. 따라서 rm, mv 같은 Nushell 내장 명령은 사용자가 확인을 취소해도 실행된다. 외부 프로그램은 검사 대상이다. 이 때문에 Nushell 보호 기능은 차단 장치가 아니라 경고로 취급해야 한다. PowerShell과 osh(Oils) 훅은 제공되지만 종단 간 테스트를 마치지 않았다. 프로젝트는 작동 여부를 보고해 달라고 안내한다.

셸 훅은 대화형 프롬프트에 입력한 명령을 검사한다. AI 에이전트에는 셸 훅이 아니라 shellfirm connect로 에이전트 훅을 설정해야 한다.

훅은 검사 전에 가능한 범위에서 별칭, 이력 참조, 호출한 작은 셸 함수의 본문을 확장한다. del → rmrf → rm -rf 같은 연쇄 별칭과 !!, !$ 같은 이력 참조도 대상이다. 예를 들어 nuke() { rm -rf "$@"; }; nuke build처럼 함수를 호출하면 배너의 Command: 줄에 실제 실행될 내용이 표시된다. Bash·Zsh·Fish에서는 한 줄에서 호출되는 모든 함수를 깊이에 관계없이 따라가며, 함수에 전달된 인수와 shift 이후의 인수도 반영한다. 전체 한도는 함수 64개 또는 64KB이며 함수 하나의 본문은 16KB까지다. 한도를 넘으면 검사를 건너뛰지 않고 Medium 수준의 shell:function_depth_limit 확인을 요구한다.

파일에서 불러온 함수가 16KB를 넘으면(nvm, sdkman, conda 등) 본문을 읽지 않고 실행하는 스크립트처럼 취급한다. 따라서 nvm use 18은 확인을 요구하지 않으며, 위험성이 함수 본문에만 있는 bigclean ~도 마찬가지다. 반면 함수가 실행하도록 넘겨받은 명령인 nvm exec 18 rm -rf ~/data는 검사한다. 세션에서 직접 입력하거나 붙여넣은 함수는 Medium 확인을 요구한다. Nushell은 별칭과 def를 따라가고, Xonsh는 별칭 연쇄를 최대 10단계까지 확장한다. Elvish의 fn 함수는 확장하지 않는다.

실행하는 스크립트(./cleanup.sh)의 내용은 검사하지 않는다. 실행 시점에 만들어지는 명령($CMD, eval "$(…)"의 출력)은 명령행에서 보이는 범위까지만 검사할 수 있다. shellfirm은 실수를 막는 장치이지 샌드박스가 아니다.

bash의 fc 내장 명령은 비활성화된다. command fc는 command_not_found_handle을 거쳐 검사되는 fc를 서브셸에서 실행한다. builtin fc는 프롬프트에 직접 입력한 줄에서만 작동한다.

Bash·Zsh·Fish에서 shellfirm이 명령을 차단하거나 사용자가 취소한 뒤에도 $? 또는 $status에는 이전 명령의 종료 상태가 남는다. 차단된 줄은 실행되지 않았고 키 바인딩으로 종료 상태를 설정할 수 없기 때문이다. Nushell은 $env.LAST_EXIT_CODE = 1로 설정한다. 차단된 줄은 편집기에 남으며 Ctrl-U로 지울 수 있다. 스크립트와 CI에서는 대화형 프롬프트의 $? 대신 shellfirm check --exit-code(종료 코드 10 / 11) 또는 shellfirm pre-command(종료 코드 65 / 77)를 사용해야 한다.

일상적인 사용과 설정

명령이 왜 표시됐는지 확인하려면 다음 명령을 사용할 수 있다.

text
shellfirm check -c 'git reset --hard' # 일치한 검사 ID, 심각도, 대안 표시
shellfirm check -c 'git reset --hard' --format json

텍스트 출력의 마지막에는 셸에서 실행할 때의 처리 방식(예: Shell (you, in a terminal): would challenge (Math))과 AI 에이전트 훅의 응답(예: AI agent (hooks, MCP): deny)이 나온다. 수학 문제를 풀면 실행할 수 있는 명령과 차단되는 명령을 혼동하지 않도록 구분한다. CI에서 --strict를 쓰면 설정, 사용자 지정 검사 또는 정책에 경고가 있을 때 종료 코드 78로 끝난다.

shellfirm에는 표시된 명령을 한 번만 허용하는 기능이 없다. 명령을 실행하려면 확인 절차를 통과해야 한다. 특정 검사를 계속 묻지 않게 하려면 해당 ID를 무시하고, 다시 적용하려면 제거한다. 검사를 항상 차단하도록 설정하거나 확인할 심각도 기준을 바꿀 수도 있다.

text
shellfirm config ignore git:reset # 이 검사에 다시 묻지 않음
shellfirm config ignore --remove git:reset # 설정 되돌리기
shellfirm config deny git:force_push # 이 검사를 항상 차단
shellfirm config severity High # High와 Critical만 확인

알 수 없는 ID는 제안과 함께 거부되므로 오타 때문에 보호가 조용히 꺼지지 않는다. 프로젝트의 .shellfirm.yaml에서 필수로 지정한 검사는 해당 프로젝트에서 무시하거나 비활성화할 수 없다. shellfirm policy show로 필수 검사를 확인할 수 있다.

설정 편집과 조회에는 다음 명령을 사용한다.

text
shellfirm config # 대화형 편집기(TUI)
shellfirm config show # 현재 설정 표시
shellfirm config edit # $EDITOR에서 settings.yaml 열기(잘못된 편집은 거부)
shellfirm config reset # 현재 파일을 백업한 뒤 초기화

설정 파일은 ~/.config/shellfirm/settings.yaml에 저장된다. macOS에서는 ~/Library/Application Support/shellfirm/settings.yaml을 사용한다. 잘못된 설정 파일은 오류가 있는 줄 번호와 함께 보고되며 덮어쓰지 않는다. 셸 훅은 잘못된 값에 안전한 기본값을 사용해 계속 작동한다. 자세한 내용은 설정 문서에 있다.

--log <level> 또는 SHELLFIRM_LOG=debug를 설정하면 진단 정보를 표준 오류(stderr)에 출력한다. RUST_LOG는 사용하지 않는다.

접근성

수학 문제 확인을 사용하지 않으려면 정신적 산술이 어렵거나(난산증, 스크린 리더 사용, 피로 등) 다른 확인 방식을 선호하는 경우 다음 설정을 적용한다. 그러면 Medium 명령과, 확인하도록 설정한 Low·Info 명령에는 Enter만 필요하다. 수학 문제를 요구했을 명령은 SSH, root 권한, 보호 브랜치 때문에 위험도가 높아진 경우까지 포함해 yes 입력을 요구한다.

text
shellfirm config challenge Enter
shellfirm config escalation severity --critical Yes --high Yes --medium Enter --low Enter --info Enter
shellfirm config context escalation --elevated Yes --critical Yes

다음 명령은 실제 적용될 확인 방식을 출력한다.

text
shellfirm pre-command -t -c 'git push -f origin main'

프로젝트의 .shellfirm.yaml이나 그룹·개별 검사 재정의 설정은 여전히 Math를 요구할 수 있다. shellfirm policy show와 shellfirm config show에서 해당 설정을 확인할 수 있다.

일반 출력은 한 줄에 하나의 사실을 표시하는 텍스트 형식이며, 심각도는 단어로 표기한다. NO_COLOR=1(비어 있지 않은 값이면 적용되며 NO_COLOR=는 색상을 유지), CLICOLOR=0, 또는 단색 터미널(TERM=dumb, vt100, xterm-mono 등)은 확인 화면, CLI, 설정 편집기의 색상을 끈다. CLICOLOR_FORCE=1은 색상을 강제로 켠다. UTF-8이 아닌 로캘(LC_ALL=C)에서는 status, config show, policy show, audit show가 —, →, ✓ 대신 ASCII를 출력한다. 시간 제한은 없고 재시도 횟수도 무제한이다.

설정 편집기(shellfirm config)의 모든 기능은 키보드로 사용할 수 있으며 ?를 누르면 키를 확인할 수 있다. 활성 탭은 [Name]으로, 선택한 행은 >로 표시해 색상 없이도 포커스를 알아볼 수 있다. NO_COLOR를 설정하면 강조 표시에 반전 영상을 쓴다. UTF-8이 아닌 로캘(예: LC_ALL=C)에서는 기호를 ASCII로 대체한다. 텍스트 입력란에는 삽입 지점에 터미널 커서가 표시된다. 창 크기가 80x24보다 작으면 선택한 행까지 탭이 스크롤되며 최소 크기는 60x16이다. TERM=dumb에서는 편집기를 시작하지 않으므로 shellfirm config show와 shellfirm config <subcommand>를 사용한다.

알려진 제한 사항

shellfirm은 명령행을 실행 전에 읽는 안전장치이지 샌드박스가 아니며, 프로그램이 시작된 뒤 수행하는 동작은 볼 수 없다. 일부 위험 명령에는 아직 규칙이 없다.

  • 리버스 셸은 흔한 표기만 인식하며 다른 표기는 인식하지 못한다.
  • 인코딩된 텍스트나 내려받은 텍스트를 디코딩해 셸·인터프리터로 바로 전달하는 동작은 항상 추적되는 것이 아니다.
  • curl, aws, gh로 키, 토큰, 자격 증명 파일을 다른 호스트에 보내는 행위는 일반적으로 감지하지 못한다.
  • 개인 키나 자격 증명 파일을 읽는 행위는 Medium으로 표시한다. Claude Code의 Read 도구를 통한 읽기도 포함한다. 하지만 .env 파일 읽기와 ~/.ssh 목록 보기는 표시하지 않는다. 에이전트가 읽기 전에 확인을 받고 싶다면 사용자 지정 검사를 추가할 수 있다. 예시는 다음과 같다.
text
test: '^(?:cat|less|head|tail|bat)\\s+(?:\\S+\\s+)*?\\S*\\.env(?:\\.(?:local|development|production|test))?(?:\\s|$)'
  • 토큰을 출력하지 않고 사용하는 명령(GH_TOKEN=$(gh auth token) gh api …, gh auth token | docker login --password-stdin)도 토큰을 출력하는 경우처럼 확인을 요구하므로 승인해야 한다. 검사를 AI 에이전트에만 적용할 수는 없다. config ignore를 사용하면 셸과 에이전트 모두에서 해당 검사가 꺼진다.
  • curl로 API를 호출해 파괴적인 작업을 하는 경우(예: 볼륨을 삭제하는 GraphQL 변경 요청, 클라우드 API에 보내는 DELETE 요청)는 인식하지 못한다.
  • root 계정을 잠그는 경우를 제외한 접근 차단 명령은 검사하지 않는다. 다른 사용자를 잠그거나 삭제하는 명령, SSH 포트를 막는 방화벽 규칙 등이 여기에 포함된다.
  • git -c에 프로그램 값(core.pager, core.editor)을 전달하면 Medium으로 분류한다. -c diff.external=과 -c credential.helper=는 일치하지 않는다. 이후 Git 명령에서 다른 파일의 설정이나 훅을 실행하게 하는 git config include.path …와 git config init.templateDir …도 검사하지 않는다.
  • ssh host <<EOF … EOF, echo 'rm -rf /srv' | sudo bash, ssh host < script.sh처럼 표준 입력으로 원격 셸이나 root 셸에 전달하는 명령은 파일이나 다른 프로그램에서 입력이 오는 경우 내용을 읽지 않는다.
  • 오탐 사례도 있다. 방화벽을 안전하게 강화하는 ufw allow OpenSSH && ufw default deny incoming도 잠금 위험으로 취급해 High로 분류하며 에이전트에서는 거부한다. 두 명령을 하나씩 실행하거나 승인해야 한다.

Nushell 내장 명령은 차단되지 않는다. 자세한 내용은 셸 지원 안내에 있다. 누락된 사례는 이슈로 보고할 수 있으며, 검사 카탈로그에서 적용 범위를 확인할 수 있다.

제거와 문서

셸 설정 파일에서 훅을 제거하고, 연결한 AI 도구 설정도 제거한 뒤 설치 상태를 확인한다.

text
shellfirm init --uninstall # 셸 rc 파일에서 훅 제거
shellfirm connect claude-code --uninstall # AI 도구를 연결했다면 제거
shellfirm status # 어떤 셸에도 "installed"가 표시되지 않는지 확인

status는 rc 파일을 읽기 때문에 즉시 not installed를 표시한다. 이미 훅을 불러온 셸에는 셸을 다시 시작할 때까지 훅이 남는다. eval "$(shellfirm init bash)" 등으로 훅을 직접 추가했다면 rc 파일에서 해당 줄을 삭제한다. 실행 중인 셸은 종료할 때까지 훅을 유지하므로 열린 셸도 다시 시작해야 한다.

설치 방식에 따라 바이너리를 제거한다.

text
npm uninstall -g @shellfirm/cli
brew uninstall shellfirm
cargo uninstall shellfirm

더 이상 필요하지 않다면 설정, 사용자 지정 검사, 감사 로그도 삭제할 수 있다.

text
rm -rf ~/.config/shellfirm

macOS에서는 ~/Library/Application Support/shellfirm을 삭제한다.

문서에는 설정(설정 파일, CLI, 사용자 지정 검사), 팀 정책(.shellfirm.yaml 프로젝트 규칙), 감사 로그(기록 형식, 필터, 로그 회전), 검사 카탈로그(그룹별 기본 검사), 기본 검사 패턴 추가 안내가 있다. 추가 가이드는 shellfirm.vercel.app에서 볼 수 있다.

기여를 환영한다. CONTRIBUTING.md에는 빌드, 테스트, 풀 리퀘스트 체크리스트가 있으며, 검사 패턴 추가 문서는 새 검사를 추가하는 방법을 설명한다. 우회 방법을 찾았다면 공개 이슈 대신 비공개로 보고해야 하며, 안내는 SECURITY.md에 있다. 라이선스는 Apache-2.0이다.

부록: 원문 예시

text
% git push origin main --force ============ RISKY COMMAND DETECTED ============ Command: git push origin main --force Check: git:force_push Severity: HIGH Blast radius: [PROJECT] — Force-pushes to origin/main Context: branch=main, target=main Description: This force-pushes and can overwrite remote history. Alternative: git push --force-with-lease (Checks that your local ref is up-to-date before force pushing, preventing accidental overwrites of others' work.) Escalated to Yes (from Math): protected branch