Skip to content

Commit 3e1cb1f

Browse files
committed
docs: update github settings to current ui (rulesets, codeql default setup)
modernize per-fork setup docs against the current github ui: - branch protection rewritten as repository rulesets, with a tag ruleset on v* so a release tag cannot be moved or deleted post-publish - code security section reflects the card-based layout and the one-click codeql default setup - actions general wording updated for the current fork pull request approval options and the send-secrets-to-forks toggles - init.sh reorders next-steps so the release env exists before rulesets reference it, and prints the rulesets api calls instead of the legacy branches/main/protection endpoint
1 parent 59639a9 commit 3e1cb1f

3 files changed

Lines changed: 227 additions & 93 deletions

File tree

docs/branch-protection.md

Lines changed: 101 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,105 @@
1-
# branch protection settings (manual, applied once)
2-
3-
github does not let you commit branch protection rules to a repo. you set
4-
them in `Settings → Branches → Branch protection rules` (or via the api).
5-
sigil expects these on `main`:
6-
7-
| setting | value |
8-
| --------------------------------------- | -------------------------------------- |
9-
| require a pull request before merging | on |
10-
| require approvals | 1 |
11-
| dismiss stale reviews on new commits | on |
12-
| require review from codeowners | on |
13-
| require status checks to pass | `ci` and `pinned-actions` |
14-
| require branches to be up to date | on |
15-
| require signed commits | on |
16-
| require linear history | on |
17-
| do not allow bypassing the above | on (no admin bypass) |
18-
| restrict who can push to matching | maintainers only |
19-
| allow force pushes | off |
20-
| allow deletions | off |
21-
22-
apply once via the cli (requires `gh` and admin scope):
1+
# branch and tag protection (one-time, applied via api)
2+
3+
github does not let you commit branch protection to a repo. you set it once
4+
in `Settings → Rules → Rulesets` (recommended) or, for legacy setups,
5+
`Settings → Branches → Branch protection rules`.
6+
7+
sigil expects two rulesets: one on `main` and one on every release tag (`v*`).
8+
both block force-push and deletion; the branch ruleset additionally requires
9+
review, signed commits, linear history, and the `ci` / `pinned-actions` status
10+
checks.
11+
12+
## main branch ruleset
13+
14+
| setting | value |
15+
| ------------------------------------------- | -------------------------------------- |
16+
| target | branch |
17+
| target branches | default branch |
18+
| enforcement | active |
19+
| restrict deletions | on |
20+
| block force pushes | on |
21+
| require linear history | on |
22+
| require signed commits | on |
23+
| require a pull request before merging | on |
24+
| required approvals | 1 |
25+
| dismiss stale reviews on push | on |
26+
| require review from codeowners | on |
27+
| require status checks to pass | `ci`, `pinned-actions` |
28+
| require branches to be up to date | on |
29+
| bypass list | empty (no admin bypass) |
30+
31+
## release tag ruleset
32+
33+
| setting | value |
34+
| ---------------------- | -------------------- |
35+
| target | tag |
36+
| target tags | `refs/tags/v*` |
37+
| enforcement | active |
38+
| restrict deletions | on |
39+
| block force pushes | on |
40+
| bypass list | empty |
41+
42+
tag protection prevents an attacker (or careless rebase) from moving `v0.0.1`
43+
to a different commit after release. the signature attached to the tarball
44+
becomes pointless if the tag itself is mutable.
45+
46+
## apply via cli
47+
48+
requires `gh` and admin scope on the repo. paste both calls.
2349

