
안전한 기본 설정 — 브랜치 보호, Dependabot, 비밀 스캐닝, 사전 보안 스캐닝 — 이 자동으로 적용된 GitHub 저장소를 생성하는 Python CLI입니다.
안전한 기본값이 자동으로 적용된 GitHub 리포지토리를 생성합니다. 생성 후 5분 동안의 설정 체크리스트를 단일 명령어로 대체합니다.``` gh-safe-repo create <owner/repo>
브랜치 보호, 불변 태그, Dependabot, 제한된 Actions 권한, 푸시 보호가 포함된 시크릿 스캐닝, 위키 및 프로젝트 비활성화 — 첫 줄의 코드를 작성하기 전에 모두 구성됩니다.
gh-safe-repo는 활발히 개발 중입니다. 보안 기본값으로 새 리포지토리를 만드는 사용 사례에는 잘 작동합니다. 사용자 기대에 가장 잘 맞도록 CLI 옵션을 다듬는 중입니다. 릴리스를 하고 CI/CD를 확정할 때까지 호환되지 않는 변경이 있을 수 있습니다. ✌️
---
## 목차
- [이유](#why)
- [변경 사항](#what-it-changes)
- [요구 사항](#requirements)
- [설치](#installation)
- [빠른 시작](#quick-start)
- [CLI 참조](#cli-reference)
- [드라이 런 / 계획 출력](#dry-run--plan-output)
- [수정 모드 (기존 리포지토리 감사)](#fix-mode-audit-existing-repos)
- [리포지토리 미러링 (`--from`)](#mirroring-repos---from)
- [로컬 디렉토리에서 리포지토리 생성 (`--local`)](#creating-a-repo-from-a-local-directory---local)
- [사전 실행 보안 스캐너](#pre-flight-security-scanner)
- [독립 실행형 스캔](#standalone-scan)
- [오탐지 억제](#suppressing-false-positives)
- [구성](#configuration)
- [GitHub 요금제 제한 사항](#github-plan-limitations)
- [작동 방식](#how-it-works)
- [개발](#development)
---
## 이유
GitHub의 기본 리포지토리 설정은 보안이 아닌 검색 가능성과 유연성에 최적화되어 있습니다. 모든 새 리포지토리에는 다음이 포함됩니다:
- 위키 및 프로젝트 활성화 (사용하지 않더라도 공격 표면)
- 병합 커밋 허용 (지저분한 기록이지만 주요 문제는 아님)
- 브랜치 보호 없음 (쓰기 권한이 있는 사람은 누구나 `main`에 직접 푸시 가능)
- Dependabot 알림 없음
- 리포지토리에 대한 쓰기 권한이 있는 GitHub Actions
- 풀 리퀘스트 승인을 허용하는 Actions
이 모든 것을 수동으로 수정하는 데는 리포지토리당 몇 분이 소요되며 쉽게 잊힙니다. `gh-safe-repo`는 한 번에 독단적이지만 실용적인 기본값 세트를 적용하며, 변경되기 전에 정확히 무엇이 변경될지 알 수 있도록 계획 미리보기를 제공합니다.
---
## 변경 사항
### 리포지토리 설정
| 설정 | GitHub 기본값 | 안전 기본값 | 비고 |
|---|---|---|---|
| 공개 여부 | 공개 | **비공개** | 재정의하려면 `--public` 전달 |
| 위키 | 활성화 | **비활성화** | |
| 프로젝트 | 활성화 | **비활성화** | |
| 이슈 | 활성화 | 활성화 | |
| 병합 시 브랜치 삭제 | 꺼짐 | 꺼짐 | 자동 정리를 위해 구성에서 `true`로 설정 |
| 병합 커밋 허용 | 켜짐 | 켜짐 | 스쿼시 전용을 위해 구성에서 `false`로 설정 |
| 스쿼시 병합 허용 | 켜짐 | 켜짐 | |
| 리베이스 병합 허용 | 켜짐 | 켜짐 | |
### GitHub Actions
| 설정 | GitHub 기본값 | 안전 기본값 |
|---|---|---|
| 허용된 Actions | 모두 | **선택됨** (GitHub 소유 + 확인된 작성자, 사용자 지정 가능) |
| 기본 워크플로 권한 | 읽기/쓰기 | **읽기 전용** |
| Actions이 PR을 승인할 수 있음 | 예 | **아니요** |
| SHA 고정 요구 | 아니요 | **예** (워크플로는 가변 태그가 아닌 커밋 SHA에 Actions을 고정해야 함) |
| 포크 PR 승인 정책 | GitHub에 처음 기여한 사용자 | **모든 외부 기여자** — 포크 PR 워크플로가 CI를 실행하기 전에 승인 필요. 옵션: 새로운 GitHub 계정만 (GitHub 기본값), 리포지토리에 처음 기여한 사용자, 또는 모든 포크 PR (가장 안전) |
### 브랜치 보호 (공개 리포지토리 또는 유료 요금제의 모든 리포지토리)
| 규칙 | 값 |
|---|---|
| 병합 전 풀 리퀘스트 필요 | 예 |
| 요구되는 승인 리뷰 | 1 |
| 푸시 시 오래된 리뷰 무시 | 예 |
| 대화 해결 필요 | 예 |
| 강제 푸시 허용 | 아니요 |
| 브랜치 삭제 허용 | 아니요 |
| 관리자에게 적용 | 아니요 (소유자가 도구로 푸시할 수 있도록 허용) |
브랜치 보호는 기본적으로 **Rulesets API**를 통해 적용됩니다 (`use_rulesets = true`):
단일 `gh-safe-repo defaults` 규칙 세트가 구성된 모든 브랜치를 포함하며
"관리자가 우회 가능"을 기존의 `enforce_admins` 플래그 대신 우회 작업자를 통해 표현합니다.
`use_rulesets = false`로 설정하면 기존의 브랜치별 클래식 경로가 사용됩니다 (한 릴리스 주기 동안 유지).
**클래식 보호에서 기존 리포지토리 마이그레이션:** `fix`가 리포지토리에서 클래식 브랜치 보호를 발견하면
`--migrate-branch-protection`을 전달하지 않는 한 이를 규칙 세트로 변환하지 않습니다.
클래식 전용 규칙은 이 도구가 구축하는 규칙 세트에 해당하는 기능이 없으며 그렇지 않으면 자동으로 삭제됩니다 — 알려진 차이점:
- `required_status_checks` — 필수 CI 검사는 규칙 세트 본문에 모델링되지 않습니다.
- `restrictions` (사용자/팀에 의한 푸시 제한) — 규칙 세트는 우회 작업자를 통해 이를 다르게 모델링하므로 1:1 매핑이 아닙니다.
- 브랜치별 차이 — 단일 공유 조건 규칙 세트는 `master`와 `main`에 대해 다른 규칙을 표현할 수 없습니다.
플래그와 함께 `fix`는 규칙 세트를 생성/업데이트한 다음 각 브랜치의 클래식 보호를 삭제하여 두 레이어가 중첩되지 않도록 합니다.
### 태그 보호 (공개 리포지토리 또는 유료 요금제의 모든 리포지토리)
태그 보호는 모든 태그 (기본적으로 `*`, `protected_tags`를 통해 구성 가능)를 대상으로 하는 GitHub Ruleset을 생성합니다. 다음 규칙이 적용됩니다:
| 규칙 집합 규칙 | 적용됨? | 비고 |
|---|---|---|
| 생성 제한 | 아니요 | |
| **업데이트 제한** | **예** | 태그 재작성/강제 푸시 방지 |
| **삭제 제한** | **예** | 태그의 `git push --delete` 방지 |
| 선형 기록 필요 | 아니요 | |
| 배포 성공 필요 | 아니요 | |
| 서명된 커밋 필요 | 아니요 | |
| 상태 검사 통과 필요 | 아니요 | |
| 강제 푸시 차단 | 아니요 | |
리포지토리 관리자는 우회 목록에 있습니다 (브랜치 보호 `enforce_admins = false` 기본값과 일관됨). 공개 리포지토리 또는 유료 GitHub 요금제에서만 작동합니다 (브랜치 보호와 동일한 제한). 무료 요금제 비공개 리포지토리의 경우 계획 출력에서 이 단계가 건너뜁니다.
### 보안
| 기능 | 동작 |
|---|---|
| Dependabot 알림 | 활성화 (공개 리포지토리 / 유료 요금제) |
| Dependabot 보안 업데이트 | 활성화 (취약한 종속성에 대해 PR 자동 생성) |
| 시크릿 스캐닝 | 공개 리포지토리에서는 자동, 비공개 유료 요금제에서는 활성화 |
| 푸시 보호 | 활성화 (지원되는 시크릿을 포함하는 커밋 차단) |
| 비공개 취약점 보고 | 활성화 (보안 연구원이 비공개로 보고 가능) |
| 종속성 그래프 | 공개 리포지토리에서는 자동, 비공개의 경우 REST API 없음 (UI만) |
---
## 요구 사항
- Python 3.8+
- [`gh` CLI](https://cli.github.com/) 설치 및 인증 (`gh auth login`) **또는** 환경에 `GITHUB_TOKEN` 설정
- `--local` / `--from` (코드를 푸시하거나 복제)의 경우: 일반 git 자격 증명이 설정되어 있어야 합니다 — SSH 키가 `ssh-agent`에 로드되거나 (`gh config get git_protocol`이 `ssh`인 경우) HTTPS 자격 증명 도우미 (`gh auth setup-git`이 자동으로 구성). git 푸시에는 OAuth 토큰이 **사용되지 않으므로** 워크플로 파일 (`.github/workflows/*`)은 OAuth `workflow` 범위 없이 푸시됩니다.
- 소스에서 설치하려면 [`uv`](https://docs.astral.sh/uv/) (권장)
- `truffleHog` v3 (선택 사항 — 사전 실행 스캐너에서 사용, PATH에서 자동 감지되거나 podman/docker를 통해 실행, 둘 다 사용할 수 없으면 정규식으로 대체)
---
## 설치
### uv를 사용한 소스 설치 (권장)```bash
git clone https://github.com/your-username/gh-safe-repo
cd gh-safe-repo
uv tool install .
이렇게 하면 gh-safe-repo가 uv의 도구 환경에 설치되고 PATH에 추가됩니다.
git clone https://github.com/your-username/gh-safe-repo cd gh-safe-repo uv sync # creates .venv ./gh-safe-repo create <owner/repo>
### 확인```bash
gh-safe-repo --help
gh-safe-repo create <owner/repo>
gh-safe-repo create <owner/repo> --dry-run
gh-safe-repo create <owner/repo> --public
gh-safe-repo create <owner/repo> --from <owner/source>
gh-safe-repo create <owner/pub> --from <owner/priv> --public
gh-safe-repo create <owner/repo> --local ~/projects/myapp
gh-safe-repo create <owner/repo> --local ~/projects/myapp --public
gh-safe-repo fix <owner/repo>
gh-safe-repo fix <owner/repo> --dry-run
gh-safe-repo fix <owner/repo> --yes
gh-safe-repo scan . gh-safe-repo scan ~/projects/myapp
## CLI 참조```
gh-safe-repo create <owner/repo> [OPTIONS]
gh-safe-repo fix <owner/repo> [OPTIONS]
gh-safe-repo scan <path> [OPTIONS]
GitHub과 상호작용하는 모든 명령어는 owner/repo 형식(예: myuser/my-repo)을 필요로 합니다. create의 경우, 소유자는 인증된 GitHub 계정을 기준으로 검증되어 다중 계정 시스템에서의 실수를 방지합니다. fix의 경우에는 대상 리포지토리에 대한 관리자 권한이 필요하므로, 조직 또는 다른 계정이 소유한 리포지토리 중 관리자 접근 권한이 있는 경우에도 수정할 수 있습니다.
create — 새로운 리포지토리 생성--local/--from 없이 create만 실행하면 리포지토리가 초기화되어 기본 브랜치가 브랜치 보호를 위해 존재하게 되며, 그 후 자동 생성된 README.md가 제거되어 새 리포지토리가 깨끗하게 시작됩니다. README를 유지하려면 설정에서 auto_init = true로 설정하십시오. --local/--from을 사용하면 자체 히스토리를 푸시하며 README를 생성하지 않습니다.
fix — 기존 리포지토리 감사 및 수정scan — 로컬 비밀 스캐닝| 옵션 | 설명 |
|---|---|
--config [PATH] | 설정 파일 경로; --config만 지정하면 기본 내장 설정만 사용합니다. |
--debug | 스캐너 세부 정보를 표시합니다. |
종료 코드는 심각한 결과가 없으면 0, 심각한 결과가 발견되면 1입니다.
--dry-run은 gh-safe-repo가 수행할 작업을 변경 사항이나 API 호출 없이 정확히 보여줍니다. 실제 실행 전에 사용하십시오. --json과 함께 사용하면 기계가 읽을 수 있는 계획 출력을 얻을 수 있습니다:```bash
gh-safe-repo create <owner/repo> --dry-run --json
gh-safe-repo fix <owner/repo> --dry-run --json
When `--json`이 활성화되면, 계획은 stdout에 JSON 객체로 기록되고, 다른 모든 메시지(진행 상황, 경고, "Dry run" 푸터)는 stderr로 전송되므로, 파이핑이나 스크립팅을 위한 출력이 깔끔해집니다.```
$ gh-safe-repo create <owner/repo> --dry-run
Plan for my-project (private)
Category Action Setting Value
──────────────────────────────────────────────────────────────────
Repository ADD repository my-project (private)
Repository ADD has_wiki false
Repository ADD has_projects false
Actions ADD default_workflow_permissions read
Actions ADD can_approve_pull_request_reviews false
Branch Protection SKIP branch_protection Not available for private repos on free plan
Security SKIP dependabot_alerts Not available for private repos on free plan
1 setting skipped (GitHub plan limitation).
Dry run — no changes made.
동작 색상:
| 동작 | 의미 |
|---|---|
ADD (녹색) |
JSON 출력 (--json):```json
{
"changes": [
{ "type": "add", "category": "repository", "key": "has_wiki", "old": null, "new": false, "reason": null },
{ "type": "skip", "category": "branch_protection", "key": "branch_protection", "old": null, "new": null, "reason": "Not available for private repos on free plan" }
],
"summary": { "add": 5, "skip": 2 }
}
`summary`는 플랜에 존재하는 유형만 포함합니다. 소비자는 네 가지 키가 모두 존재한다고 가정하기보다 `.get("delete", 0)` 등을 사용해야 합니다.
---
## 수정 모드 (기존 리포지토리 감사)
`fix`는 기존 리포지토리의 현재 설정을 안전 기본값과 비교하고 필요한 수정 사항을 적용합니다. 비밀 검사는 수행하지 않습니다. `fix`는 순수하게 리포지토리 설정에 관한 것입니다.```bash
# See what's out of compliance
gh-safe-repo fix <owner/repo> --dry-run
# Apply missing safe defaults
gh-safe-repo fix <owner/repo>
# Apply without confirmation prompt (scripting/batch use)
gh-safe-repo fix <owner/repo> --yes
수정 모드:
UPDATE, 이미 원하는 값인 설정에는 SKIP을 표시하는 계획 테이블을 보여줍니다 (무효 작업 감지 — 아무것도 변경하지 않는 API 호출을 수행하지 않습니다).--yes로 건너뛰기).실제 변경 사항만 적용됩니다 — 이미 원하는 값인 설정은 SKIP으로 표시되며 API 호출이 생성되지 않습니다.
--from)--from은 기존 레포지토리를 안전 기본값이 적용된 새 레포지토리로 미러링합니다. 개인 및 공개 대상 모두에서 작동합니다:```bash
gh-safe-repo create <owner/repo> --from <owner/source>
gh-safe-repo create <owner/pub> --from <owner/priv> --public
**순서대로 발생하는 작업:**
1. `github.com`에 대한 git 자격 증명이 먼저 확인됩니다(SSH 프로브: `gh config get git_protocol`이 `ssh`인 경우; HTTPS는 신뢰됨). 따라서 누락된 키가 있으면 저장소가 생성되기 전에 빠르게 실패합니다.
2. 소스 저장소가 로컬에 클론됩니다(전체 클론, `--depth` 없음, 그래서 truffleHog가 전체 커밋 기록을 탐색할 수 있습니다).
3. [사전 보안 스캐너](#pre-flight-security-scanner)가 로컬 클론에서 실행됩니다.
4. 결과를 검토하고 확인(또는 중단)합니다.
5. 새 저장소가 생성됩니다(기본적으로 비공개, 또는 `--public`으로 공개).
6. Actions 권한 및 보안 설정이 적용됩니다(Dependabot, 비밀 스캐닝, 푸시 보호).
7. 전체 기록이 미러링됩니다: `git clone --mirror` + `git push --mirror`
8. 브랜치 및 태그 보호가 적용됩니다(코드 푸시 후이므로 대상 브랜치가 존재합니다).
스캔에서 문제가 발견되어 중단하면 코드가 GitHub에 복사되지 않습니다.
> **참고:** `--from`은 소스와 대상 모두에 대해 `owner/repo` 형식을 사용합니다.
---
## 로컬 디렉터리에서 저장소 생성 (`--local`)
`--local PATH`는 `--from`에 해당하는 로컬-투-깃허브 옵션입니다. 새 GitHub 저장소를 생성하고 로컬 git 저장소에서 코드를 푸시합니다. `PATH`는 초기화된 git 저장소(`git init` 또는 클론)여야 합니다.```bash
gh-safe-repo create <owner/repo> --local ~/projects/myapp
gh-safe-repo create <owner/repo> --local ~/projects/myapp --public
순서대로 수행되는 작업:
github.com에 대한 Git 자격 증명이 사전에 확인됩니다(gh config get git_protocol이 ssh일 때 SSH 프로브; HTTPS는 신뢰됨). 따라서 키가 없으면 저장소가 생성되기 전에 빠르게 실패합니다.push --all --tags로 푸시됩니다(모든 브랜치 및 태그).origin이 원본 로컬 저장소에 새 GitHub URL을 가리키도록 추가되고, 현재 브랜치의 업스트림 추적이 구성되어 git push 및 git pull이 추가 설정 없이 즉시 작동합니다.--local과 --from 모두 비공개 및 공개 저장소에서 작동합니다. 서로 배타적입니다.
로컬 기본 브랜치(git -C PATH symbolic-ref HEAD를 통해)는 브랜치 보호 규칙을 대상으로 하는 데 사용되므로, 보호가 main이 아니더라도 올바른 브랜치에 적용됩니다.
팁: 아무것도 생성하지 않고 결과를 검사하려면 먼저
gh-safe-repo scan PATH를 실행하세요.
스캐너는 로컬에서 실행되며 코드를 GitHub로 보내지 않습니다. 푸시 전에 독립 실행형으로 사용하거나, --from 및 --local 워크플로의 일부로 자동으로 실행됩니다.
gh-safe-repo scan .
gh-safe-repo scan ~/projects/myapp
종료 코드는 `0`으로 심각한 문제가 없음을 나타내며, `1`은 심각한 문제가 발견되었음을 의미합니다 — 따라서 다른 명령어와 깔끔하게 연동됩니다:```bash
gh-safe-repo scan . && git push
전체 [pre_flight_scan] 설정이 적용됩니다: banned_strings, max_file_size_mb, trufflehog_mode 등.
gh-safe-repo는 3단계 검색 체인을 사용하여 사용 가능한 최고의 스캐너를 자동으로 선택합니다.
trufflehog --version을 실행하여 v3인지 확인하고 사용합니다. v2 설치 또는 인식되지 않는 버전은 경고를 출력하고 2단계로 넘어갑니다.podman run 또는 docker run을 사용하여 컨테이너(ghcr.io/trufflesecurity/trufflehog:latest)에서 truffleHog를 실행하며, 스캔 경로를 동일한 절대 경로에 읽기 전용으로 마운트하여 JSON 출력 경로가 네이티브 실행과 동일하도록 합니다.선택된 스캐너는 'Running pre-flight security scan...' 헤더와 계획 테이블의 SCAN 항목에 표시됩니다. 예:``` Running pre-flight security scan... (truffleHog v3.93.4) Running pre-flight security scan... (truffleHog via podman) Running pre-flight security scan... (regex only — see warning above)
컨테이너 경로에서 존중되는 환경 변수: `CONTAINER_RUNTIME` (런타임 선택을 재정의, 예: `CONTAINER_RUNTIME=docker`) 및 `TRUFFLEHOG_IMAGE` (특정 이미지 태그 고정).
### podman 또는 Docker를 통한 truffleHog 실행 (로컬 설치 불필요)
수동 설정이 필요하지 않습니다. `gh-safe-repo`는 podman 또는 docker를 자동으로 감지하여(위의 2단계) 올바른 볼륨 마운트로 컨테이너에서 truffleHog를 실행합니다. `CONTAINER_RUNTIME` 및 `TRUFFLEHOG_IMAGE` 환경 변수가 존중됩니다.
쉘 래퍼(`tools/trufflehog`)와 고정된 로컬 이미지를 빌드하기 위한 `Containerfile`이 [`tools/`](https://github.com/ariesq/gh-safe-repo/blob/HEAD/tools/README.md)에 제공됩니다. 이는 시스템 전체에서 사용 가능한 컨테이너 기반 truffleHog를 원하거나, 에어갭 이미지가 필요한 사용자를 위한 것입니다.
### 대화형 검토```
Pre-flight scan: my-private-project
CRITICAL my_private_project/config.py:12 AWS Access Key ID
[redacted]
WARNING my_private_project/setup.py:3 Email address
author_email="[email protected]"
1 critical finding, 1 warning.
Critical findings detected. Continue anyway? [y/N]:
N)입니다. 계속하려면 y를 명시적으로 입력해야 합니다.Y)입니다. Enter를 눌러 진행하거나 n을 입력하여 중단합니다.비밀 정보는 출력에서 가려집니다. 이메일 주소와 TODO는 일치하는 줄을 표시합니다.
빌드 아티팩트 디렉토리(node_modules, __pycache__, .venv, venv, dist, build)는 스캔 속도를 유지하기 위해 기본적으로 건너뜁니다. Git 저장소에서는 이 건너뛰기가 조건부입니다. 디렉토리를 제외하기 전에 스캐너는 git ls-files -- <dir>을 실행하여 내부의 파일이 추적되는지 확인합니다. 추적되는 경우 디렉토리는 정상적으로 스캔됩니다.
즉, 커밋된 node_modules 또는 dist 트리(드물지만 발생할 수 있음)가 조용히 누락되지 않습니다. 커밋되지 않은 디렉토리(일반적인 경우)는 이전과 같이 계속 건너뜁니다.
복제된 소스 저장소에서 SKIP_DIRS 하위 디렉토리가 발견되면 여전히 경고가 출력됩니다. 이는 예상보다 많은 내용이 커밋되었음을 나타낼 수 있기 때문입니다.
두 가지 구성 키를 사용하면 전체 검사 범주를 비활성화하지 않고 알려진 안전한 발견을 억제할 수 있습니다.
scan_exclude_paths — 파일 또는 디렉토리를 완전히 건너뜁니다. 값은 상대 파일 경로와 일치하는 줄바꿈/쉼표로 구분된 정규식 패턴입니다. 일치하는 파일은 모든 검사(비밀, 이메일, TODO, 큰 파일, AI 컨텍스트 파일 탐지)에서 제외됩니다. 동일한 패턴은 --exclude-paths를 통해 truffleHog에도 전달되므로 어떤 스캐너 엔진이 활성화되어 있든 적용 범위가 일관됩니다.```ini
[pre_flight_scan]
scan_exclude_paths = docs/api.github.com.json tests/fixtures/
**`exclude_emails`** — 특정 주소 또는 전체 도메인에 대한 이메일 검색 결과를 숨깁니다. 값은 줄바꿈/쉼표로 구분되며, 대소문자를 구분하지 않습니다. `@`로 시작하는 항목은 해당 도메인의 모든 이메일과 일치하며, 그 외의 경우 전체 주소와 정확히 일치해야 합니다. 작업 트리 및 git 기록 검색 결과 모두에 적용됩니다.```ini
[pre_flight_scan]
# Suppress bot addresses and placeholder domains
exclude_emails = [email protected], [email protected], @example.com
[pre_flight_scan] scan_for_secrets = true scan_for_emails = true scan_for_todos = true max_file_size_mb = 100
When banned strings or AI context files are found the scanner prints a ready-to-run `git filter-repo` command to remove them from the source repo's history before re-running.
---
## 설정
`gh-safe-repo`는 다음 순서대로 설정을 찾습니다 (첫 번째 일치 항목 사용):
1. **`--config PATH`** — 명시적 재정의
2. **`./gh-safe-repo.ini`** — 현재 작업 디렉터리
3. **`$XDG_CONFIG_HOME/gh-safe-repo/gh-safe-repo.ini`** — `$XDG_CONFIG_HOME`이 설정되지 않은 경우 `~/.config`로 기본 설정
인수 없는 `--config`는 파일 조회를 완전히 건너뛰고 내장 기본값만 사용합니다.
모든 값은 안전한 기본값을 가지므로 시작하는 데 설정 파일이 필요하지 않습니다.
완전히 주석 처리된 예제 설정 파일이 `gh-safe-repo.ini.example`로 저장소에 포함되어 있습니다. 이를 복사하여 시작하세요:```bash
# User-level config (XDG)
mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/gh-safe-repo"
cp gh-safe-repo.ini.example "${XDG_CONFIG_HOME:-$HOME/.config}/gh-safe-repo/gh-safe-repo.ini"
# Or project-level config (current directory)
cp gh-safe-repo.ini.example ./gh-safe-repo.ini
[repo]
private = true
has_wiki = false has_projects = false has_issues = true
delete_branch_on_merge = false
allow_squash_merge = true allow_merge_commit = true allow_rebase_merge = true
create leaves an initialized README in the new repo.auto_init = false
[actions]
allowed_actions = selected
github_owned_allowed = true # actions maintained by GitHub (e.g. actions/checkout) verified_allowed = true # actions from Marketplace verified creators
default_workflow_permissions = read
can_approve_pull_request_reviews = false
sha_pinning_required = true
[branch_protection]
protected_branch = main
require_pull_request = true
required_approving_reviews = 1
dismiss_stale_reviews = true
require_conversation_resolution = true
enforce_admins = false
allow_force_pushes = false
allow_deletions = false
use_rulesets = true
[tag_protection]
protected_tags = *
prevent_tag_deletion = true
prevent_tag_update = true
[security]
enable_dependabot_alerts = true
enable_dependabot_security_updates = true
enable_private_vulnerability_reporting = true
enable_secret_scanning_push_protection = true
[pre_flight_scan] scan_for_secrets = true scan_for_emails = true scan_for_todos = true
max_file_size_mb = 100
[git_transport]
workflow token scope to pushworkflow scope intentionally.---
## GitHub 요금제 제한 사항
일부 기능은 저장소 공개 여부와 GitHub 요금제에 따라 사용 가능 여부가 결정됩니다.
| 기능 | Free + Public | Free + Private | Pro/Team + Private |
|---|:---:|:---:|:---:|
| 브랜치 보호 / Rulesets | 예 | 아니오 | 예 |
| 태그 보호 (Rulesets) | 예 | 아니오 | 예 |
| Dependabot 알림 | 예 | 아니오 | 예 |
| Dependabot 보안 업데이트 | 예 | 아니오 | 예 |
| 시크릿 스캐닝 | 자동 | 아니오 | 예 |
| 푸시 보호 | 예 | 아니오 | 예 |
| 비공개 취약점 보고 | 예 | 예 | 예 |
| 종속성 그래프 | 자동 | 아니오 | 예 |
`gh-safe-repo`는 런타임에 요금제 수준과 저장소 공개 여부를 감지합니다. 사용할 수 없는 기능은 계획 출력에 `SKIP`으로 표시되고 명확한 이유가 함께 제공됩니다. 도구는 절대 조용히 실패하지 않습니다.
---
## 작동 방식```
gh-safe-repo create <owner/repo>
│
├─ Parse owner/repo, validate owner matches authenticated user (create only)
├─ Load config (./gh-safe-repo.ini or $XDG_CONFIG_HOME/gh-safe-repo/gh-safe-repo.ini)
├─ Apply CLI flag overrides (--public, etc.)
├─ Authenticate via gh CLI or GITHUB_TOKEN
├─ GET /user → owner login + plan level (single cached call)
│
├─ Build plan (each plugin compares desired vs. current state)
│ ├─ RepositoryPlugin → repo creation + basic settings
│ ├─ ActionsPlugin → allowed actions, workflow permissions, SHA pinning
│ ├─ BranchProtectionPlugin → Rulesets API (default; classic if use_rulesets = false)
│ ├─ SecurityPlugin → Dependabot, secret scanning, push protection, private vuln reporting
│ └─ TagProtectionPlugin → immutable tags via Rulesets API
│
├─ Print plan table
│
└─ Apply (unless --dry-run)
├─ POST /user/repos
├─ PATCH /repos/{owner}/{repo} (settings)
├─ PUT /repos/{owner}/{repo}/actions/permissions/workflow
├─ POST/PATCH /repos/{owner}/{repo}/rulesets (branch protection; default)
│ or PUT /repos/{owner}/{repo}/branches/main/protection (if use_rulesets = false)
├─ PUT /repos/{owner}/{repo}/vulnerability-alerts
├─ PUT /repos/{owner}/{repo}/automated-security-fixes
├─ PUT /repos/{owner}/{repo}/private-vulnerability-reporting
├─ PATCH /repos/{owner}/{repo} (security_and_analysis: push protection)
├─ POST /repos/{owner}/{repo}/rulesets (tag protection ruleset)
├─ git clone --mirror + git push --mirror (if --from)
└─ git clone <local> + git push --all --tags (if --local, git repo)
or git init + add -A + commit + push (if --local, plain dir)
각 설정 카테고리는 자체 포함된 플러그인 클래스(gh_safe_repo/plugins/)입니다. 모든 플러그인은:
Plan(변경 객체 목록: ADD / UPDATE / DELETE / SKIP)을 반환합니다.즉, 감사 모드와 생성 모드는 동일한 plan/apply 경로를 사용합니다. 유일한 차이점은 현재 상태를 기존 리포지토리에서 가져오는지 아니면 GitHub 기본값으로 가정하는지 여부입니다.
API 호출은 다음 순서로 토큰을 확인합니다:
GITHUB_TOKEN 환경 변수 — 활성 gh 세션을 전환하지 않고 특정 계정을 대상으로 지정할 수 있습니다(그리고 CI에서 필요한 유일한 자격 증명입니다).gh auth token — gh auth login에서 설정한 모든 것.토큰은 하위 프로세스 환경에서 GH_TOKEN으로 하위 gh api 프로세스에 전달되며 절대 기록되지 않습니다.
Git 작업(--local / --from push 및 clone)은 기본적으로 API 토큰이 아닌 사용자 자신의 Git 자격 증명(SSH 키 또는 자격 증명 도우미)을 사용합니다. 둘 다 없는 환경(예: GITHUB_TOKEN만 있는 CI)에서는 도구가 URL에 토큰을 포함하여 HTTPS를 통해 푸시하는 것으로 대체됩니다. [git_transport] mode 설정이 이를 제어합니다(설정 참조 참조). 토큰이 포함된 URL은 리포지토리의 .git/config에 기록되지 않으며 모든 출력에서 수정됩니다.
모든 GitHub API 호출은 subprocess를 통해 gh api를 거칩니다. 이렇게 하면 인증이 전적으로 gh CLI에 유지됩니다 — 토큰 관리 코드, OAuth 흐름, PyGithub 버전 고정이 필요 없습니다. JSON 요청 본문은 --field 플래그가 아닌 --input -(stdin)을 통해 전달됩니다.
git clone https://github.com/your-username/gh-safe-repo cd gh-safe-repo uv sync # creates .venv, installs pytest
uv run pytest tests/ -v
./gh-safe-repo create <owner/repo> --dry-run
uv tool install .
테스트 파일 설명, 모킹 규칙 및 새 테스트를 추가하는 방법은 [`tests/README.md`](https://github.com/ariesq/gh-safe-repo/blob/HEAD/tests/README.md)를 참조하세요.
### 프로젝트 구조```
gh-safe-repo/
├── gh-safe-repo # Thin launcher (entry point for direct use)
├── gh_safe_repo/ # Package — see gh_safe_repo/README.md for internals
│ ├── cli.py # Subparser dispatch (create, fix, scan)
│ ├── commands/ # Subcommand implementations
│ │ ├── _common.py # Shared helpers, CLIContext, plan formatting
│ │ ├── create.py # create subcommand
│ │ ├── fix.py # fix subcommand
│ │ └── scan.py # scan subcommand
│ └── plugins/ # Settings plugins (one per category)
├── pyproject.toml # Build config, entry points
├── gh-safe-repo.ini.example # Fully annotated example config
└── tests/
See gh_safe_repo/README.md for the module map, plugin architecture, and a guide to adding new settings.
There are no runtime dependencies. Everything uses the Python standard library (argparse, configparser, subprocess, json, re). Do not add third-party packages without discussion.
pytest is the only dev dependency, declared as a UV-native [dependency-groups] entry in pyproject.toml.
These projects were studied during design and influenced the architecture of gh-safe-repo. They are distinct tools with different scope and user models — see docs/LEARNINGS.md for detailed technical notes on how patterns were adapted.
github/safe-settings — Org-level GitHub App (Node.js/Probot) that enforces repository settings from a central config. Source of the plugin architecture pattern (one class per setting category, fetch → diff → apply) and the mergeDeep comparison approach.
repository-settings/app — Simpler per-repo variant of safe-settings, also Node.js/Probot. Provided a cleaner reference for the Diffable base plugin pattern.
nicholasgasior/gh-repo-settings — CLI extension written in Go with a plan/apply workflow. Primary inspiration for the gh api subprocess wrapper pattern and the dry-run plan output design.
| 옵션 | 설명 |
|---|
--public | 공개 리포지토리로 생성 (기본값: 비공개) |
--local PATH | 로컬 git 리포지토리에서 새 리포지토리로 코드를 푸시합니다. 먼저 사전 점검을 실행합니다. --from과 상호 배타적입니다. |
--from OWNER/REPO | 기존 리포지토리에서 새 리포지토리로 코드를 미러링합니다. 사전 점검을 실행합니다. --local과 상호 배타적입니다. |
--yes / -y | 확인 프롬프트를 건너뛰고 즉시 적용합니다 (스크립팅/배치 사용용) |
--dry-run | 변경 사항 없이 계획만 출력합니다. |
--json | ANSI 테이블 대신 계획을 JSON으로 stdout에 출력합니다. |
--config [PATH] | 설정 파일 경로; --config만 지정하면 기본 내장 설정만 사용합니다. |
--debug | 모든 API 호출 및 응답을 출력합니다. |
| 옵션 | 설명 |
|---|
--yes / -y | 확인 프롬프트를 건너뛰고 즉시 적용합니다 (스크립팅/배치 사용용) |
--dry-run | 변경 사항을 적용하지 않고 설정 차이만 표시합니다. |
--json | ANSI 테이블 대신 계획을 JSON으로 stdout에 출력합니다. |
--config [PATH] | 설정 파일 경로; --config만 지정하면 기본 내장 설정만 사용합니다. |
--debug | 모든 API 호출 및 응답과 함께 확인된 리포지토리 식별 정보(id, 전체 이름, 소유자 유형)를 출력합니다. |
| 새 설정이 적용 중 |
UPDATE (노란색) | 기존 설정이 변경 중 (감사 모드) |
DELETE (빨간색) | 설정이 제거 중 |
SKIP (희미함) | 필요한 동작 없음 — 이미 원하는 값이거나, 현재 요금제/가시성 조합에서 기능을 사용할 수 없음 |
| Category | Severity | Examples |
|---|
| 하드코딩된 비밀 | 치명적 | AWS 키 (AKIA…), GitHub 토큰 (ghp_…, github_pat_…), 개인 키, 데이터베이스 URL |
| 금지된 문자열 | 치명적 | 구성한 모든 리터럴 문자열 (사용자 이름, 내부 호스트 이름, 코드명) |
| AI 컨텍스트 파일 | 치명적 | CLAUDE.md, AGENTS.md, .cursorrules, copilot-instructions.md, .cursor/ — 내부 개발 노트가 포함될 수 있음; git 히스토리는 현재 버전보다 더 민감할 수 있음 |
| 이메일 주소 | 경고 | 작업 트리 및 git 히스토리에서 [email protected] 패턴 |
| 대용량 파일 | 경고 | 설정된 크기 임계값 초과 파일 (기본값: 100 MB) |
| TODO/FIXME 주석 | 정보 | # TODO, # FIXME, # HACK, # XXX |