# Git에서 --end-of-options가 필요한 이유

> Clean Markdown view of GeekNews topic #31698. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=31698](https://news.hada.io/topic?id=31698)
- GeekNews Markdown: [https://news.hada.io/topic/31698.md](https://news.hada.io/topic/31698.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-07-23T00:03:59+09:00
- Updated: 2026-07-23T00:03:59+09:00
- Original source: [nesbitt.io](https://nesbitt.io/2026/07/21/end-of-options.html)
- Points: 1
- Comments: 1

## Topic Body

- Git의 `--`는 일반적인 옵션 종료자가 아니라 **리비전과 경로 명세를 구분**하므로, 신뢰할 수 없는 리비전을 안전하게 전달하려면 Git 2.24.0부터 지원된 `--end-of-options`가 필요함
- `git log --end-of-options "$rev" -- "$path"`에서 앞 표식은 **옵션과 리비전**, 뒤의 `--`는 리비전과 경로를 구분하며 서로 대체할 수 없음
- 셸 없이 `argv` 배열을 직접 실행해도 대시로 시작하는 입력이 `--upload-pack`, `core.sshCommand`, `ProxyCommand` 같은 옵션으로 해석되면 **CWE-88 인자 주입**이 발생할 수 있음
- 조사한 패키지 관리자 19개 중 17개가 기본 또는 유일한 방식으로 Git 바이너리를 실행하지만, `--end-of-options`를 사용하는 도구는 **Go의 `cmd/go` 하나뿐**이었음
- 근본 대응에는 서브명령에 따라 Git 최소 버전을 2.24.0·2.30.0·2.43.1로 높여야 하는 호환성 비용이 있으며, Git 라이브러리는 인자 주입 경계를 없애는 대신 upstream의 체크아웃 안전성 수정을 직접 추적해야 함

---

### `--`와 `--end-of-options`의 차이
- 일반적인 Unix 도구에서 `--`는 옵션 해석의 끝을 표시하므로 `rm -- -f`는 `-f`를 강제 삭제 옵션이 아닌 파일명으로 처리함
- Git은 일찍부터 `--`를 **리비전과 경로 명세(pathspec)의 구분자**로 사용함
  - `git log foo`는 `foo`라는 브랜치와 파일 중 무엇을 뜻하는지 모호함
  - `git log main -- README.md`는 `main`의 커밋 중 `README.md`를 건드린 커밋을 뜻함
- 이 설계 때문에 리비전 위치에는 옵션 종료 표식이 없었고, `git log "$rev"`에서 `$rev`가 대시로 시작하면 Git이 옵션으로 해석함
- [`--end-of-options`를 도입한 커밋](https://github.com/git/git/commit/19e8789b236dfe33667747d5523d6689bb59b5ef)은 기존 `--`가 이미 리비전과 경로 명세를 나누고 있어 옵션과 리비전을 구분할 별도 표식이 필요하다고 밝힘
- `--end-of-options`는 [gitcli(7)](https://git-scm.com/docs/gitcli)에 문서화됐으며, **Git 2.24.0**이 출시된 2019년 11월 추가됨

### 명령별 올바른 사용법
- `git clone -- "$url"`은 `clone`이 POSIX 관례를 따르므로 URL 앞의 `--`가 옵션 해석을 종료함
- `git checkout "$ref" --`의 뒤쪽 `--`는 `$ref`를 파일명이 아닌 리비전으로 표시하지만, `$ref`가 먼저 옵션으로 해석되는 것은 막지 못함
- 신뢰할 수 없는 리비전과 경로를 함께 안전하게 전달하려면 `git log --end-of-options "$rev" -- "$path"`처럼 **두 표식**을 모두 사용해야 함
  - `--end-of-options`는 옵션과 리비전을 구분함
  - `--`는 리비전과 경로를 나눔
- 두 표식을 서로 바꿔 쓸 수 있다고 취급하면 대시로 시작하는 입력을 차단하지 못함

### 서브명령마다 다른 지원 시점
- `--end-of-options` 지원은 모든 Git 명령에 한꺼번에 적용되지 않고 **서브명령별로 추가**됨
- `git rev-parse`는 자체 인자 파서를 사용해 최초 도입보다 1년 늦은 [Git 2.30.0에서 지원](https://github.com/git/git/commit/3a1f91cfd9)하기 시작함
- `git checkout`과 `git reset`은 자체적으로 `--`를 해석하며, 초기 구현이 `--end-of-options`를 인자 목록에 남겨 두는 바람에 이를 거부함
  - 이 문제는 [Git 2.43.1](https://github.com/git/git/commit/9385174627)이 출시된 **2024년 2월**에 해결됨

### 셸 없이도 발생하는 인자 주입
- Git, Mercurial, SSH에는 호출자가 지정한 명령을 실행하는 옵션이 공식 기능으로 존재함
  - `git clone --upload-pack=&lt;cmd&gt;`는 서버 측 바이너리를 지정함
  - 모든 Git 호출의 `-c core.sshCommand=&lt;cmd&gt;`는 연결 명령을 바꿈
  - Mercurial의 `--config=alias.&lt;subcmd&gt;=!&lt;shell&gt;`은 실행할 서브명령을 임의의 셸 스크립트로 재정의함
  - SSH의 `-oProxyCommand=&lt;cmd&gt;`는 프록시 명령을 지정함
- 래퍼 프로그램이 신뢰할 수 없는 문자열을 인자 목록에 넣으면 이러한 기능이 **공격 수단**으로 바뀔 수 있음
- 이 실패 유형은 [CWE-88](https://cwe.mitre.org/data/definitions/88.html) **인자 주입(argument injection)** 에 해당하며 셸 명령 주입과 다름
  - 프로그램이 `system()` 대신 `argv` 배열과 `exec`를 사용해도 발생함
  - 배열은 손상 없이 Git에 전달되지만, Git이 대시로 시작하는 인자를 옵션으로 해석함
- [`docker build`의 CVE-2019-13139](https://staaldraad.github.io/post/2019-07-16-cve-2019-13139-docker-build/)는 Go의 `os/exec`와 `argv` 배열을 사용하고 셸을 거치지 않은 사례임
  - Git 컨텍스트 URL의 `#ref:dir` 조각이 `git fetch origin &lt;ref&gt;`에 전달되면서 `&lt;ref&gt;`가 `--upload-pack=&lt;cmd&gt;`로 해석됨

### 여러 버전 관리 시스템에서 반복된 취약점
- 2017년 8월 같은 날 네 개 버전 관리 시스템에서 동일한 패턴이 공개됨
  - Git의 [CVE-2017-1000117](https://nvd.nist.gov/vuln/detail/CVE-2017-1000117)
  - Mercurial의 [CVE-2017-1000116](https://nvd.nist.gov/vuln/detail/CVE-2017-1000116)
  - Subversion의 [CVE-2017-9800](https://subversion.apache.org/security/CVE-2017-9800-advisory.txt)
  - CVS의 [CVE-2017-12836](https://nvd.nist.gov/vuln/detail/CVE-2017-12836)
- 네 시스템 모두 URL의 호스트명을 SSH 인자로 전달했고, `-oProxyCommand=`로 시작하는 호스트명이 **SSH 옵션**으로 처리됨
- [Phabricator 사후 분석](https://web.archive.org/web/20251216145944/https://secure.phabricator.com/T12961)에 따르면 당시 활발히 유지되던 세 도구 중 Subversion만 호스트명 앞에 `--`를 추가함
  - Git과 Mercurial은 모든 SSH 구현이 `--`를 지원하지 않는다는 이유도 있어 호스트명 형식을 검증함
- `--`가 빠진 코드도 인자가 대시로 시작하기 전까지는 정상적으로 보이고 작동하므로, 이 메커니즘은 **기본적으로 안전하지 않음**

### 패키지 관리자가 노출되는 경로
- 패키지 관리자는 매니페스트, 잠금 파일, 전이 의존성 메타데이터에서 Git URL이나 ref를 받아 하위 프로세스에 전달함
  - Gemfile의 `gem 'foo', git: '...'`
  - `package.json`의 `github:user/repo#ref`
  - `pyproject.toml`, `Cargo.toml`, `mix.exs`, `Package.swift`, `pubspec.yaml`, `conanfile.py`, `go.mod`의 동등한 설정
- 2026년 7월 HEAD 기준 조사한 **19개 패키지 관리자 중 17개**가 Git 바이너리를 기본 또는 유일한 경로로 실행함
- 나머지 두 개는 라이브러리를 기본으로 사용함
  - Cargo는 [libgit2](https://libgit2.org/)를 사용하며 `net.git-fetch-with-cli`를 활성화하면 Git 프로세스를 실행함
  - Poetry는 [1.2.0부터 dulwich로 전환](https://github.com/python-poetry/poetry/commit/ad1b0938)했고 `system-git-client` 설정으로 시스템 Git을 사용할 수 있음
- Nix는 로컬 저장소를 읽을 때 libgit2를 쓰지만, [libgit2가 git-credential 도우미를 지원하지 않아](https://github.com/NixOS/nix/blob/3aff4dc5edf30998d64eec024de186ac2d6fb5ea/src/libfetchers/git-utils.cc#L639-L641) fetch에는 Git 프로세스를 실행함
- 조사 대상은 Bundler, Cargo, CocoaPods, Composer, Conan, Go, Helm, Homebrew, Mix, Nix, npm, pip, pnpm, Poetry, Pub, SwiftPM, uv, vcpkg, Yarn임

### 패키지 관리자에서 확인된 CVE
- 이 유형으로 공개된 패키지 관리자 취약점에는 다음 사례가 있음
  - Bundler의 [CVE-2021-43809](https://github.com/advisories/GHSA-fj7f-vq84-fh43)
  - Composer의 [CVE-2021-29472](https://github.com/composer/composer/security/advisories/GHSA-h5h8-pc6h-jvvx), [CVE-2022-24828](https://github.com/composer/composer/security/advisories/GHSA-x7cr-6qr6-2hh6)
  - Poetry의 [CVE-2022-36069](https://github.com/advisories/GHSA-9xgj-fcgf-x6mw)
  - pip의 [CVE-2023-5752](https://github.com/advisories/GHSA-mq26-g339-26xf)
  - CocoaPods의 [CVE-2022-21223](https://github.com/advisories/GHSA-g397-v4w5-4m79), [CVE-2022-24440](https://github.com/advisories/GHSA-7627-mp87-jf6q)
  - Go의 [CVE-2025-68119](https://pkg.go.dev/vuln/GO-2026-4338)
- 여러 2022년 취약점을 발견한 Snyk는 [Git과 Mercurial의 인자 주입 연구](https://snyk.io/blog/argument-injection-when-using-git-and-mercurial/)를 공개함
- Sonar는 바이너리별 [위험한 옵션 목록](https://sonarsource.github.io/argument-injection-vectors/)을 관리함

### 실제 방어 현황과 Go의 수정
- Git 프로세스를 실행하는 17개 패키지 관리자 중 `--end-of-options`를 사용한 도구는 **Go의 `cmd/go` 하나**였음
- Go는 2019년 6월 일반적인 방어 강화 차원에서 [저장소 URL 앞에 `--`를 추가](https://github.com/golang/go/commit/55d31e16c1)함
- 2026년 1월 `--`만으로는 충분하지 않다는 사실이 드러나 CVE-2025-68119 수정으로 [`--end-of-options`를 전반적으로 추가](https://github.com/golang/go/commit/94a1296a457387d1fd6eca1a9bcd44e89bdd9d55)함
- 같은 수정에는 `HGPLAIN=+strictflags`도 포함됨
  - 이 설정은 [Mercurial 4.4.2](https://wiki.mercurial-scm.org/WhatsNew/Archive#Mercurial_4.4.2_.282017-12-01.29)가 출시된 2017년부터 Mercurial의 초기 옵션 해석을 제한함
- Go 수정 커밋은 같은 문제가 재도입되기 어렵도록 더 구조적인 변경이 필요할 수 있지만, 우선 현재 문제를 해결함

### 대부분의 방어는 취약점 공개 뒤 추가
- 나머지 패키지 관리자들은 인자 목록을 보호하더라도 주로 `--` 또는 입력의 **선행 대시 거부**를 사용함
- Bundler의 `git clone` URL 앞 `--`는 [CVE-2021-43809 패치](https://github.com/ruby/rubygems/commit/90b1ed8b9f)로 추가됨
- cocoapods-downloader의 선행 대시 거부는 CVE-2022-21223 공개 시기와 맞물린 2022년 3월 열흘 동안 [세 개 커밋으로 적용](https://github.com/CocoaPods/cocoapods-downloader/commit/35340f4b)됨
- Poetry의 방어는 [2021년 9월 추가](https://github.com/python-poetry/poetry-core/commit/cc84be6)됐고 1년 뒤 CVE가 배정됐으며, 6개월 뒤 dulwich로 전환함
- vcpkg는 예외적으로 Git 레지스트리 지원이 작성된 [첫날부터 `--`를 사용](https://github.com/microsoft/vcpkg-tool/commit/8cde8a06)함

### 최소 Git 버전이 만드는 호환성 제약
- Composer의 [CVE-2022-24828 권고문](https://blog.packagist.com/cve-2022-24828-composer-command-injection-vulnerability/)은 `--end-of-options`를 올바른 수정으로 지목하지만, 더 오래된 Git도 지원해야 하므로 대시로 시작하는 브랜치명을 거부하는 방식을 택함
- vcpkg의 Git 통합은 최소 버전을 **Git 2.7.4**로 명시하며, Homebrew의 Linux용 `HOMEBREW_MINIMUM_GIT_VERSION`은 [2018년에 설정된 2.7.0](https://github.com/Homebrew/brew/commit/7aa9956934)임
- Git 2.14.3을 제공한 Amazon Linux 2가 2026년 6월 수명을 종료하면서 이러한 하한선이 추적하던 배포판들이 이제야 지원 범위에서 빠지고 있음
- Ubuntu의 장기 지원 상태도 일괄적인 전환을 어렵게 함
  - Ubuntu 18.04는 Git 2.17.0을 제공하며 2028년까지 연장 지원됨
  - Ubuntu 20.04는 Git 2.25.1을 제공하며 2030년까지 연장 지원됨
  - Git 2.25.1은 `git fetch`의 `--end-of-options`를 받아들이지만 `git rev-parse`에서는 거부함
- `--end-of-options`에 의존하려면 대부분의 서브명령은 **Git 2.24.0**, `rev-parse`는 2.30.0, `checkout`과 `reset`은 2.43.1을 최소 버전으로 요구해야 함
- 최소 버전을 높이면 배포판에 포함된 오래된 Git을 사용하는 이용자를 지원할 수 없게 됨

### 프로세스 실행 대신 Git 라이브러리 사용
- libgit2, [gitoxide](https://github.com/GitoxideLabs/gitoxide), [go-git](https://github.com/go-git/go-git), [JGit](https://github.com/eclipse-jgit/jgit), dulwich는 프로세스 내부에서 clone과 fetch에 필요한 Git 전송 프로토콜을 구현함
- 별도의 `argv` 경계가 없어 **인자 목록에 주입할 대상 자체가 없음**
- [Jujutsu](https://github.com/jj-vcs/jj)는 Git 연동에 gitoxide를 사용하며 인자 주입 유형의 공개 CVE가 없음
  - 현재까지의 권고 두 건은 경로 순회와 라이브러리에서 물려받은 SHA-1 충돌 검사 누락임
- go-git의 [CVE-2025-21613](https://github.com/go-git/go-git/security/advisories/GHSA-v725-9546-7q7m)은 `file://` 전송에 한정됨
  - 이 경로는 go-git에서 Git 바이너리를 실행하는 유일한 코드 경로임
- 자체 Git 구현을 포함하면 upstream Git이 내놓는 모든 체크아웃 안전성 수정을 추적해야 하며, libgit2와 JGit 모두 이와 관련된 수정이 반복된 적이 있음
- 이 비용은 실제로 존재하지만, 각 호출 지점에서 영구적으로 인자 검사를 기억하는 대신 **구체적인 upstream 패치 흐름**을 적용하는 문제로 바뀜

### Homebrew에 제안한 변경 범위
- [Homebrew PR](https://github.com/Homebrew/brew/pull/23223)은 최소 Git 버전을 **2.30.0**으로 높이고 다음 위치에 `--end-of-options`를 추가함
  - `clone`, `remote set-url`, `ls-remote`의 URL 앞
  - `rev-parse`의 ref 앞
- `checkout`과 `reset` 호출은 변경하지 않음
  - 두 명령까지 보호하려면 2024년 2월 출시된 Git 2.43.1이 필요함
  - 이 버전은 현재 지원되는 여러 배포판이 제공하는 Git보다 최신임

## Comments



### Comment 62228

- Author: neo
- Created: 2026-07-23T00:04:00+09:00
- Points: 1

###### [Lobste.rs 의견들](https://lobste.rs/s/cxz7vd/git_end_options) 
- 요즘 점점 **jj에 빠지고 있음**. 이런 Git의 난잡함과 비교하면 마음이 편해질 정도이며, 아직 익히는 중이지만 의도를 잘 반영하고 원하는 작업법도 쉽게 파악할 수 있음  
  특히 한 `jj` 명령에서 배운 지식이 다른 명령에도 자연스럽게 적용됨. [`git log` 문서](https://git-scm.com/docs/git-log)는 출력 옵션별 플래그가 폭증한 데다 커밋 범위 문법인 “특수 표기법”과 추가 필터 플래그까지 뒤섞여 있고, 다른 Git 명령으로 전이되는 지식도 적음  
  반면 [`jj log` 문서](https://docs.jj-vcs.dev/latest/cli-reference/#jj-log)는 한 페이지에도 여백이 남을 만큼 간결함. Git의 잡동사니식 복잡성을 **리비전 집합·파일 집합·출력 템플릿 DSL** 세 가지로 대체했고, 이들이 jj 전반에서 일관되게 쓰여 훨씬 단순하고 조합 가능하며 직관적임. Git이 20년간 군더더기를 쌓아왔다는 점은 감안해야 하지만, jj는 이를 피하기에 훨씬 유리해 보임
- 명령어에서 사용자 제공 인수 앞에 보통 `--`가 필요하다는 건 알지만, 여기에 **독자적인 변형 규칙**까지 요구하면 지나치게 위험한 함정이 됨
- 도구를 지나치게 복잡하게 만든 정직한 결과임. Git은 새로 등장한 **바사호** 같아 보임
- 원래 파일 인수의 모호성을 해소하려고 `--`를 택한 이유가 궁금함. 파일을 명시적 인수로 받는 단순한 방식 대신 이를 선택한 데 겉으로 드러나지 않는 **설계상 절충**이 있었는지 의문임
  - 이미 확립된 **UNIX 옵션 파싱 관례**를 전혀 다른 용도로 사용한 건 예상대로 어리석고, 지극히 Git다운 선택임
- UNIX의 **모든 것은 텍스트** 철학이 또 문제를 일으킴. 명령줄은 구조화된 데이터의 완벽한 예인데도, 이 구덩이에서 벗어나기 위한 공동 조율 비용이 너무 큰 듯함  
  1990년대 Tcl과 Scheme의 논쟁을 돌아보면 이 경우에는 “더 나쁜 것이 더 낫다”가 맞았을지도 모름. JSON과 XML 등을 포함한 S-표현식 계열이 UNIX 생태계에 거의 자리 잡지 못한 반면, Tcl은 문자열 안에 하위 언어를 계층화하는 비교적 원칙적인 접근법을 갖추고 있었음