2450
```bash
25-
gh api -X PUT repos/<org>/<repo>/branches/main/protection \
26-
-F required_pull_request_reviews.required_approving_review_count=1 \
27-
-F required_pull_request_reviews.dismiss_stale_reviews=true \
28-
-F required_pull_request_reviews.require_code_owner_reviews=true \
29-
-F required_status_checks.strict=true \
30-
-F 'required_status_checks.contexts[]=ci' \
31-
-F 'required_status_checks.contexts[]=pinned-actions' \
32-
-F enforce_admins=true \
33-
-F required_linear_history=true \
34-
-F required_signatures.enabled=true \
35-
-F allow_force_pushes=false \
36-
-F allow_deletions=false
51+
ORG=<your-org>; REPO=<your-repo>
52+
53+
# main branch ruleset
54+
gh api -X POST "repos/$ORG/$REPO/rulesets" --input - <<'EOF'
55+
{
56+
"name": "main protection",
57+
"target": "branch",
58+
"enforcement": "active",
59+
"conditions": { "ref_name": { "include": ["~DEFAULT_BRANCH"], "exclude": [] } },
60+
"rules": [
61+
{ "type": "deletion" },
62+
{ "type": "non_fast_forward" },
63+
{ "type": "required_linear_history" },
64+
{ "type": "required_signatures" },
65+
{ "type": "pull_request", "parameters": {
66+
"required_approving_review_count": 1,
67+
"dismiss_stale_reviews_on_push": true,
68+
"require_code_owner_review": true,
69+
"require_last_push_approval": false,
70+
"required_review_thread_resolution": false
71+
}},
72+
{ "type": "required_status_checks", "parameters": {
73+
"required_status_checks": [
74+
{ "context": "ci" },
75+
{ "context": "pinned-actions" }
76+
],
77+
"strict_required_status_checks_policy": true
78+
}}
79+
]
80+
}
81+
EOF
82+
83+
# release tag ruleset
84+
gh api -X POST "repos/$ORG/$REPO/rulesets" --input - <<'EOF'
85+
{
86+
"name": "release tag protection",
87+
"target": "tag",
88+
"enforcement": "active",
89+
"conditions": { "ref_name": { "include": ["refs/tags/v*"], "exclude": [] } },
90+
"rules": [
91+
{ "type": "deletion" },
92+
{ "type": "non_fast_forward" }
93+
]
94+
}
95+
EOF
3796
```
3897

39-
`scripts/init.sh` prints this command at the end with your org/repo substituted.
98+
`scripts/init.sh` prints both calls at the end with your org/repo substituted.
99+
100+
## why rulesets, not classic branch protection
101+
102+
both satisfy the openssf scorecard `Branch-Protection` check. but rulesets
103+
are where github is investing: tag targeting, org-level rules, layered
104+
bypass lists with audit trails. if you start a new repo today, start with
105+
rulesets. classic branch protection still works on existing repos.

docs/github-settings.md

Lines changed: 77 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,35 +18,48 @@ apply once after `init.sh`. each section says where to click.
1818
| pull requests: automatically delete head branch | on |
1919
| archives: include git lfs objects | off |
2020

21-
## settings -> branches
21+
## settings -> rules -> rulesets
2222

23-
apply branch protection on `main`. see [branch-protection.md](./branch-protection.md)
24-
for the full setting list and a `gh api` one-liner.
23+
apply two rulesets: one for the default branch, one for release tags.
24+
see [branch-protection.md](./branch-protection.md) for the exact settings
25+
and the `gh api` calls. classic `Settings → Branches → Branch protection
26+
rules` still works but rulesets are the supported path going forward and
27+
are required for tag protection.
2528

2629
## settings -> code security
2730

28-
| setting | value |
29-
| ------------------------------- | ----------------------------------------- |
30-
| dependabot alerts | on |
31-
| dependabot security updates | on |
32-
| dependabot version updates | on (auto, reads `.github/dependabot.yml`) |
33-
| secret scanning | on |
34-
| secret scanning push protection | on |
35-
| code scanning (codeql) | on |
36-
| private vulnerability reporting | on |
31+
since 2024 this page is a list of cards rather than a single toggle column.
32+
turn each card on:
3733

38-
`SECURITY.md` already wires private vulnerability reporting; flipping the toggle
39-
exposes the form to reporters.
34+
| card | action |
35+
| ------------------------------------------ | ---------------------------------------- |
36+
| dependabot alerts | enable |
37+
| dependabot security updates | enable |
38+
| dependabot version updates | already configured via `.github/dependabot.yml` |
39+
| secret scanning | enable |
40+
| secret scanning push protection | enable |
41+
| code scanning (codeql) | "set up" -> default |
42+
| private vulnerability reporting | enable |
43+
44+
`code scanning -> default setup` is now one click and covers every
45+
language github detects. choose default unless you have a reason to
46+
write your own `codeql.yml`.
47+
48+
`SECURITY.md` already wires private vulnerability reporting; flipping
49+
the toggle exposes the form to reporters.
4050

4151
## settings -> actions -> general
4252

