정보
이 Claude 기술은 계층화된 GitHub 저장소 보안 보호를 통제적이고 단계적인 접근 방식으로 적용합니다. 기본적인 규칙 세트와 비밀 스캐닝 같은 기초 보호 장치부터 시작한 후, 필요에 따라 자동화를 위한 GitHub 앱 우회 기능이 포함된 필수 상태 검사를 추가할 수 있습니다. 이 기술은 감사 후 공개 저장소를 강화하거나, 자동 커밋을 수행하는 기존 CI/CD 봇을 중단시키지 않으면서 브랜치 보호를 추가하는 데 사용하세요.
빠른 설치
Claude Code
추천npx skills add pjt222/agent-almanac -a claude-code/plugin add https://github.com/pjt222/agent-almanacgit clone https://github.com/pjt222/agent-almanac.git ~/.claude/skills/harden-github-repo-securityClaude Code에서 이 명령을 복사하여 붙여넣어 스킬을 설치하세요
문서
Harden GitHub Repository Security
Apply GitHub protections in order of blast radius: assess, then a zero-downside
baseline that never breaks CI, then a gated decision on required checks / PR.
Every mutating step is confirmation-gated. Running example: a public,
user-owned repo whose CI auto-commits to the default branch via
stefanzweifel/git-auto-commit-action with the default GITHUB_TOKEN.
Throughout, R = OWNER/REPO (e.g. pjt222/agent-almanac). Set it once:
R=OWNER/REPO. All gh api writes require repo admin.
When to Use
- Hardening a public user-owned repo after
assess-github-repo-security - A repo has no ruleset / branch protection and you want a safe baseline
- Adding required status checks or required PR without breaking a bot that pushes to the default branch
- Provisioning a GitHub App (or deploy key) bypass actor for trusted automation
Inputs
- Required:
OWNER/REPOand admin access (gh auth statusshows admin) - Required: whether a CI bot pushes to the default branch (and which action/identity)
- Optional: assessment report from
assess-github-repo-security - Optional: exact CI check
contextname(s) to require (must run onpush) - Optional: GitHub App id + private key if going to required checks/PR
Procedure
Step 1: Assess First and Confirm Scope
Never mutate blind. Run the read-only assessment and confirm the plan with the user.
# 1. Run the companion read-only skill first (or its core probes):
gh api /repos/$R --jq '{visibility,default_branch,is_org:.organization!=null}'
gh api /repos/$R/rulesets --jq '.[] | {id,name,enforcement}'
gh api /repos/$R/actions/permissions/workflow
# 2. Confirm the two decisive facts before any write:
# - Is the repo PUBLIC and USER-OWNED? (rulesets are free here)
# - Does a bot push to the default branch? (gates Step 3 entirely)
Confirm with the user: baseline (Step 2) is always safe; Step 3 (required checks / PR) is a separate opt-in that will block a default-branch bot unless a bypass is provisioned first.
Expected: You know visibility, owner type, current rulesets, current token default, and whether a bot pushes to the default branch. The user has approved applying at least the baseline.
On failure: If the repo is private and user-owned, rulesets need GitHub Pro — stop and surface that. If not admin, stop (writes will 403). If a bot pushes to the default branch, flag that Step 3 is blocked until Step 3a runs.
Step 2: Apply the Zero-Downside Baseline (never breaks a CI auto-commit)
This tier hardens the repo without breaking a direct-push bot. Apply after confirmation. Force-push/deletion protection, a read-only token default, the free security features, and policy files.
# 2a. Default-branch ruleset: force-push + deletion protection ONLY.
# These do NOT block the bot's direct push.
gh api --method POST /repos/$R/rulesets --input - <<'JSON'
{
"name": "protect-default",
"target": "branch",
"enforcement": "active",
"conditions": { "ref_name": { "include": ["~DEFAULT_BRANCH"], "exclude": [] } },
"rules": [ { "type": "deletion" }, { "type": "non_fast_forward" } ]
}
JSON
# 2b. Actions token: read-only default + no token PR approval.
# A DEFAULT (not a cap) for same-repo push events, so the bot still works
# once its job declares contents: write.
gh api --method PUT /repos/$R/actions/permissions/workflow \
-f default_workflow_permissions=read \
-F can_approve_pull_request_reviews=false
# 2c. Free security features (all free on public repos):
gh api -X PUT repos/$R/vulnerability-alerts # alerts + dependency graph
gh api -X PUT repos/$R/automated-security-fixes # Dependabot fix PRs (separate toggle!)
gh api -X PATCH repos/$R --input - <<'JSON'
{"security_and_analysis":{"secret_scanning":{"status":"enabled"},"secret_scanning_push_protection":{"status":"enabled"}}}
JSON
gh api -X PUT repos/$R/private-vulnerability-reporting
Leave Settings > Actions > General fork-PR approval at the public-repo
default ("Require approval for first-time contributors"). Fork pull_request
runs already receive a read-only GITHUB_TOKEN with no access to secrets, so a
fork cannot auto-commit to the default branch (no REST toggle — this is a UI
setting; tighten to "all external contributors" only if the repo has secrets or
self-hosted runners).
Then add two tracked files (commit them):
.github/dependabot.yml — include a github-actions block so action SHAs stay fresh:
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
.github/SECURITY.md — a short disclosure policy pointing reporters at the
"Report a vulnerability" button (private vulnerability reporting, enabled above).
Flipping the token default to read silently strips write from every
workflow that assumed a write default, not just the auto-commit job. Audit
all files in .github/workflows/ and declare the minimal permissions: each
job actually needs — common scopes are contents, packages, pull-requests,
pages, and id-token. For the running example: top-level permissions: {},
contents: write only on the auto-commit job, and every uses: pinned to a
full 40-char commit SHA with the version in a trailing comment.
Expected: gh api /repos/$R/rulesets shows protect-default active;
.../actions/permissions/workflow shows default_workflow_permissions: read
and can_approve_pull_request_reviews: false; secret scanning + push protection
- Dependabot + private reporting are on;
.github/dependabot.ymland.github/SECURITY.mdare committed. The bot's next run still pushes.
On failure: If 2b makes an existing bot push 403 — or any other workflow
fails after losing a write scope it silently relied on — the job is missing the
explicit permissions: it needs (e.g. contents: write, packages: write,
pull-requests: write); add the minimal scope back (the read-only default is not
a hard cap for same-repo pushes). If automated-security-fixes seems to do nothing, verify
vulnerability-alerts (2c line 1) ran first — alerts are a separate prerequisite
toggle from fixes. If push protection later fails a bot run, treat the flagged
secret as a real finding — the bot cannot click the interactive web bypass.
Step 3: Decision Gate — Required Status Checks / Required PR
STOP. Required checks and required PR block direct pushes to the ref, not
just PR merges. If a bot pushes to the default branch, turning either on will
break it unless you first provision a bypass. The default GITHUB_TOKEN /
github-actions[bot] cannot be a bypass actor (GitHub design). Do not enable
this tier on a bot-pushing branch without a bypass in place.
Also, on a solo repo, required_approving_review_count >= 1 is a self-lockout
(you cannot approve your own PR) — keep it 0. And a required check that only
runs on pull_request never reports on a direct push, so it permanently
blocks the bot. Ensure the check runs on push.
Step 3a — provision the bot bypass FIRST (if a bot pushes to this branch).
Recommended: a GitHub App installation token. (Deploy key is a narrower
single-repo alternative: add an SSH deploy key with write access and use
actor_type: DeployKey in 3b.)
# Create a GitHub App (personal account, Settings > Developer settings >
# GitHub Apps), permission Repository > Contents: Read and write; install it
# on THIS repo. Store the App id as a repo variable and the private key as an
# Actions secret. In the workflow, mint a token and pass it to checkout + the
# commit action:
# - uses: actions/create-github-app-token@<sha> # v2
# id: app-token
# with: { app-id: ${{ vars.APP_ID }}, private-key: ${{ secrets.APP_KEY }} }
# - uses: actions/checkout@<sha>
# with: { token: ${{ steps.app-token.outputs.token }} }
# - uses: stefanzweifel/git-auto-commit-action@<sha>
# with: { ... } # inherits the app token via the checkout credentials
#
# The numeric App id used in Step 3b is shown on the App's own page:
# Settings > Developer settings > GitHub Apps > <your app> > About ("App ID").
# It is unrelated to the repository id and cannot be derived from /repos/$R.
Higher-assurance alternative (no standing bypass actor). If a manual merge
click is acceptable, convert the job to open a PR with the App token (e.g.
peter-evans/create-pull-request) and let a human merge. No bypass actor is
needed and every rule stays enforced even against the automation's identity. The
App token is still required: a PR opened with the plain GITHUB_TOKEN does not
trigger pull_request/push workflows, so required checks never run and it sits
expected forever (see Common Pitfalls).
Step 3b — apply the hardened ruleset with the App as an Integration bypass
actor. Replace RULESET_ID with the id from Step 2a, <APP_ID> with your App
id, and build with your real check context. bypass_mode: always because the
action pushes directly (use pull_request only if the bot opens PRs).
gh api --method PUT /repos/$R/rulesets/RULESET_ID --input - <<'JSON'
{
"name": "protect-default",
"target": "branch",
"enforcement": "active",
"conditions": { "ref_name": { "include": ["~DEFAULT_BRANCH"], "exclude": [] } },
"bypass_actors": [ { "actor_id": <APP_ID>, "actor_type": "Integration", "bypass_mode": "always" } ],
"rules": [
{ "type": "deletion" },
{ "type": "non_fast_forward" },
{ "type": "pull_request", "parameters": { "required_approving_review_count": 0, "dismiss_stale_reviews_on_push": true, "require_code_owner_review": false, "require_last_push_approval": false, "required_review_thread_resolution": false, "allowed_merge_methods": ["merge"] } },
{ "type": "required_status_checks", "parameters": { "strict_required_status_checks_policy": true, "do_not_enforce_on_create": true, "required_status_checks": [ { "context": "build" } ] } }
]
}
JSON
# Verify the bypass actor is present:
gh api /repos/$R/rulesets/RULESET_ID --jq '.bypass_actors'
do_not_enforce_on_create: true avoids the new-branch catch-22 (CI can't have
run on a branch that does not exist yet). If there is no bot on this branch,
you may skip 3a and omit bypass_actors.
Trade-off: strict_required_status_checks_policy: true ("branch must be
up to date") forces every open human PR to be re-updated each time the bot
auto-commits to the default branch, and dismiss_stale_reviews_on_push: true
compounds the churn — a chatty bot can make PRs perpetually unmergeable. On a
frequently-auto-committed branch prefer strict_required_status_checks_policy: false (loose) unless up-to-date-before-merge is genuinely required.
Expected: The ruleset now enforces the required check and required-PR rule
for humans and forks, .bypass_actors lists the App (Integration), and the
bot's next run still lands on the default branch (via the App token). Humans
must open a PR; the check must be green.
On failure: If the bot 403s after 3b, the App identity is not actually the
bypass actor — confirm the workflow passes the App token to checkout (not
just to the commit action) and that <APP_ID> in bypass_actors matches. If
the bot's push sits expected/pending forever, the required check does not run
on push — make it trigger on push or drop it from the required list. If the
bypass-actor picker does not appear on your personal repo, the required-PR +
auto-commit combination is unsatisfiable here; GitHub's workaround is a free org.
If merges are blocked on a solo repo, required_approving_review_count is >= 1
— set it to 0.
Step 4: Optional Advanced Hardening
Apply individually, each still confirmation-gated. None of these are needed for the baseline; several have sharp edges.
# 4a. Tag ruleset — protect release tags (classic tag protection is deprecated):
gh api --method POST /repos/$R/rulesets --input - <<'JSON'
{ "name": "protect-tags", "target": "tag", "enforcement": "active",
"conditions": { "ref_name": { "include": ["refs/tags/v*"], "exclude": [] } },
"rules": [ { "type": "deletion" }, { "type": "non_fast_forward" } ] }
JSON
# 4b. Merge-method toggles (e.g. merge-commits-only to preserve per-commit history):
gh api -X PATCH repos/$R -F allow_merge_commit=true -F allow_squash_merge=false \
-F allow_rebase_merge=false -F delete_branch_on_merge=true
# 4c. CodeQL "default setup" (server-managed — commits NO workflow YAML):
gh api -X PATCH repos/$R/code-scanning/default-setup -f state=configured -f query_suite=default
# 4d. sha_pinning_required — ONLY after every `uses:` is a full 40-char SHA,
# or every workflow run fails (including actions/checkout):
gh api --method PUT /repos/$R/actions/permissions -F enabled=true \
-f allowed_actions=selected -F sha_pinning_required=true
# selected mode also gates WHICH actions may run: github_owned covers actions/*,
# but git-auto-commit-action is NOT GitHub-owned — allow-list it in the SAME
# step or the bot fails with "actions are not allowed":
gh api --method PUT /repos/$R/actions/permissions/selected-actions \
-F github_owned_allowed=true -F verified_allowed=false \
-f 'patterns_allowed[]=stefanzweifel/git-auto-commit-action@*'
gh api /repos/$R/actions/permissions --jq '.sha_pinning_required' # verify
Note: allowed_actions=selected with github_owned_allowed=true still blocks
stefanzweifel/git-auto-commit-action until it is in patterns_allowed (the 4d
allow-list command handles this). Require-signed-commits and require-linear-history
are intentionally omitted here:
signing blocks the unsigned git-CLI bot push, and linear history is mutually
exclusive with a merge-commits-only policy.
Expected: Each selected advanced control is applied and verified without
breaking the bot: tag ruleset lists on .../rulesets, merge toggles reflect on
the repo object, CodeQL default setup shows state: configured,
sha_pinning_required reads true only after all actions are SHA-pinned, and
git-auto-commit-action is in patterns_allowed so the bot still runs.
On failure: If every workflow fails right after 4d, an action is still
tag-pinned — convert all uses: to SHAs or revert sha_pinning_required to
false. If the bot fails with "actions are not allowed" after restricting
allowed actions, add git-auto-commit-action to patterns_allowed. If 4c
errors, the repo may have no CodeQL-supported language — skip it.
Validation
- Assessment ran and scope (public/user-owned, bot-on-default-branch) confirmed before any write
-
protect-defaultruleset active withdeletion+non_fast_forward -
default_workflow_permissions=readandcan_approve_pull_request_reviews=false - Secret scanning + push protection + Dependabot alerts + fixes + private reporting enabled
-
.github/dependabot.yml(github-actions ecosystem) and.github/SECURITY.mdcommitted - Auto-commit bot's next run still pushes successfully (baseline did not break it)
- Required checks/PR enabled ONLY with a GitHub App (or deploy key) bypass in place first
- Solo repo keeps
required_approving_review_count: 0; required checks run onpush - Loop guard added if a bot now pushes with an App token or PAT
Common Pitfalls
- GITHUB_TOKEN is not a bypass actor:
github-actions[bot]/ the default token cannot be added to any ruleset bypass list and cannot push to a protected branch, by design. "Just add the Actions bot to the bypass list" has no such option — the push 403s. Use a GitHub App or deploy key. - PAT bypass is an anti-pattern: an admin/personal PAT in the bypass list
(or classic BP with
enforce_admins=false) exempts ALL admin actions including your own interactive pushes — role-wide, not automation-scoped, and long-lived. Prefer an App (Integration) or deploy key (DeployKey). - Required checks only on
pull_request: a check that never runs onpushnever reports on the bot's direct push, so it permanently blocks the bot and new-branch creation. Make the check run onpushand setdo_not_enforce_on_create: true. - Loop guard once an App token replaces GITHUB_TOKEN: the default token
suppresses downstream
push/pull_requesttriggers; an App token or PAT does not, so the auto-commit workflow can re-trigger itself indefinitely. Guard withif: github.actor != '<app>[bot]',paths-ignore, or[skip ci]. - PR via the default GITHUB_TOKEN is a false fix: a PR opened by
GITHUB_TOKENdoes not triggerpull_request/pushworkflows, so required checks never run and it sitsexpectedforever. Create the PR with the App token. - Alerts != fixes:
vulnerability-alertsandautomated-security-fixesare separate toggles; enabling alerts opens no PRs. Enable both. - Don't buy GHAS on a public repo: secret scanning, push protection, CodeQL, and dependency review are already free on public repos; the paid Secret Protection / Code Security products target private/org repos.
Related Skills
assess-github-repo-security- the read-only companion; always run this firstconfigure-git-repository- baseline .gitignore, branches, remotes, hookssetup-github-actions-ci- the CI workflow whose token/permissions this hardens
GitHub 저장소
자주 묻는 질문
harden-github-repo-security Skill이란 무엇인가요?
harden-github-repo-security은(는) pjt222이(가) 만든 Claude Skill입니다. Skill은 Claude가 필요할 때 불러오는 지침과 리소스를 묶어 추가 프롬프트 없이 harden-github-repo-security 관련 작업을 수행할 수 있게 합니다.
harden-github-repo-security은(는) 어떻게 설치하나요?
이 페이지의 설치 명령을 사용하세요. harden-github-repo-security을(를) Claude Code 플러그인으로 추가하거나 저장소를 skills 디렉터리에 복제한 다음 Claude를 다시 시작해 Skill을 불러옵니다.
harden-github-repo-security은(는) 어떤 카테고리에 속하나요?
harden-github-repo-security은(는) 디자인 카테고리에 속합니다.
harden-github-repo-security은(는) 무료로 사용할 수 있나요?
네. harden-github-repo-security은(는) AIMCP에 등록되어 있으며 무료로 설치할 수 있습니다.
연관 스킬
executing-plans 스킬은 검토 체크포인트가 포함된 통제된 배치로 실행할 완전한 구현 계획이 있을 때 사용합니다. 이 스킬은 계획을 불러와 비판적으로 검토한 후, 소규모 배치(기본값 3개 작업)로 작업을 실행하면서 각 배치 사이에 진행 상황을 아키텍트 검토를 위해 보고합니다. 이를 통해 내재된 품질 관리 체크포인트를 갖춘 체계적인 구현이 보장됩니다.
이 스킬은 코드 변경 사항을 요구 사항에 따라 분석하기 위해 코드 리뷰어 하위 에이전트를 호출합니다. 작업 완료 후, 주요 기능 구현 후, 또는 메인 브랜치에 병합하기 전에 사용해야 합니다. 이 리뷰는 현재 구현체와 원래 계획을 비교하여 문제를 조기에 발견하는 데 도움이 됩니다.
이 스킬은 개발자들이 HTTP, stdio 또는 SSE 전송 방식을 통해 MCP 서버를 Claude Code에 연결하는 포괄적인 가이드를 제공합니다. GitHub, Notion 및 사용자 정의 API와 같은 외부 서비스를 통합하기 위한 설치, 구성, 인증 및 보안을 다룹니다. MCP 통합 설정, 외부 도구 구성 또는 Claude의 모델 컨텍스트 프로토콜 작업 시 활용하세요.
이 스킬은 작업 분석을 기반으로 개발자가 Claude Code 웹 인터페이스와 CLI 인터페이스 중 선택할 수 있도록 돕고, 두 환경 간 원활한 세션 텔레포트를 가능하게 합니다. 웹, CLI 또는 모바일 환경 전환 시 세션 상태와 컨텍스트를 관리하여 워크플로를 최적화합니다. 다양한 단계에서 서로 다른 도구가 필요한 복잡한 프로젝트에 사용하세요.