43-
| setting | value |
44-
| ------------------------------------------------ | ---------------------------------------------------------------------------------- |
45-
| actions permissions | `allow <your-org>, and select non-<your-org> actions and reusable workflows` |
46-
| allowed actions | enumerate by full name and sha (see below) |
47-
| fork pull request workflows from outside collabs | "require approval for first-time contributors who are new to github" (or stricter) |
48-
| workflow permissions | "read repository contents and packages permissions" (default to read) |
49-
| allow github actions to create / approve prs | off |
53+
| setting | value |
54+
| -------------------------------------------------- | ---------------------------------------------------------------------------------- |
55+
| actions permissions | "allow `<your-org>` actions and reusable workflows, and select non-`<your-org>` actions and reusable workflows" |
56+
| allowed actions | enumerate by full name (see below) |
57+
| approval for fork pull request workflows | "require approval for first-time contributors" (or stricter) |
58+
| run workflows from fork pull requests | leave default; rely on the approval gate above |
59+
| send write tokens to fork pull request workflows | off |
60+
| send secrets and variables to fork pull request workflows | off |
61+
| workflow permissions | "read repository contents and packages permissions" |
62+
| allow github actions to create or approve pull requests | off |
5063

5164
allowed actions list (paste, after `init.sh` pins them):
5265

@@ -63,27 +76,31 @@ actions/upload-artifact@*
6376
actions/download-artifact@*
6477
```
6578

66-
`*` is github's allowed-actions wildcard, not a sha. the sha is enforced by
67-
`scripts/check-pins.sh` in ci.
79+
`*` is github's allowed-actions wildcard, not a sha. the sha is enforced
80+
by `scripts/check-pins.sh` in ci.
6881

6982
## settings -> environments -> release
7083

7184
create the `release` environment used by `release.yml`. set:
7285

73-
| setting | value |
74-
| ---------------------------- | -------------------------------- |
75-
| required reviewers | at least 1 maintainer |
76-
| wait timer | 5 minutes (rollback window) |
77-
| deployment branches and tags | protected branches and tags only |
78-
| environment secrets | none (oidc replaces tokens) |
79-
| environment variables | none unless required |
86+
| setting | value |
87+
| ----------------------------- | --------------------------------------------- |
88+
| required reviewers | at least 1 maintainer |
89+
| wait timer | 5 minutes (rollback window) |
90+
| prevent self-review | off for solo maintainer, on if more than one |
91+
| deployment branches and tags | "selected branches and tags" -> add `v*` |
92+
| environment secrets | none (oidc replaces tokens) |
93+
| environment variables | none unless required |
8094

81-
reviewers must approve every release before npm publish runs.
95+
reviewers must approve every release before npm publish runs. the wait
96+
timer gives you a final five-minute window to cancel the deploy from
97+
the actions tab.
8298

8399
## settings -> webhooks
84100

85101
webhooks should be empty. every webhook is an outbound trust point.
86-
if you need ci notifications, prefer github built-ins (slack github app, github email).
102+
if you need ci notifications, prefer github built-ins (slack github app,
103+
github email).
87104

88105
## settings -> deploy keys
89106

@@ -92,11 +109,27 @@ use github app installations or oidc workloads instead.
92109

93110
## settings -> integrations and third-party access
94111

95-
| setting | value |
96-
| ------------------ | ------------------------------- |
97-
| third-party access | restrict to vetted apps |
98-
| github apps | review installed apps quarterly |
99-
| oauth apps | none unless required |
112+
| setting | value |
113+
| ---------------------- | ---------------------------------- |
114+
| github apps | review installed apps quarterly |
115+
| oauth apps | none unless required |
116+
| third-party tokens | restrict to vetted apps |
117+
118+
## npmjs.com -> package -> settings -> trusted publisher
119+
120+
set this once per package on npmjs.com. there is no ci file to commit.
121+
122+
| field | value |
123+
| -------------------- | -------------------------------- |
124+
| publisher | github actions |
125+
| repository owner | `<your-org>` |
126+
| repository name | `<your-repo>` |
127+
| workflow filename | `release.yml` |
128+
| environment name | `release` |
129+
130+
once trusted publisher is configured, `npm publish --provenance` in
131+
`release.yml` mints credentials over oidc per run. there is no
132+
`NPM_TOKEN` and never will be.
100133

101134
## organization-level (if you own the org)
102135

@@ -112,7 +145,13 @@ beyond the repo, set these org-wide:
112145
| outside collaborators | minimize, audit quarterly |
113146
| sso session duration | as short as your team tolerates |
114147

148+
org-level rulesets (`Settings → Rules → Rulesets` at the org) let you
149+
apply the main-branch protection across every repo at once instead of
150+
per-repo. recommended once you operate more than a handful of repos.
151+
115152
## verifying
116153

117154
after applying everything, run the [openssf scorecard](https://securityscorecards.dev)
118-
on your repo. target score: **8.5+**. anything below means a check above was missed.
155+
on your repo. target score: **8.5+**. anything below means a check above
156+
was missed (or a check that needs time, like `Maintained` which requires
157+
the repo to be 90+ days old).

scripts/init.sh

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -66,32 +66,61 @@ fi
6666
echo
6767
echo "==> next steps:"
6868
echo
69-
echo " 1. configure npm trusted publisher"
70-
echo " https://docs.npmjs.com/trusted-publishers"
71-
echo " repository: $NEW_ORG/$NEW_REPO"
72-
echo " workflow: .github/workflows/release.yml"
73-
echo " environment: release"
74-
echo
75-
echo " 2. create the github environment named 'release'"
69+
echo " 1. create the github environment named 'release'"
7670
echo " gh api -X PUT repos/$NEW_ORG/$NEW_REPO/environments/release"
7771
echo
78-
echo " 3. apply branch protection on main (one shot, requires admin):"
72+
echo " 2. apply rulesets on main and on release tags (requires admin):"
7973
echo
8074
cat <<EOF
81-
gh api -X PUT repos/$NEW_ORG/$NEW_REPO/branches/main/protection \\
82-
-F required_pull_request_reviews.required_approving_review_count=1 \\
83-
-F required_pull_request_reviews.dismiss_stale_reviews=true \\
84-
-F required_pull_request_reviews.require_code_owner_reviews=true \\
85-
-F required_status_checks.strict=true \\
86-
-F 'required_status_checks.contexts[]=ci' \\
87-
-F 'required_status_checks.contexts[]=pinned-actions' \\
88-
-F enforce_admins=true \\
89-
-F required_linear_history=true \\
90-
-F required_signatures.enabled=true \\
91-
-F allow_force_pushes=false \\
92-
-F allow_deletions=false
75+
gh api -X POST repos/$NEW_ORG/$NEW_REPO/rulesets --input - <<'JSON'
76+
{
77+
"name": "main protection",
78+
"target": "branch",
79+
"enforcement": "active",
80+
"conditions": { "ref_name": { "include": ["~DEFAULT_BRANCH"], "exclude": [] } },
81+
"rules": [
82+
{ "type": "deletion" },
83+
{ "type": "non_fast_forward" },
84+
{ "type": "required_linear_history" },
85+
{ "type": "required_signatures" },
86+
{ "type": "pull_request", "parameters": {
87+
"required_approving_review_count": 1,
88+
"dismiss_stale_reviews_on_push": true,
89+
"require_code_owner_review": true,
90+
"require_last_push_approval": false,
91+
"required_review_thread_resolution": false
92+
}},
93+
{ "type": "required_status_checks", "parameters": {
94+
"required_status_checks": [
95+
{ "context": "ci" },
96+
{ "context": "pinned-actions" }
97+
],
98+
"strict_required_status_checks_policy": true
99+
}}
100+
]
101+
}
102+
JSON
103+
104+
gh api -X POST repos/$NEW_ORG/$NEW_REPO/rulesets --input - <<'JSON'
105+
{
106+
"name": "release tag protection",
107+
"target": "tag",
108+
"enforcement": "active",
109+
"conditions": { "ref_name": { "include": ["refs/tags/v*"], "exclude": [] } },
110+
"rules": [
111+
{ "type": "deletion" },
112+
{ "type": "non_fast_forward" }
113+
]
114+
}
115+
JSON
93116
EOF
94117
echo
118+
echo " 3. configure npm trusted publisher at npmjs.com:"
119+
echo " https://docs.npmjs.com/trusted-publishers"
120+
echo " repository: $NEW_ORG/$NEW_REPO"
121+
echo " workflow filename: release.yml"
122+
echo " environment name: release"
123+
echo
95124
echo " 4. commit the rewrite, push, watch ci go green."
96125
echo
97126
echo "done."

0 commit comments

Comments
 (0)